From 64b16d950c4d3f2c93106c4a5f13f0f3a5df7e16 Mon Sep 17 00:00:00 2001 From: William Date: Wed, 11 Feb 2026 15:29:20 +0000 Subject: [PATCH] Improve database setup script with env flag and auto-discovery - Accept --env flag (dev, staging, prod) to target different environments - Auto-discover all migration files in supabase/migrations/ sorted by name - Skip seed data for prod environment (migrations only) - Load env-specific files (.env.dev, .env.staging) with .env.local fallback - Add db:setup:dev, db:setup:staging, db:setup:prod npm scripts Closes #5 Co-Authored-By: Claude Opus 4.6 --- package.json | 5 +- scripts/setup-database.js | 202 +++++++++++++++++++++++++++----------- 2 files changed, 148 insertions(+), 59 deletions(-) diff --git a/package.json b/package.json index eaf1d0f..928d000 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,10 @@ "build": "next build", "start": "next start", "lint": "eslint", - "type-check": "tsc --noEmit" + "type-check": "tsc --noEmit", + "db:setup:dev": "node scripts/setup-database.js --env dev", + "db:setup:staging": "node scripts/setup-database.js --env staging", + "db:setup:prod": "node scripts/setup-database.js --env prod" }, "dependencies": { "@radix-ui/react-avatar": "^1.1.11", diff --git a/scripts/setup-database.js b/scripts/setup-database.js index f05c88f..0901790 100644 --- a/scripts/setup-database.js +++ b/scripts/setup-database.js @@ -3,32 +3,89 @@ /** * Database Setup Script for Project Firefly * - * Opens the SQL files in your browser's Supabase SQL Editor - * for easy copy/paste execution. + * Runs all migrations in order and optionally seeds demo data. + * Loads environment variables based on the target environment. * * Usage: - * node scripts/setup-database.js + * node scripts/setup-database.js --env dev (migrations + seed) + * node scripts/setup-database.js --env staging (migrations + seed) + * node scripts/setup-database.js --env prod (migrations only, no seed) + * node scripts/setup-database.js (defaults to dev) */ const fs = require('fs'); const path = require('path'); const { exec } = require('child_process'); -// Load environment variables from .env.local -function loadEnv() { - const envPath = path.join(__dirname, '..', '.env.local'); - if (!fs.existsSync(envPath)) { - console.error('Error: .env.local not found. Please create it first.'); +const VALID_ENVS = ['dev', 'staging', 'prod']; +const SEED_ENVS = ['dev', 'staging']; + +function parseArgs() { + const args = process.argv.slice(2); + let env = 'dev'; + + for (let i = 0; i < args.length; i++) { + if (args[i] === '--env' && args[i + 1]) { + env = args[i + 1]; + i++; + } + } + + if (!VALID_ENVS.includes(env)) { + console.error(`Error: Invalid environment "${env}". Must be one of: ${VALID_ENVS.join(', ')}`); process.exit(1); } - const envContent = fs.readFileSync(envPath, 'utf-8'); - envContent.split('\n').forEach(line => { - const [key, ...valueParts] = line.split('='); - if (key && valueParts.length > 0) { - process.env[key.trim()] = valueParts.join('=').trim(); + return { env }; +} + +function loadEnv(env) { + // Try env-specific file first, then fall back to .env.local + const envFiles = [ + path.join(__dirname, '..', `.env.${env}`), + path.join(__dirname, '..', '.env.local'), + ]; + + let loaded = false; + for (const envPath of envFiles) { + if (fs.existsSync(envPath)) { + const envContent = fs.readFileSync(envPath, 'utf-8'); + envContent.split('\n').forEach(line => { + const trimmed = line.trim(); + if (!trimmed || trimmed.startsWith('#')) return; + const [key, ...valueParts] = trimmed.split('='); + if (key && valueParts.length > 0) { + process.env[key.trim()] = valueParts.join('=').trim(); + } + }); + console.log(` Loaded env from: ${path.basename(envPath)}`); + loaded = true; + break; } - }); + } + + if (!loaded) { + console.error(`Error: No env file found. Create .env.${env} or .env.local`); + process.exit(1); + } +} + +function discoverMigrations() { + const migrationsDir = path.join(__dirname, '..', 'supabase', 'migrations'); + if (!fs.existsSync(migrationsDir)) { + console.error('Error: supabase/migrations directory not found.'); + process.exit(1); + } + + const files = fs.readdirSync(migrationsDir) + .filter(f => f.endsWith('.sql')) + .sort(); + + return files.map(f => ({ + name: f, + path: path.join(migrationsDir, f), + sql: fs.readFileSync(path.join(migrationsDir, f), 'utf-8'), + })); } function copyToClipboard(text) { @@ -41,79 +98,108 @@ function copyToClipboard(text) { }); } +function waitForEnter() { + return new Promise(resolve => { + process.stdin.once('data', resolve); + }); +} + async function main() { + const { env } = parseArgs(); + const shouldSeed = SEED_ENVS.includes(env); + console.log('šŸ”„ Project Firefly - Database Setup\n'); console.log('='.repeat(50)); + console.log(` Environment: ${env}`); + console.log(` Seed data: ${shouldSeed ? 'yes' : 'no (production)'}`); + console.log('='.repeat(50)); // Load environment - loadEnv(); + loadEnv(env); const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; - if (!supabaseUrl) { - console.error('Error: NEXT_PUBLIC_SUPABASE_URL not set in .env.local'); + console.error('\nError: NEXT_PUBLIC_SUPABASE_URL not set.'); process.exit(1); } - // Extract project ref from URL + // Extract project ref and build SQL Editor URL const projectRef = supabaseUrl.replace('https://', '').split('.')[0]; const sqlEditorUrl = `https://supabase.com/dashboard/project/${projectRef}/sql/new`; - // Read SQL files - const migrationPath = path.join(__dirname, '..', 'supabase', 'migrations', '001_initial_schema.sql'); - const seedPath = path.join(__dirname, '..', 'supabase', 'seed.sql'); + console.log(` Supabase: ${projectRef}`); + console.log(''); - const migrationSQL = fs.readFileSync(migrationPath, 'utf-8'); - const seedSQL = fs.readFileSync(seedPath, 'utf-8'); + // Discover migrations + const migrations = discoverMigrations(); + console.log(` Found ${migrations.length} migration(s):`); + migrations.forEach(m => console.log(` • ${m.name}`)); + console.log(''); - console.log('\nšŸ“‹ STEP 1: Run the Migration\n'); - console.log(' The migration SQL has been copied to your clipboard.'); - console.log(' Opening Supabase SQL Editor...\n'); - - // Copy migration to clipboard - await copyToClipboard(migrationSQL); - - // Open SQL Editor + // Open SQL Editor once exec(`open "${sqlEditorUrl}"`, (err) => { if (err) console.log(' Could not open browser automatically.'); }); - console.log(' āœ… Migration SQL copied to clipboard!'); - console.log(' → Paste (Cmd+V) into the SQL Editor and click "Run"\n'); + // Run each migration + for (let i = 0; i < migrations.length; i++) { + const migration = migrations[i]; + const step = i + 1; - // Wait for user - console.log('-'.repeat(50)); - console.log('\n Press ENTER after running the migration...'); + console.log('-'.repeat(50)); + console.log(`\nšŸ“‹ STEP ${step}/${migrations.length}: ${migration.name}\n`); - await new Promise(resolve => { - process.stdin.once('data', resolve); - }); + await copyToClipboard(migration.sql); - console.log('\nšŸ“‹ STEP 2: Run the Seed Data\n'); - console.log(' The seed SQL has been copied to your clipboard.\n'); + console.log(' āœ… SQL copied to clipboard!'); + console.log(' → Paste (Cmd+V) into the SQL Editor and click "Run"'); + console.log('\n Press ENTER after running this migration...'); - // Copy seed to clipboard - await copyToClipboard(seedSQL); + await waitForEnter(); + } - console.log(' āœ… Seed SQL copied to clipboard!'); - console.log(' → Create a new query in SQL Editor'); - console.log(' → Paste (Cmd+V) and click "Run"\n'); + // Seed data (dev and staging only) + if (shouldSeed) { + const seedPath = path.join(__dirname, '..', 'supabase', 'seed.sql'); - // Wait for user - console.log('-'.repeat(50)); - console.log('\n Press ENTER after running the seed...'); + if (fs.existsSync(seedPath)) { + const seedSQL = fs.readFileSync(seedPath, 'utf-8'); - await new Promise(resolve => { - process.stdin.once('data', resolve); - }); + console.log('-'.repeat(50)); + console.log('\nšŸ“‹ SEED: Insert demo data\n'); + + await copyToClipboard(seedSQL); - console.log('\n='.repeat(50)); + console.log(' āœ… Seed SQL copied to clipboard!'); + console.log(' → Create a new query in SQL Editor'); + console.log(' → Paste (Cmd+V) and click "Run"'); + console.log('\n Press ENTER after running the seed...'); + + await waitForEnter(); + } else { + console.log('\n āš ļø No seed.sql found, skipping seed step.'); + } + } else { + console.log('\n ā­ļø Skipping seed data (production environment).'); + } + + // Done + console.log('\n' + '='.repeat(50)); console.log('\nšŸŽ‰ Database setup complete!\n'); - console.log(' Your database now has:'); - console.log(' • All required tables (users, vendors, orders, etc.)'); - console.log(' • Demo vendor: Maria\'s Kitchen'); - console.log(' • 5 menu items'); - console.log(' • 1 delivery person\n'); + console.log(` Environment: ${env}`); + console.log(` Migrations: ${migrations.length} applied`); + console.log(` Seed data: ${shouldSeed ? 'applied' : 'skipped'}`); + console.log(''); + + if (shouldSeed) { + console.log(' Your database now has:'); + console.log(' • All required tables (users, vendors, orders, etc.)'); + console.log(' • Demo vendor: Maria\'s Kitchen'); + console.log(' • 5 menu items'); + console.log(' • 1 delivery person'); + console.log(''); + } + console.log(' Next: Restart your dev server and visit http://localhost:3000/vendors\n'); }