supabase-stateful 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supabase-stateful",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "description": "Persistent local state for Supabase development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -71,9 +71,10 @@ export async function init() {
71
71
  }
72
72
  }
73
73
 
74
- // Add state file to .gitignore
74
+ // Add state file and backup to .gitignore
75
75
  await appendIfMissing('.gitignore', 'supabase/local-state.sql');
76
- log.success('Added state file to .gitignore');
76
+ await appendIfMissing('.gitignore', 'supabase/local-state.sql.backup');
77
+ log.success('Added state files to .gitignore');
77
78
 
78
79
  console.log('');
79
80
  log.success('Initialized!');
@@ -421,26 +421,9 @@ async function addDevScripts(services) {
421
421
  log.success('Added dev:local script');
422
422
  }
423
423
 
424
- // Add dev:all:local if they have concurrently
424
+ // Add dev:all:local - point to the bash script for graceful shutdown
425
425
  if (services.hasConcurrently && !pkg.scripts['dev:all:local']) {
426
- let cmds = '"npm run supabase:start" "npm run dev:local"';
427
- let names = 'SUPABASE,NEXT';
428
- let colors = 'green,cyan';
429
-
430
- if (services.hasInngest) {
431
- const inngestCmd = pkg.scripts['dev:inngest'] ? 'dev:inngest' : 'inngest';
432
- cmds += ` "npm run ${inngestCmd}"`;
433
- names += ',INNGEST';
434
- colors += ',magenta';
435
- }
436
-
437
- if (services.hasNgrok) {
438
- cmds += ' "npm run ngrok"';
439
- names += ',NGROK';
440
- colors += ',yellow';
441
- }
442
-
443
- pkg.scripts['dev:all:local'] = `concurrently ${cmds} --names "${names}" --prefix-colors "${colors}"`;
426
+ pkg.scripts['dev:all:local'] = './scripts/dev-local.sh';
444
427
  log.success('Added dev:all:local script');
445
428
  }
446
429
 
@@ -475,6 +458,11 @@ async function installGitHubWorkflow(workflowType, force = false) {
475
458
 
476
459
  log.success(`Created ${targetFile} (${workflowType === 'migrations' ? 'migrations only' : 'migrations + Vercel deploy'})`);
477
460
 
461
+ // For full workflow, add vercel.json to disable auto-deploy
462
+ if (workflowType === 'full') {
463
+ await addVercelConfig();
464
+ }
465
+
478
466
  // Show required secrets
479
467
  console.log('');
480
468
  log.info('Required GitHub secrets:');
@@ -485,9 +473,44 @@ async function installGitHubWorkflow(workflowType, force = false) {
485
473
  console.log(' - VERCEL_TOKEN');
486
474
  console.log(' - VERCEL_ORG_ID');
487
475
  console.log(' - VERCEL_PROJECT_ID');
476
+ console.log('');
477
+ log.info('Run `npx vercel link` to get VERCEL_ORG_ID and VERCEL_PROJECT_ID');
488
478
  }
489
479
  }
490
480
 
481
+ /**
482
+ * Add or update vercel.json to disable automatic deployments
483
+ * This ensures deploys only happen via GitHub Actions (after migrations pass)
484
+ */
485
+ async function addVercelConfig() {
486
+ const vercelJsonPath = 'vercel.json';
487
+ let config = {};
488
+
489
+ // Read existing config if it exists
490
+ if (await fileExists(vercelJsonPath)) {
491
+ try {
492
+ const content = await fs.readFile(vercelJsonPath, 'utf8');
493
+ config = JSON.parse(content);
494
+ } catch {
495
+ // If we can't parse it, start fresh
496
+ config = {};
497
+ }
498
+ }
499
+
500
+ // Check if already configured
501
+ if (config.git?.deploymentEnabled === false) {
502
+ log.dim('vercel.json already has deploymentEnabled: false');
503
+ return;
504
+ }
505
+
506
+ // Add git.deploymentEnabled = false
507
+ config.git = config.git || {};
508
+ config.git.deploymentEnabled = false;
509
+
510
+ await fs.writeFile(vercelJsonPath, JSON.stringify(config, null, 2) + '\n');
511
+ log.success('Updated vercel.json to disable auto-deploy (deploys only via GitHub Actions)');
512
+ }
513
+
491
514
  /**
492
515
  * Generate dev-local.sh script content
493
516
  */
@@ -512,42 +535,50 @@ function generateDevLocalScript(services) {
512
535
 
513
536
  # Local Development Script with Graceful Shutdown
514
537
  # Generated by supabase-stateful setup
515
- # Press Ctrl+C to stop - you'll be asked whether to save Supabase state
538
+ # Press Ctrl+C to stop and save Supabase state
516
539
 
517
540
  GREEN='\\033[0;32m'
518
541
  YELLOW='\\033[0;33m'
519
542
  CYAN='\\033[0;36m'
520
- RED='\\033[0;31m'
521
543
  NC='\\033[0m'
522
544
 
523
545
  echo -e "\${CYAN}Starting local development environment...\${NC}"
524
546
  echo ""
525
547
 
526
- # Run concurrently (it will handle Ctrl+C and stop all processes)
527
- npx concurrently \\
528
- --names "${names.join(',')}" \\
529
- --prefix-colors "${colors.join(',')}" \\
530
- --kill-others-on-fail \\
531
- ${commands.join(' \\\n ')}
548
+ # Cleanup function called on Ctrl+C
549
+ cleanup() {
550
+ echo ""
551
+ echo -e "\${YELLOW}Shutting down gracefully...\${NC}"
552
+ echo -e "\${CYAN}Saving Supabase state...\${NC}"
532
553
 
533
- # After concurrently exits (from Ctrl+C or error), ask about saving state
534
- echo ""
535
- echo -e "\${CYAN}Save Supabase state and stop? [Y/n]\${NC}"
536
- echo -e "\${CYAN}(Choose 'n' if you're just restarting the dev server)\${NC}"
554
+ # Kill the concurrently process group
555
+ kill \$DEV_PID 2>/dev/null
537
556
 
538
- # Read with timeout, default to Yes
539
- read -t 10 -n 1 response
540
- echo ""
557
+ # Wait a moment for processes to terminate
558
+ sleep 1
541
559
 
542
- if [[ "\$response" =~ ^[Nn]$ ]]; then
543
- echo -e "\${GREEN}Dev server stopped. Supabase still running.\${NC}"
544
- echo -e "Run \${YELLOW}npx supabase-stateful stop\${NC} later to save state."
545
- else
546
- echo -e "\${CYAN}Saving Supabase state...\${NC}"
560
+ # Save Supabase state
547
561
  npx supabase-stateful stop
562
+
548
563
  echo ""
549
564
  echo -e "\${GREEN}Development environment stopped. State saved.\${NC}"
550
- fi
565
+ exit 0
566
+ }
567
+
568
+ # Trap Ctrl+C (SIGINT) and call cleanup
569
+ trap cleanup SIGINT SIGTERM
570
+
571
+ # Start the development environment in background
572
+ npx concurrently \\
573
+ --names "${names.join(',')}" \\
574
+ --prefix-colors "${colors.join(',')}" \\
575
+ --kill-others-on-fail \\
576
+ ${commands.join(' \\\n ')} &
577
+
578
+ DEV_PID=\$!
579
+
580
+ # Wait for the background process
581
+ wait \$DEV_PID
551
582
  `;
552
583
  }
553
584
 
@@ -59,12 +59,33 @@ async function applyMigrations() {
59
59
  log.info('Checking for pending migrations...');
60
60
 
61
61
  try {
62
- const output = execSync('supabase migration list --output json', {
62
+ // Get migration list with --local flag to check local database status
63
+ const output = execSync('supabase migration list --local', {
63
64
  encoding: 'utf8',
64
65
  stdio: ['pipe', 'pipe', 'pipe'],
65
66
  });
66
67
 
67
- const pendingCount = (output.match(/"Applied": false/g) || []).length;
68
+ // Parse table output - pending migrations have version in Local column but empty in Remote column
69
+ // Format: " 20251221044839 | | 2025-12-21 04:48:39"
70
+ // A pending local migration has the version but an empty second column
71
+ const lines = output.split('\n');
72
+ let pendingCount = 0;
73
+
74
+ for (const line of lines) {
75
+ // Skip header lines and empty lines
76
+ if (line.includes('Local') || line.includes('---') || !line.trim()) continue;
77
+
78
+ // Split by | and check columns
79
+ const parts = line.split('|').map(p => p.trim());
80
+ if (parts.length >= 2) {
81
+ const localVersion = parts[0];
82
+ const remoteVersion = parts[1];
83
+ // If there's a local version but no remote version, it's pending
84
+ if (localVersion && /^\d+$/.test(localVersion) && !remoteVersion) {
85
+ pendingCount++;
86
+ }
87
+ }
88
+ }
68
89
 
69
90
  if (pendingCount > 0) {
70
91
  log.info(`Found ${pendingCount} pending migration(s)`);