#!/usr/bin/env tsx
import { readFileSync } from 'fs';
import { join } from 'path';
import { supabase } from './client.js';
import type { Style, Palette, CompatibilityMatrix } from '../types.js';
async function loadJSONFile<T>(filePath: string): Promise<T> {
try {
// Try different possible paths
const possiblePaths = [
join(process.cwd(), '..', filePath),
join('/Users/mariscal/.openclaw/workspace', filePath),
filePath
];
let content: string = '';
let successPath = '';
for (const path of possiblePaths) {
try {
content = readFileSync(path, 'utf-8');
successPath = path;
break;
} catch {
// Continue to next path
}
}
if (!content) {
throw new Error(`Could not find file at any of these paths: ${possiblePaths.join(', ')}`);
}
console.log(`π Loaded: ${successPath}`);
return JSON.parse(content) as T;
} catch (error) {
console.error(`β Error loading ${filePath}:`, error);
throw error;
}
}
async function checkAndCreateTables(): Promise<void> {
console.log('π Checking if tables exist...');
const client = supabase.getAnonClient();
const { exists, missing } = await supabase.checkTables();
if (missing.length > 0) {
console.log(`β οΈ Missing tables: ${missing.join(', ')}`);
console.log(`\nπ Please execute this SQL in your Supabase dashboard:\n`);
const sql = readFileSync(join(process.cwd(), 'sql', 'complete-setup.sql'), 'utf-8');
console.log('ββββββββββββββββββββββββββββββββββββββββ');
console.log(sql);
console.log('ββββββββββββββββββββββββββββββββββββββββ\n');
console.log('After running the SQL, you can continue with seeding.');
return;
}
console.log('β
All required tables exist');
}
async function seedStyles(): Promise<void> {
console.log('π₯ Seeding styles data...');
const stylesData = await loadJSONFile<Record<string, Style>>('../style-library/styles-complete.json');
const serviceClient = supabase.getServiceClient();
// Check if data already exists
const { count: existingCount } = await serviceClient
.from('design_styles')
.select('*', { count: 'exact', head: true });
if (existingCount && existingCount > 0) {
console.log(`βΉοΈ Found ${existingCount} existing styles. Skipping styles seeding.`);
return;
}
const styles = Object.values(stylesData).map(style => ({
id: style.id,
name: style.name,
industry: style.industry,
style_dna: style.styleDNA,
dos: style.dos,
donts: style.donts,
tokens: style.tokens,
raw_length: style.rawLength,
businesses: style.businesses,
category: style.category,
category_label: style.categoryLabel
}));
console.log(`Inserting ${styles.length} styles...`);
// Insert in smaller batches to avoid payload limits
const batchSize = 5;
for (let i = 0; i < styles.length; i += batchSize) {
const batch = styles.slice(i, i + batchSize);
const { error } = await serviceClient
.from('design_styles')
.upsert(batch);
if (error) {
console.error(`β Error inserting styles batch ${i}-${i + batch.length}:`, error);
throw error;
}
console.log(`β
Inserted styles ${i + 1}-${Math.min(i + batch.length, styles.length)} of ${styles.length}`);
// Small delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 100));
}
}
async function seedPalettes(): Promise<void> {
console.log('π₯ Seeding palettes data...');
const palettesData = await loadJSONFile<Palette[]>('../style-library/palettes/palettes-index.json');
const serviceClient = supabase.getServiceClient();
// Check if data already exists
const { count: existingCount } = await serviceClient
.from('design_palettes')
.select('*', { count: 'exact', head: true });
if (existingCount && existingCount > 0) {
console.log(`βΉοΈ Found ${existingCount} existing palettes. Skipping palettes seeding.`);
return;
}
const palettes = palettesData.map(palette => ({
id: palette.id,
name: palette.name,
mood: palette.mood,
industries: palette.industries,
category: palette.category,
primary_light: palette.primaryLight,
primary_dark: palette.primaryDark,
accent_light: palette.accentLight,
heading_font: palette.heading,
body_font: palette.body,
border_radius: palette.radius
}));
console.log(`Inserting ${palettes.length} palettes...`);
// Insert in smaller batches
const batchSize = 5;
for (let i = 0; i < palettes.length; i += batchSize) {
const batch = palettes.slice(i, i + batchSize);
const { error } = await serviceClient
.from('design_palettes')
.upsert(batch);
if (error) {
console.error(`β Error inserting palettes batch ${i}-${i + batch.length}:`, error);
throw error;
}
console.log(`β
Inserted palettes ${i + 1}-${Math.min(i + batch.length, palettes.length)} of ${palettes.length}`);
// Small delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 100));
}
}
async function seedCompatibility(): Promise<void> {
console.log('π₯ Seeding compatibility data...');
const compatibilityData = await loadJSONFile<CompatibilityMatrix[]>('../style-library/compatibility-matrix.json');
const serviceClient = supabase.getServiceClient();
// Check if data already exists
const { count: existingCount } = await serviceClient
.from('style_palette_compatibility')
.select('*', { count: 'exact', head: true });
if (existingCount && existingCount > 0) {
console.log(`βΉοΈ Found ${existingCount} existing compatibility records. Skipping compatibility seeding.`);
return;
}
// Flatten the compatibility data
const compatibilityRecords: Array<{
style_id: string;
palette_id: string;
score: number;
}> = [];
for (const styleMatrix of compatibilityData) {
for (const score of styleMatrix.allScores) {
compatibilityRecords.push({
style_id: styleMatrix.styleId,
palette_id: score.id,
score: score.score
});
}
}
console.log(`Inserting ${compatibilityRecords.length} compatibility records...`);
// Insert in smaller batches
const batchSize = 20;
for (let i = 0; i < compatibilityRecords.length; i += batchSize) {
const batch = compatibilityRecords.slice(i, i + batchSize);
const { error } = await serviceClient
.from('style_palette_compatibility')
.upsert(batch);
if (error) {
console.error(`β Error inserting compatibility batch ${i}-${i + batch.length}:`, error);
throw error;
}
console.log(`β
Inserted compatibility ${i + 1}-${Math.min(i + batch.length, compatibilityRecords.length)} of ${compatibilityRecords.length}`);
// Small delay to avoid rate limits
await new Promise(resolve => setTimeout(resolve, 50));
}
}
async function verifyData(): Promise<void> {
console.log('π Verifying seeded data...');
const anonClient = supabase.getAnonClient();
// Check styles count
const { count: stylesCount, error: stylesError } = await anonClient
.from('design_styles')
.select('*', { count: 'exact', head: true });
if (stylesError) {
console.error('β Error counting styles:', stylesError);
} else {
console.log(`β
Styles in database: ${stylesCount || 0}`);
}
// Check palettes count
const { count: palettesCount, error: palettesError } = await anonClient
.from('design_palettes')
.select('*', { count: 'exact', head: true });
if (palettesError) {
console.error('β Error counting palettes:', palettesError);
} else {
console.log(`β
Palettes in database: ${palettesCount || 0}`);
}
// Check compatibility count
const { count: compatibilityCount, error: compatibilityError } = await anonClient
.from('style_palette_compatibility')
.select('*', { count: 'exact', head: true });
if (compatibilityError) {
console.error('β Error counting compatibility records:', compatibilityError);
} else {
console.log(`β
Compatibility records in database: ${compatibilityCount || 0}`);
}
}
async function main(): Promise<void> {
console.log('π Starting WebForge database setup and seeding...');
try {
// Test connection
const isConnected = await supabase.testConnection();
if (!isConnected) {
throw new Error('Failed to connect to Supabase');
}
// Check tables exist
await checkAndCreateTables();
// Check if we can proceed with seeding
const { missing } = await supabase.checkTables();
if (missing.length > 0) {
console.log('β οΈ Cannot proceed with seeding until tables are created.');
return;
}
// Seed data
await seedStyles();
await seedPalettes();
await seedCompatibility();
// Verify the data was inserted
await verifyData();
console.log('π Database setup and seeding completed successfully!');
} catch (error) {
console.error('π₯ Setup failed:', error);
process.exit(1);
}
}
// Run if called directly
if (import.meta.url === `file://${process.argv[1]}`) {
main();
}