spine-framework 0.1.14 ā 0.1.15
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.
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/commands/migrate
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* `spine-framework migrate` ā Apply SQL migrations to a Supabase project
|
|
9
|
+
* via a direct Postgres connection.
|
|
10
|
+
*
|
|
11
|
+
* Requires --db-password (the Supabase database password).
|
|
12
|
+
* Get it from: https://supabase.com/dashboard/project/_/settings/database
|
|
13
|
+
*
|
|
14
|
+
* **Usage:**
|
|
15
|
+
* ```bash
|
|
16
|
+
* spine-framework migrate --db-password <password>
|
|
17
|
+
* spine-framework migrate --db-password <password> --dry-run
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import type { Command } from 'commander'
|
|
22
|
+
import { existsSync, readdirSync, readFileSync } from 'fs'
|
|
23
|
+
import { resolve, dirname } from 'path'
|
|
24
|
+
import { fileURLToPath } from 'url'
|
|
25
|
+
|
|
26
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
27
|
+
const __dirname = dirname(__filename)
|
|
28
|
+
const MIGRATIONS_DIR = resolve(__dirname, '../../migrations')
|
|
29
|
+
|
|
30
|
+
interface MigrateOptions {
|
|
31
|
+
dbPassword: string
|
|
32
|
+
dryRun: boolean
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function migrateCommand(options: MigrateOptions): Promise<void> {
|
|
36
|
+
console.log('\nšļø Spine Framework ā Migrate\n')
|
|
37
|
+
|
|
38
|
+
const supabaseUrl = process.env.SUPABASE_URL
|
|
39
|
+
if (!supabaseUrl) {
|
|
40
|
+
console.error('ā SUPABASE_URL not set. Run `spine-framework init` first.')
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Derive connection string from Supabase URL
|
|
45
|
+
// Format: https://<ref>.supabase.co ā postgres://postgres.<ref>:<password>@aws-0-<region>.pooler.supabase.com:5432/postgres
|
|
46
|
+
// Use direct connection (port 5432) via the db.<ref>.supabase.co host
|
|
47
|
+
const projectRef = supabaseUrl.replace('https://', '').split('.')[0]
|
|
48
|
+
const connectionString = `postgresql://postgres:${options.dbPassword}@db.${projectRef}.supabase.co:5432/postgres`
|
|
49
|
+
|
|
50
|
+
if (!existsSync(MIGRATIONS_DIR)) {
|
|
51
|
+
console.error(`ā Migrations directory not found: ${MIGRATIONS_DIR}`)
|
|
52
|
+
process.exit(1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const files = readdirSync(MIGRATIONS_DIR)
|
|
56
|
+
.filter(f => f.endsWith('.sql'))
|
|
57
|
+
.sort()
|
|
58
|
+
|
|
59
|
+
if (files.length === 0) {
|
|
60
|
+
console.log(' No migration files found.')
|
|
61
|
+
return
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(` Found ${files.length} migration file(s)\n`)
|
|
65
|
+
|
|
66
|
+
if (options.dryRun) {
|
|
67
|
+
for (const file of files) {
|
|
68
|
+
console.log(` [dry-run] Would apply: ${file}`)
|
|
69
|
+
}
|
|
70
|
+
return
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Dynamically import pg to avoid loading it at module init time
|
|
74
|
+
const { default: pg } = await import('pg')
|
|
75
|
+
const client = new pg.Client({ connectionString })
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
await client.connect()
|
|
79
|
+
console.log(' ā Connected to database\n')
|
|
80
|
+
} catch (err: any) {
|
|
81
|
+
console.error(`ā Could not connect to database: ${err.message}`)
|
|
82
|
+
console.error(' Check your --db-password and that the project is active.')
|
|
83
|
+
process.exit(1)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
let applied = 0
|
|
87
|
+
let failed = 0
|
|
88
|
+
|
|
89
|
+
for (const file of files) {
|
|
90
|
+
const sql = readFileSync(resolve(MIGRATIONS_DIR, file), 'utf8')
|
|
91
|
+
process.stdout.write(` Applying ${file}... `)
|
|
92
|
+
try {
|
|
93
|
+
await client.query(sql)
|
|
94
|
+
console.log('ā')
|
|
95
|
+
applied++
|
|
96
|
+
} catch (err: any) {
|
|
97
|
+
console.log('ā')
|
|
98
|
+
console.error(` ${err.message}`)
|
|
99
|
+
failed++
|
|
100
|
+
// Continue applying remaining migrations
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await client.end()
|
|
105
|
+
|
|
106
|
+
console.log(`\n ${applied} applied, ${failed} failed`)
|
|
107
|
+
|
|
108
|
+
if (failed > 0) {
|
|
109
|
+
console.error('\nā Some migrations failed. Check errors above.')
|
|
110
|
+
process.exit(1)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
console.log('\nā
All migrations applied successfully!')
|
|
114
|
+
console.log('\n Next steps:')
|
|
115
|
+
console.log(' 1. spine-framework install-app <app-slug>')
|
|
116
|
+
console.log(' 2. npm run assemble && netlify dev')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function registerMigrateCommands(program: Command) {
|
|
120
|
+
program
|
|
121
|
+
.command('migrate')
|
|
122
|
+
.description('Apply SQL migrations via direct Postgres connection')
|
|
123
|
+
.requiredOption('--db-password <password>', 'Supabase database password (from dashboard ā Settings ā Database)')
|
|
124
|
+
.option('--dry-run', 'Show what would happen without making changes', false)
|
|
125
|
+
.action(async (opts) => {
|
|
126
|
+
try {
|
|
127
|
+
await migrateCommand(opts)
|
|
128
|
+
} catch (err: any) {
|
|
129
|
+
console.error('Error:', err.message)
|
|
130
|
+
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
131
|
+
process.exit(1)
|
|
132
|
+
}
|
|
133
|
+
})
|
|
134
|
+
}
|
package/.framework/cli/index.ts
CHANGED
|
@@ -66,6 +66,7 @@ const [
|
|
|
66
66
|
{ registerInstallAppCommands },
|
|
67
67
|
{ registerStatusCommands },
|
|
68
68
|
{ registerUninstallAppCommands },
|
|
69
|
+
{ registerMigrateCommands },
|
|
69
70
|
] = await Promise.all([
|
|
70
71
|
import('./commands/auth.ts'),
|
|
71
72
|
import('./commands/pipelines.ts'),
|
|
@@ -82,6 +83,7 @@ const [
|
|
|
82
83
|
import('./commands/install-app.ts'),
|
|
83
84
|
import('./commands/status.ts'),
|
|
84
85
|
import('./commands/uninstall-app.ts'),
|
|
86
|
+
import('./commands/migrate.ts'),
|
|
85
87
|
])
|
|
86
88
|
|
|
87
89
|
registerAuthCommands(program)
|
|
@@ -99,6 +101,7 @@ registerInitCommands(program)
|
|
|
99
101
|
registerInstallAppCommands(program)
|
|
100
102
|
registerStatusCommands(program)
|
|
101
103
|
registerUninstallAppCommands(program)
|
|
104
|
+
registerMigrateCommands(program)
|
|
102
105
|
|
|
103
106
|
program.parseAsync(process.argv).catch((err) => {
|
|
104
107
|
console.error('Error:', err.message)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module cli/commands/migrate
|
|
3
|
+
* @audience installer
|
|
4
|
+
* @layer cli
|
|
5
|
+
* @stability stable
|
|
6
|
+
*
|
|
7
|
+
* `spine-framework migrate` ā Apply SQL migrations to a Supabase project
|
|
8
|
+
* via a direct Postgres connection.
|
|
9
|
+
*
|
|
10
|
+
* Requires --db-password (the Supabase database password).
|
|
11
|
+
* Get it from: https://supabase.com/dashboard/project/_/settings/database
|
|
12
|
+
*
|
|
13
|
+
* **Usage:**
|
|
14
|
+
* ```bash
|
|
15
|
+
* spine-framework migrate --db-password <password>
|
|
16
|
+
* spine-framework migrate --db-password <password> --dry-run
|
|
17
|
+
* ```
|
|
18
|
+
*/
|
|
19
|
+
import type { Command } from 'commander';
|
|
20
|
+
export declare function registerMigrateCommands(program: Command): void;
|
|
21
|
+
//# sourceMappingURL=migrate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"migrate.d.ts","sourceRoot":"","sources":["../../../.framework/cli/commands/migrate.ts"],"names":[],"mappings":"AACA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AAkGxC,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,OAAO,QAevD"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spine-framework",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.15",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Spine ā enterprise application framework built on Supabase + Netlify + React",
|
|
6
6
|
"type": "module",
|
|
@@ -90,7 +90,8 @@
|
|
|
90
90
|
"jsonwebtoken": "^9.0.3",
|
|
91
91
|
"jwt-decode": "^4.0.0",
|
|
92
92
|
"tsx": "^4.21.0",
|
|
93
|
-
"ws": "^8.21.0"
|
|
93
|
+
"ws": "^8.21.0",
|
|
94
|
+
"pg": "^8.13.0"
|
|
94
95
|
},
|
|
95
96
|
"peerDependencies": {
|
|
96
97
|
"@netlify/functions": "^2.0.0",
|