spine-framework 0.3.73 → 0.3.75

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.
@@ -23,6 +23,7 @@ import { resolve, dirname, join, basename } from 'path'
23
23
  import { fileURLToPath } from 'url'
24
24
  import { execSync } from 'child_process'
25
25
  import { createClient } from '@supabase/supabase-js'
26
+ import { runMigrations } from './migrate.js'
26
27
 
27
28
  const __filename = fileURLToPath(import.meta.url)
28
29
  const __dirname = dirname(__filename)
@@ -85,12 +86,30 @@ export function registerInstallAppCommands(program: Command) {
85
86
  }
86
87
  console.log(` ✓ App files copied to custom/apps/${appSlug}/`)
87
88
 
88
- // 5. Seed types, roles, link-types into database
89
+ // 5. Run pending migrations before seeding so schema is up to date
90
+ console.log(' Running migrations...')
91
+ try {
92
+ const migrateResult = await runMigrations({ frameworkOnly: false })
93
+ if (migrateResult === null) {
94
+ console.log(' ○ Skipping migrations (SUPABASE_DB_PASSWORD not set)')
95
+ } else if (migrateResult.applied > 0) {
96
+ console.log(` ✓ Migrations: ${migrateResult.applied} applied, ${migrateResult.skipped} skipped`)
97
+ } else {
98
+ console.log(` ✓ Migrations: up to date (${migrateResult.skipped} already applied)`)
99
+ }
100
+ if (migrateResult && migrateResult.failed > 0) {
101
+ console.warn(` ⚠ ${migrateResult.failed} migration(s) failed — seed may fail`)
102
+ }
103
+ } catch (migrateErr: any) {
104
+ console.warn(` ⚠ Migration error: ${migrateErr.message} — continuing with seed`)
105
+ }
106
+
107
+ // 6. Seed types, roles, link-types into database
89
108
  if (!opts.seed === false) {
90
109
  await seedApp(appDest, appSlug)
91
110
  }
92
111
 
93
- // 6. Aggregate registration config from manifest into spine.config.json
112
+ // 7. Aggregate registration config from manifest into spine.config.json
94
113
  const manifestPath = join(appDest, 'manifest.json')
95
114
  let manifest: Record<string, any> = {}
96
115
  try { manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) } catch {}
@@ -35,62 +35,32 @@ interface MigrateOptions {
35
35
  frameworkOnly: boolean
36
36
  }
37
37
 
38
- async function migrateCommand(options: MigrateOptions): Promise<void> {
39
- console.log('\nRunning Spine Framework migrations...\n')
40
-
41
- // Load .env from project root
42
- loadEnv()
43
-
38
+ /**
39
+ * Run all pending migrations programmatically.
40
+ * Returns { applied, skipped, failed } — throws on connection failure.
41
+ * Silent on missing SUPABASE_DB_PASSWORD (returns null to indicate skipped).
42
+ */
43
+ export async function runMigrations(opts: { frameworkOnly?: boolean } = {}): Promise<{ applied: number; skipped: number; failed: number } | null> {
44
44
  const supabaseUrl = process.env.SUPABASE_URL
45
45
  if (!supabaseUrl || supabaseUrl.includes('placeholder') || supabaseUrl.includes('your-project')) {
46
- console.error('Error: SUPABASE_URL not set. Edit your .env file first.')
47
- console.error(' Get it from: https://supabase.com/dashboard/project/_/settings/api')
48
- process.exit(1)
46
+ return null
49
47
  }
50
48
 
51
- const dbPassword = options.dbPassword || process.env.SUPABASE_DB_PASSWORD
49
+ const dbPassword = process.env.SUPABASE_DB_PASSWORD
52
50
  if (!dbPassword) {
53
- console.error('Error: SUPABASE_DB_PASSWORD not set.')
54
- console.error(' Add it to .env or pass --db-password <password>')
55
- console.error(' Get it from: https://supabase.com/dashboard/project/_/settings/database')
56
- process.exit(1)
51
+ return null
57
52
  }
58
53
 
59
54
  const projectRef = supabaseUrl.replace('https://', '').split('.')[0]
60
55
  const connectionString = `postgresql://postgres:${dbPassword}@db.${projectRef}.supabase.co:5432/postgres`
61
56
 
62
- // Collect migration files
63
- const migrationFiles = collectMigrations(options.frameworkOnly)
64
-
65
- if (migrationFiles.length === 0) {
66
- console.log('No migration files found.')
67
- return
68
- }
69
-
70
- console.log(` Found ${migrationFiles.length} migration file(s)\n`)
71
-
72
- if (options.dryRun) {
73
- for (const file of migrationFiles) {
74
- console.log(` [dry-run] Would apply: ${file.name}`)
75
- }
76
- console.log('\nDry run complete — no changes made.\n')
77
- return
78
- }
57
+ const migrationFiles = collectMigrations(opts.frameworkOnly ?? false)
58
+ if (migrationFiles.length === 0) return { applied: 0, skipped: 0, failed: 0 }
79
59
 
80
- // Dynamically import pg to avoid loading it at module init time
81
60
  const { default: pg } = await import('pg')
82
61
  const client = new pg.Client({ connectionString })
62
+ await client.connect()
83
63
 
84
- try {
85
- await client.connect()
86
- console.log(' Connected to database\n')
87
- } catch (err: any) {
88
- console.error(`Error: Could not connect to database: ${err.message}`)
89
- console.error(' Check your SUPABASE_DB_PASSWORD and that the project is active.')
90
- process.exit(1)
91
- }
92
-
93
- // Create migration tracking table if it doesn't exist
94
64
  try {
95
65
  await client.query(`
96
66
  CREATE TABLE IF NOT EXISTS _spine_migrations (
@@ -99,40 +69,28 @@ async function migrateCommand(options: MigrateOptions): Promise<void> {
99
69
  applied_at TIMESTAMPTZ DEFAULT NOW()
100
70
  )
101
71
  `)
102
- } catch {
103
- // Non-fatal: proceed without tracking
104
- }
72
+ } catch { /* non-fatal */ }
105
73
 
106
- // Load already-applied migrations
107
74
  let appliedMigrations = new Set<string>()
108
75
  try {
109
76
  const { rows } = await client.query('SELECT filename FROM _spine_migrations')
110
77
  appliedMigrations = new Set(rows.map((r: { filename: string }) => r.filename))
111
- } catch {
112
- // Non-fatal: proceed without tracking
113
- }
78
+ } catch { /* non-fatal */ }
114
79
 
115
- let applied = 0
116
- let skipped = 0
117
- let failed = 0
80
+ let applied = 0, skipped = 0, failed = 0
118
81
 
119
82
  for (const { name, path } of migrationFiles) {
120
83
  if (appliedMigrations.has(name)) {
121
- console.log(` Skipping ${name} (already applied)`)
122
84
  skipped++
123
85
  continue
124
86
  }
125
87
 
126
88
  const sql = readFileSync(path, 'utf8')
127
89
  process.stdout.write(` Applying ${name}... `)
128
-
129
90
  const statements = splitSqlStatements(sql)
130
91
 
131
92
  try {
132
- for (const stmt of statements) {
133
- await client.query(stmt)
134
- }
135
- // Record as applied
93
+ for (const stmt of statements) await client.query(stmt)
136
94
  await client.query(
137
95
  `INSERT INTO _spine_migrations (filename) VALUES ($1) ON CONFLICT (filename) DO NOTHING`,
138
96
  [name]
@@ -147,10 +105,60 @@ async function migrateCommand(options: MigrateOptions): Promise<void> {
147
105
  }
148
106
 
149
107
  await client.end()
108
+ return { applied, skipped, failed }
109
+ }
110
+
111
+ async function migrateCommand(options: MigrateOptions): Promise<void> {
112
+ console.log('\nRunning Spine Framework migrations...\n')
113
+
114
+ // Load .env from project root
115
+ loadEnv()
116
+
117
+ const supabaseUrl = process.env.SUPABASE_URL
118
+ if (!supabaseUrl || supabaseUrl.includes('placeholder') || supabaseUrl.includes('your-project')) {
119
+ console.error('Error: SUPABASE_URL not set. Edit your .env file first.')
120
+ console.error(' Get it from: https://supabase.com/dashboard/project/_/settings/api')
121
+ process.exit(1)
122
+ }
123
+
124
+ const dbPassword = options.dbPassword || process.env.SUPABASE_DB_PASSWORD
125
+ if (!dbPassword) {
126
+ console.error('Error: SUPABASE_DB_PASSWORD not set.')
127
+ console.error(' Add it to .env or pass --db-password <password>')
128
+ console.error(' Get it from: https://supabase.com/dashboard/project/_/settings/database')
129
+ process.exit(1)
130
+ }
131
+
132
+ // Override env with CLI flag if provided
133
+ if (options.dbPassword) process.env.SUPABASE_DB_PASSWORD = options.dbPassword
134
+
135
+ const migrationFiles = collectMigrations(options.frameworkOnly)
136
+
137
+ if (migrationFiles.length === 0) {
138
+ console.log('No migration files found.')
139
+ return
140
+ }
141
+
142
+ console.log(` Found ${migrationFiles.length} migration file(s)\n`)
143
+
144
+ if (options.dryRun) {
145
+ for (const file of migrationFiles) {
146
+ console.log(` [dry-run] Would apply: ${file.name}`)
147
+ }
148
+ console.log('\nDry run complete — no changes made.\n')
149
+ return
150
+ }
151
+
152
+ const result = await runMigrations({ frameworkOnly: options.frameworkOnly })
153
+
154
+ if (!result) {
155
+ console.error('Could not connect: SUPABASE_URL or SUPABASE_DB_PASSWORD missing.')
156
+ process.exit(1)
157
+ }
150
158
 
151
- console.log(`\n ${applied} applied, ${skipped} skipped, ${failed} failed`)
159
+ console.log(`\n ${result.applied} applied, ${result.skipped} skipped, ${result.failed} failed`)
152
160
 
153
- if (failed > 0) {
161
+ if (result.failed > 0) {
154
162
  console.error('\nSome migrations failed. Check errors above.')
155
163
  process.exit(1)
156
164
  }
@@ -1,3 +1,4 @@
1
- /api/* /.netlify/functions/:splat 200
2
- /v2/* /.netlify/v2/functions/:splat 200
3
- /* /index.html 200
1
+ /api/* /.netlify/functions/:splat 200
2
+ /v2/* /.netlify/v2/functions/:splat 200
3
+ /spine.config.json /spine.config.json 200
4
+ /* /index.html 200
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "spine-framework",
3
- "version": "0.3.73",
3
+ "version": "0.3.75",
4
4
  "description": "Multi-tenant, modular application platform for modern SaaS systems",
5
5
  "type": "module",
6
6
  "bin": {