spine-framework 0.3.16 → 0.3.18
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/.framework/cli/commands/create-app.ts +1 -1
- package/.framework/cli/commands/dev.ts +215 -33
- package/.framework/cli/index.ts +37 -75
- package/.framework/functions/_shared/app-manifest.ts +8 -15
- package/.framework/functions/_shared/db.ts +11 -15
- package/.framework/functions/_shared/index.ts +7 -16
- package/.framework/functions/_shared/principal.ts +4 -62
- package/.framework/functions/_shared/resolve-ids.ts +6 -6
- package/.framework/functions/_shared/webhook-registry.ts +43 -1
- package/.framework/functions/admin-funnel.ts +104 -0
- package/.framework/functions/admin-install-funnel.ts +225 -0
- package/.framework/functions/admin-signals.ts +241 -0
- package/.framework/functions/integration-routes.ts +3 -7
- package/.framework/migrations/001_schema.sql +406 -0
- package/.framework/migrations/002_rls_and_functions.sql +710 -0
- package/.framework/migrations/003_system_data.sql +520 -0
- package/.framework/migrations/004_enhancements.sql +43 -0
- package/.framework/public/_redirects +3 -0
- package/.framework/public/spine-logo.jpg +0 -0
- package/.framework/public/spine-logo.svg +3 -0
- package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
- package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
- package/{scripts → .framework/scripts}/assemble.sh +2 -8
- package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
- package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
- package/.framework/seeds/link_types/account_cli_instances.json +9 -0
- package/.framework/seeds/types/funnel_signal_use.json +98 -0
- package/.framework/seeds/types/spine_instance.json +109 -0
- package/.framework/src/App.tsx +11 -21
- package/.framework/src/components/CustomAppLoader.tsx +2 -19
- package/.framework/src/components/ui/accordion.tsx +1 -1
- package/.framework/src/components/ui/alert-dialog.tsx +1 -1
- package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
- package/.framework/src/components/ui/avatar.tsx +1 -1
- package/.framework/src/components/ui/badge.tsx +2 -2
- package/.framework/src/components/ui/breadcrumb.tsx +2 -2
- package/.framework/src/components/ui/button-group.tsx +2 -2
- package/.framework/src/components/ui/button.tsx +2 -2
- package/.framework/src/components/ui/chart.tsx +6 -1
- package/.framework/src/components/ui/checkbox.tsx +1 -1
- package/.framework/src/components/ui/collapsible.tsx +1 -1
- package/.framework/src/components/ui/context-menu.tsx +1 -1
- package/.framework/src/components/ui/dialog.tsx +1 -1
- package/.framework/src/components/ui/direction.tsx +17 -1
- package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
- package/.framework/src/components/ui/hover-card.tsx +1 -1
- package/.framework/src/components/ui/item.tsx +2 -2
- package/.framework/src/components/ui/label.tsx +1 -1
- package/.framework/src/components/ui/menubar.tsx +1 -1
- package/.framework/src/components/ui/navigation-menu.tsx +1 -1
- package/.framework/src/components/ui/popover.tsx +1 -1
- package/.framework/src/components/ui/progress.tsx +1 -1
- package/.framework/src/components/ui/radio-group.tsx +1 -1
- package/.framework/src/components/ui/scroll-area.tsx +1 -1
- package/.framework/src/components/ui/select.tsx +1 -1
- package/.framework/src/components/ui/separator.tsx +1 -1
- package/.framework/src/components/ui/sheet.tsx +1 -1
- package/.framework/src/components/ui/sidebar.tsx +6 -6
- package/.framework/src/components/ui/slider.tsx +1 -1
- package/.framework/src/components/ui/switch.tsx +1 -1
- package/.framework/src/components/ui/tabs.tsx +1 -1
- package/.framework/src/components/ui/toggle-group.tsx +1 -1
- package/.framework/src/components/ui/toggle.tsx +1 -1
- package/.framework/src/components/ui/tooltip.tsx +1 -1
- package/.framework/src/pages/auth/LoginPage.tsx +10 -9
- package/LICENSE.md +63 -93
- package/README.md +254 -129
- package/custom/.devin/skills/install-app/SKILL.md +39 -0
- package/custom/.devin/workflows/app-install.yaml +83 -0
- package/custom/README.md +79 -0
- package/docs/APPS.md +232 -0
- package/docs/ARCHITECTURE.md +246 -0
- package/docs/INSTALL.md +155 -0
- package/docs/README.md +170 -0
- package/docs/design-schema-spec.md +1837 -0
- package/docs/dev-guides/01-create-an-app.md +183 -0
- package/docs/dev-guides/02-create-a-function.md +178 -0
- package/docs/dev-guides/03-create-a-component.md +178 -0
- package/docs/dev-guides/04-webhook-handlers.md +129 -0
- package/docs/dev-guides/05-testing.md +168 -0
- package/docs/dev-guides/README.md +40 -0
- package/docs/npm-packages-plan.md +288 -0
- package/docs/spine-core--api-reference.md +822 -0
- package/package.json +78 -150
- package/.framework/cli/bin.cjs +0 -14
- package/.framework/cli/commands/claim.ts +0 -430
- package/.framework/cli/commands/init.ts +0 -219
- package/.framework/cli/commands/install-app.ts +0 -685
- package/.framework/cli/commands/migrate.ts +0 -178
- package/.framework/cli/commands/publish.ts +0 -191
- package/.framework/cli/commands/status.ts +0 -268
- package/.framework/cli/commands/uninstall-app.ts +0 -166
- package/.framework/cli/commands/update-app.ts +0 -397
- package/.framework/cli/welcome.cjs +0 -45
- package/.framework/config/signal-client.ts +0 -147
- package/.framework/config/signals.ts +0 -70
- package/.framework/docs/STABILITY.md +0 -52
- package/.framework/docs/examples/custom-app/README.md +0 -77
- package/.framework/docs/examples/custom-function/README.md +0 -27
- package/.framework/docs/examples/custom-function/handler.ts +0 -48
- package/.framework/docs/examples/custom-webhook/README.md +0 -68
- package/.framework/functions/handler-registry.ts +0 -22
- package/.framework/functions/signal.ts +0 -168
- package/.framework/migrations/000_foundation.sql +0 -1302
- package/.framework/migrations/001_seed.sql +0 -163
- package/.framework/migrations/002_seed_constraints.sql +0 -19
- package/.framework/migrations/003_auth_user_trigger.sql +0 -67
- package/.framework/migrations/004_app_version_tracking.sql +0 -8
- package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
- package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
- package/CHANGELOG.md +0 -68
- package/STRUCTURE.md +0 -150
- package/bin/spine-framework.cjs +0 -62
- package/bin/welcome.cjs +0 -58
- package/bin/ws-shim.ts +0 -10
- package/config/components.json +0 -25
- package/config/deno.lock +0 -108
- package/config/package-lock.json +0 -17183
- package/config/postcss.config.cjs +0 -10
- package/config/tailwind.config.cjs +0 -78
- package/config/tsconfig.build.json +0 -32
- package/config/tsconfig.cli.json +0 -18
- package/config/tsconfig.json +0 -41
- package/config/tsconfig.node.json +0 -17
- package/config/tsconfig.node.tsbuildinfo +0 -1
- package/config/tsconfig.tsbuildinfo +0 -1
- package/config/typedoc.json +0 -16
- package/config/vite.config.d.ts +0 -2
- package/config/vite.config.ts +0 -71
- package/dist/cli/commands/agents.d.ts +0 -39
- package/dist/cli/commands/agents.d.ts.map +0 -1
- package/dist/cli/commands/auth.d.ts +0 -36
- package/dist/cli/commands/auth.d.ts.map +0 -1
- package/dist/cli/commands/claim.d.ts +0 -38
- package/dist/cli/commands/claim.d.ts.map +0 -1
- package/dist/cli/commands/create-app.d.ts +0 -23
- package/dist/cli/commands/create-app.d.ts.map +0 -1
- package/dist/cli/commands/dev.d.ts +0 -24
- package/dist/cli/commands/dev.d.ts.map +0 -1
- package/dist/cli/commands/doctor.d.ts +0 -42
- package/dist/cli/commands/doctor.d.ts.map +0 -1
- package/dist/cli/commands/generate.d.ts +0 -36
- package/dist/cli/commands/generate.d.ts.map +0 -1
- package/dist/cli/commands/init.d.ts +0 -20
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/install-app.d.ts +0 -30
- package/dist/cli/commands/install-app.d.ts.map +0 -1
- package/dist/cli/commands/items.d.ts +0 -45
- package/dist/cli/commands/items.d.ts.map +0 -1
- package/dist/cli/commands/migrate.d.ts +0 -21
- package/dist/cli/commands/migrate.d.ts.map +0 -1
- package/dist/cli/commands/migrations.d.ts +0 -41
- package/dist/cli/commands/migrations.d.ts.map +0 -1
- package/dist/cli/commands/pipelines.d.ts +0 -40
- package/dist/cli/commands/pipelines.d.ts.map +0 -1
- package/dist/cli/commands/publish.d.ts +0 -23
- package/dist/cli/commands/publish.d.ts.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -23
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/system.d.ts +0 -29
- package/dist/cli/commands/system.d.ts.map +0 -1
- package/dist/cli/commands/test.d.ts +0 -46
- package/dist/cli/commands/test.d.ts.map +0 -1
- package/dist/cli/commands/uninstall-app.d.ts +0 -23
- package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
- package/dist/cli/commands/update-app.d.ts +0 -24
- package/dist/cli/commands/update-app.d.ts.map +0 -1
- package/dist/cli/context.d.ts +0 -88
- package/dist/cli/context.d.ts.map +0 -1
- package/dist/cli/env-loader.d.ts +0 -14
- package/dist/cli/env-loader.d.ts.map +0 -1
- package/dist/cli/index.d.ts +0 -44
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/config/signals.d.ts +0 -82
- package/dist/config/signals.d.ts.map +0 -1
- package/dist/functions/_shared/agent-runner.d.ts +0 -156
- package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
- package/dist/functions/_shared/app-manifest.d.ts +0 -68
- package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
- package/dist/functions/_shared/audit.d.ts +0 -91
- package/dist/functions/_shared/audit.d.ts.map +0 -1
- package/dist/functions/_shared/db.d.ts +0 -125
- package/dist/functions/_shared/db.d.ts.map +0 -1
- package/dist/functions/_shared/index.d.ts +0 -299
- package/dist/functions/_shared/index.d.ts.map +0 -1
- package/dist/functions/_shared/middleware.d.ts +0 -315
- package/dist/functions/_shared/middleware.d.ts.map +0 -1
- package/dist/functions/_shared/permissions.d.ts +0 -626
- package/dist/functions/_shared/permissions.d.ts.map +0 -1
- package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
- package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
- package/dist/functions/_shared/principal.d.ts +0 -284
- package/dist/functions/_shared/principal.d.ts.map +0 -1
- package/dist/functions/_shared/resolve-ids.d.ts +0 -10
- package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
- package/dist/functions/_shared/schema-utils.d.ts +0 -181
- package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
- package/dist/functions/_shared/testing.d.ts +0 -172
- package/dist/functions/_shared/testing.d.ts.map +0 -1
- package/dist/functions/_shared/trigger-engine.d.ts +0 -140
- package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
- package/dist/functions/_shared/webhook-registration.d.ts +0 -81
- package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
- package/dist/functions/_shared/webhook-registry.d.ts +0 -41
- package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
- package/dist/functions/account-nodes.d.ts +0 -48
- package/dist/functions/account-nodes.d.ts.map +0 -1
- package/dist/functions/admin-data.d.ts +0 -178
- package/dist/functions/admin-data.d.ts.map +0 -1
- package/dist/functions/ai-agents.d.ts +0 -125
- package/dist/functions/ai-agents.d.ts.map +0 -1
- package/dist/functions/api-keys.d.ts +0 -140
- package/dist/functions/api-keys.d.ts.map +0 -1
- package/dist/functions/apps.d.ts +0 -163
- package/dist/functions/apps.d.ts.map +0 -1
- package/dist/functions/auth.d.ts +0 -74
- package/dist/functions/auth.d.ts.map +0 -1
- package/dist/functions/debug-auth.d.ts +0 -33
- package/dist/functions/debug-auth.d.ts.map +0 -1
- package/dist/functions/embeddings.d.ts +0 -205
- package/dist/functions/embeddings.d.ts.map +0 -1
- package/dist/functions/handler-registry.d.ts +0 -22
- package/dist/functions/handler-registry.d.ts.map +0 -1
- package/dist/functions/integration-routes.d.ts +0 -45
- package/dist/functions/integration-routes.d.ts.map +0 -1
- package/dist/functions/integrations.d.ts +0 -124
- package/dist/functions/integrations.d.ts.map +0 -1
- package/dist/functions/item-progress.d.ts +0 -41
- package/dist/functions/item-progress.d.ts.map +0 -1
- package/dist/functions/logs.d.ts +0 -162
- package/dist/functions/logs.d.ts.map +0 -1
- package/dist/functions/observability.d.ts +0 -123
- package/dist/functions/observability.d.ts.map +0 -1
- package/dist/functions/pipeline-executions.d.ts +0 -190
- package/dist/functions/pipeline-executions.d.ts.map +0 -1
- package/dist/functions/pipelines.d.ts +0 -171
- package/dist/functions/pipelines.d.ts.map +0 -1
- package/dist/functions/prompt-configs.d.ts +0 -125
- package/dist/functions/prompt-configs.d.ts.map +0 -1
- package/dist/functions/roles.d.ts +0 -118
- package/dist/functions/roles.d.ts.map +0 -1
- package/dist/functions/signal.d.ts +0 -73
- package/dist/functions/signal.d.ts.map +0 -1
- package/dist/functions/system-cron.d.ts +0 -65
- package/dist/functions/system-cron.d.ts.map +0 -1
- package/dist/functions/system.d.ts +0 -29
- package/dist/functions/system.d.ts.map +0 -1
- package/dist/functions/tests.d.ts +0 -28
- package/dist/functions/tests.d.ts.map +0 -1
- package/dist/functions/timers.d.ts +0 -139
- package/dist/functions/timers.d.ts.map +0 -1
- package/dist/functions/triggers.d.ts +0 -203
- package/dist/functions/triggers.d.ts.map +0 -1
- package/dist/functions/types.d.ts +0 -151
- package/dist/functions/types.d.ts.map +0 -1
- package/dist/src/types/types.d.ts +0 -364
- package/dist/src/types/types.d.ts.map +0 -1
- package/index.html +0 -13
- package/netlify.toml +0 -36
- package/package-project.json +0 -71
- package/scripts/ingest-chunks.cjs +0 -202
- package/scripts/kb-chunk-parser.cjs +0 -312
- package/scripts/kb-chunk-parser.ts +0 -330
- package/scripts/load-test-app-install.ts +0 -484
- package/scripts/netlify-dev-wrapper.sh +0 -22
- package/vitest.config.ts +0 -45
- /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
- /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
- /package/{scripts → .framework/scripts}/verify-integrity.sh +0 -0
|
@@ -1,178 +0,0 @@
|
|
|
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 || supabaseUrl === 'https://your-project.supabase.co') {
|
|
40
|
-
console.error('❌ SUPABASE_URL not set. Fill in your .env file first.')
|
|
41
|
-
process.exit(1)
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const dbPassword = options.dbPassword || process.env.SUPABASE_DB_PASSWORD
|
|
45
|
-
if (!dbPassword || dbPassword === 'your-db-password') {
|
|
46
|
-
console.error('❌ SUPABASE_DB_PASSWORD not set.')
|
|
47
|
-
console.error(' Add it to .env or pass --db-password <password>')
|
|
48
|
-
console.error(' Get it from: https://supabase.com/dashboard/project/_/settings/database')
|
|
49
|
-
process.exit(1)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const projectRef = supabaseUrl.replace('https://', '').split('.')[0]
|
|
53
|
-
const connectionString = `postgresql://postgres:${dbPassword}@db.${projectRef}.supabase.co:5432/postgres`
|
|
54
|
-
|
|
55
|
-
if (!existsSync(MIGRATIONS_DIR)) {
|
|
56
|
-
console.error(`❌ Migrations directory not found: ${MIGRATIONS_DIR}`)
|
|
57
|
-
process.exit(1)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const files = readdirSync(MIGRATIONS_DIR)
|
|
61
|
-
.filter(f => f.endsWith('.sql'))
|
|
62
|
-
.sort()
|
|
63
|
-
|
|
64
|
-
if (files.length === 0) {
|
|
65
|
-
console.log(' No migration files found.')
|
|
66
|
-
return
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
console.log(` Found ${files.length} migration file(s)\n`)
|
|
70
|
-
|
|
71
|
-
if (options.dryRun) {
|
|
72
|
-
for (const file of files) {
|
|
73
|
-
console.log(` [dry-run] Would apply: ${file}`)
|
|
74
|
-
}
|
|
75
|
-
return
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Dynamically import pg to avoid loading it at module init time
|
|
79
|
-
const { default: pg } = await import('pg')
|
|
80
|
-
const client = new pg.Client({ connectionString })
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
await client.connect()
|
|
84
|
-
console.log(' ✓ Connected to database\n')
|
|
85
|
-
} catch (err: any) {
|
|
86
|
-
console.error(`❌ Could not connect to database: ${err.message}`)
|
|
87
|
-
console.error(' Check your --db-password and that the project is active.')
|
|
88
|
-
process.exit(1)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
let applied = 0
|
|
92
|
-
let failed = 0
|
|
93
|
-
|
|
94
|
-
for (const file of files) {
|
|
95
|
-
const sql = readFileSync(resolve(MIGRATIONS_DIR, file), 'utf8')
|
|
96
|
-
process.stdout.write(` Applying ${file}... `)
|
|
97
|
-
|
|
98
|
-
// Split SQL into statements, being careful to preserve dollar-quoted blocks.
|
|
99
|
-
// Dollar-quoted strings ($$ ... $$ or $tag$ ... $tag$) can contain semicolons
|
|
100
|
-
// that are NOT statement terminators.
|
|
101
|
-
function splitSqlStatements(sql: string): string[] {
|
|
102
|
-
const dollarBlocks: string[] = []
|
|
103
|
-
let blockIndex = 0
|
|
104
|
-
|
|
105
|
-
// Step 1: Protect dollar-quoted blocks with placeholders (preserves internal semicolons)
|
|
106
|
-
const protectedSql = sql.replace(/\$[A-Za-z_]*\$[\s\S]*?\$[A-Za-z_]*\$/g, (match) => {
|
|
107
|
-
const placeholder = `__DOLLAR_BLOCK_${blockIndex}__`
|
|
108
|
-
dollarBlocks[blockIndex] = match
|
|
109
|
-
blockIndex++
|
|
110
|
-
return placeholder
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
// Step 2: Strip single-line comments (prevents semicolons inside -- comments
|
|
114
|
-
// from being treated as statement terminators)
|
|
115
|
-
const noComments = protectedSql.replace(/--[^\n]*/g, '')
|
|
116
|
-
|
|
117
|
-
// Step 3: Split by semicolons, restore dollar blocks, and filter empty statements
|
|
118
|
-
return noComments
|
|
119
|
-
.split(';')
|
|
120
|
-
.map(s => {
|
|
121
|
-
let restored = s.trim()
|
|
122
|
-
// Use function form of replace — prevents JS from interpreting $$ as $
|
|
123
|
-
dollarBlocks.forEach((block, idx) => {
|
|
124
|
-
restored = restored.replace(`__DOLLAR_BLOCK_${idx}__`, () => block)
|
|
125
|
-
})
|
|
126
|
-
return restored
|
|
127
|
-
})
|
|
128
|
-
.filter(s => s.length > 0)
|
|
129
|
-
.map(s => s + ';')
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const statements = splitSqlStatements(sql)
|
|
133
|
-
|
|
134
|
-
try {
|
|
135
|
-
for (const stmt of statements) {
|
|
136
|
-
await client.query(stmt)
|
|
137
|
-
}
|
|
138
|
-
console.log('✓')
|
|
139
|
-
applied++
|
|
140
|
-
} catch (err: any) {
|
|
141
|
-
console.log('❌')
|
|
142
|
-
console.error(` ${err.message}`)
|
|
143
|
-
failed++
|
|
144
|
-
// Continue applying remaining migrations
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
await client.end()
|
|
149
|
-
|
|
150
|
-
console.log(`\n ${applied} applied, ${failed} failed`)
|
|
151
|
-
|
|
152
|
-
if (failed > 0) {
|
|
153
|
-
console.error('\n❌ Some migrations failed. Check errors above.')
|
|
154
|
-
process.exit(1)
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
console.log('\n✅ All migrations applied successfully!')
|
|
158
|
-
console.log('\n Next steps:')
|
|
159
|
-
console.log(' 1. spine-framework install-app <app-slug>')
|
|
160
|
-
console.log(' 2. npm run assemble && netlify dev')
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
export function registerMigrateCommands(program: Command) {
|
|
164
|
-
program
|
|
165
|
-
.command('migrate')
|
|
166
|
-
.description('Apply SQL migrations via direct Postgres connection')
|
|
167
|
-
.option('--db-password <password>', 'DB password override (default: reads SUPABASE_DB_PASSWORD from .env)')
|
|
168
|
-
.option('--dry-run', 'Show what would happen without making changes', false)
|
|
169
|
-
.action(async (opts) => {
|
|
170
|
-
try {
|
|
171
|
-
await migrateCommand(opts)
|
|
172
|
-
} catch (err: any) {
|
|
173
|
-
console.error('Error:', err.message)
|
|
174
|
-
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
175
|
-
process.exit(1)
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
}
|
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module cli/commands/publish
|
|
4
|
-
* @audience developer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* `spine-framework publish` — Bump version, write changelog, publish to npm, commit.
|
|
9
|
-
*
|
|
10
|
-
* Run from the spine-ia coding repo. Handles both the framework package and
|
|
11
|
-
* individual app packages (cortex, portal, etc.).
|
|
12
|
-
*
|
|
13
|
-
* **Usage:**
|
|
14
|
-
* ```bash
|
|
15
|
-
* spine-framework publish # publish framework (patch bump)
|
|
16
|
-
* spine-framework publish --app cortex # publish cortex app (patch bump)
|
|
17
|
-
* spine-framework publish --app cortex --bump minor
|
|
18
|
-
* spine-framework publish --app cortex --bump patch --notes "Fix sidebar nav"
|
|
19
|
-
* spine-framework publish --app cortex --ci --notes "Fix sidebar nav" --bump patch
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { Command } from 'commander'
|
|
24
|
-
import { existsSync, readFileSync, writeFileSync } from 'fs'
|
|
25
|
-
import { resolve, dirname } from 'path'
|
|
26
|
-
import { fileURLToPath } from 'url'
|
|
27
|
-
import { execSync } from 'child_process'
|
|
28
|
-
import * as readline from 'readline'
|
|
29
|
-
|
|
30
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
31
|
-
const __dirname = dirname(__filename)
|
|
32
|
-
const PROJECT_ROOT = process.cwd()
|
|
33
|
-
|
|
34
|
-
interface PublishOptions {
|
|
35
|
-
app?: string
|
|
36
|
-
bump: 'patch' | 'minor' | 'major'
|
|
37
|
-
notes?: string
|
|
38
|
-
ci: boolean
|
|
39
|
-
dryRun: boolean
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ─── SEMVER BUMP ──────────────────────────────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
function bumpVersion(current: string, bump: 'patch' | 'minor' | 'major'): string {
|
|
45
|
-
const [major, minor, patch] = current.replace(/^v/, '').split('.').map(Number)
|
|
46
|
-
if (bump === 'major') return `${major + 1}.0.0`
|
|
47
|
-
if (bump === 'minor') return `${major}.${minor + 1}.0`
|
|
48
|
-
return `${major}.${minor}.${patch + 1}`
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
// ─── INTERACTIVE PROMPT ───────────────────────────────────────────────────────
|
|
52
|
-
|
|
53
|
-
function promptNotes(): Promise<string> {
|
|
54
|
-
return new Promise(resolve => {
|
|
55
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
56
|
-
rl.question(' Changelog notes (comma-separated): ', answer => {
|
|
57
|
-
rl.close()
|
|
58
|
-
resolve(answer.trim())
|
|
59
|
-
})
|
|
60
|
-
})
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ─── PUBLISH LOGIC ────────────────────────────────────────────────────────────
|
|
64
|
-
|
|
65
|
-
async function publishTarget(options: PublishOptions): Promise<void> {
|
|
66
|
-
const isApp = !!options.app
|
|
67
|
-
const slug = options.app
|
|
68
|
-
|
|
69
|
-
let pkgJsonPath: string
|
|
70
|
-
let manifestPath: string | null
|
|
71
|
-
let publishDir: string
|
|
72
|
-
let displayName: string
|
|
73
|
-
|
|
74
|
-
if (isApp && slug) {
|
|
75
|
-
pkgJsonPath = resolve(PROJECT_ROOT, `custom/apps/${slug}/package.json`)
|
|
76
|
-
manifestPath = resolve(PROJECT_ROOT, `custom/apps/${slug}/manifest.json`)
|
|
77
|
-
publishDir = resolve(PROJECT_ROOT, `custom/apps/${slug}`)
|
|
78
|
-
displayName = `spine-framework-${slug}`
|
|
79
|
-
} else {
|
|
80
|
-
pkgJsonPath = resolve(PROJECT_ROOT, 'package.json')
|
|
81
|
-
manifestPath = null
|
|
82
|
-
publishDir = PROJECT_ROOT
|
|
83
|
-
displayName = 'spine-framework'
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
if (!existsSync(pkgJsonPath)) {
|
|
87
|
-
console.error(` ❌ package.json not found: ${pkgJsonPath}`)
|
|
88
|
-
process.exit(1)
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// 1. Read current version
|
|
92
|
-
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
|
|
93
|
-
const currentVersion = pkgJson.version as string
|
|
94
|
-
const newVersion = bumpVersion(currentVersion, options.bump)
|
|
95
|
-
|
|
96
|
-
console.log(`\n📦 Publishing ${displayName}`)
|
|
97
|
-
console.log(` ${currentVersion} → ${newVersion} (${options.bump} bump)\n`)
|
|
98
|
-
|
|
99
|
-
// 2. Collect changelog notes
|
|
100
|
-
let notesInput = options.notes || ''
|
|
101
|
-
if (!notesInput) {
|
|
102
|
-
if (options.ci) {
|
|
103
|
-
console.error(` ❌ --ci mode requires --notes "..."`)
|
|
104
|
-
process.exit(1)
|
|
105
|
-
}
|
|
106
|
-
notesInput = await promptNotes()
|
|
107
|
-
}
|
|
108
|
-
const notes = notesInput
|
|
109
|
-
.split(',')
|
|
110
|
-
.map(s => s.trim())
|
|
111
|
-
.filter(Boolean)
|
|
112
|
-
|
|
113
|
-
if (notes.length === 0) {
|
|
114
|
-
console.error(` ❌ At least one changelog note is required.`)
|
|
115
|
-
process.exit(1)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
if (options.dryRun) {
|
|
119
|
-
console.log(` [dry-run] Would bump ${displayName} ${currentVersion} → ${newVersion}`)
|
|
120
|
-
console.log(` [dry-run] Changelog entry:`)
|
|
121
|
-
console.log(` { "version": "${newVersion}", "notes": ${JSON.stringify(notes)} }`)
|
|
122
|
-
console.log(` [dry-run] Would run: npm publish (from ${publishDir})`)
|
|
123
|
-
console.log(` [dry-run] Would commit: chore: publish ${displayName}@${newVersion}`)
|
|
124
|
-
return
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// 3. Bump version in package.json
|
|
128
|
-
pkgJson.version = newVersion
|
|
129
|
-
writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf8')
|
|
130
|
-
console.log(` ✓ package.json version → ${newVersion}`)
|
|
131
|
-
|
|
132
|
-
// 4. Prepend changelog entry to manifest.json (apps only)
|
|
133
|
-
if (manifestPath && existsSync(manifestPath)) {
|
|
134
|
-
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
135
|
-
const changelog: Array<{ version: string; notes: string[] }> = manifest.changelog || []
|
|
136
|
-
changelog.unshift({ version: newVersion, notes })
|
|
137
|
-
manifest.version = newVersion
|
|
138
|
-
manifest.changelog = changelog
|
|
139
|
-
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8')
|
|
140
|
-
console.log(` ✓ manifest.json changelog prepended, version → ${newVersion}`)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// 5. npm publish
|
|
144
|
-
console.log(`\n🚀 Running npm publish...`)
|
|
145
|
-
try {
|
|
146
|
-
execSync('npm publish --access public', { cwd: publishDir, stdio: 'inherit' })
|
|
147
|
-
console.log(` ✓ Published ${displayName}@${newVersion}`)
|
|
148
|
-
} catch (err: any) {
|
|
149
|
-
console.error(` ❌ npm publish failed: ${err.message}`)
|
|
150
|
-
console.error(` Version files have already been updated — commit them manually.`)
|
|
151
|
-
process.exit(1)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// 6. Git commit
|
|
155
|
-
console.log(`\n📝 Committing version bump...`)
|
|
156
|
-
try {
|
|
157
|
-
const filesToStage = [pkgJsonPath]
|
|
158
|
-
if (manifestPath && existsSync(manifestPath)) filesToStage.push(manifestPath)
|
|
159
|
-
execSync(`git add ${filesToStage.map(f => `"${f}"`).join(' ')}`, { cwd: PROJECT_ROOT })
|
|
160
|
-
execSync(
|
|
161
|
-
`git commit -m "chore: publish ${displayName}@${newVersion}"`,
|
|
162
|
-
{ cwd: PROJECT_ROOT, stdio: 'inherit' },
|
|
163
|
-
)
|
|
164
|
-
console.log(` ✓ Committed`)
|
|
165
|
-
} catch (err: any) {
|
|
166
|
-
console.error(` ⚠️ Git commit failed: ${err.message}`)
|
|
167
|
-
console.error(` Published successfully — commit the version files manually.`)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
console.log(`\n✅ ${displayName}@${newVersion} published successfully!\n`)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function registerPublishCommands(program: Command) {
|
|
174
|
-
program
|
|
175
|
-
.command('publish')
|
|
176
|
-
.description('Bump version, write changelog, publish to npm, commit (run from spine-ia)')
|
|
177
|
-
.option('--app <slug>', 'App slug to publish (e.g. cortex). Omit to publish the framework itself.')
|
|
178
|
-
.option('--bump <level>', 'Version bump level: patch | minor | major', 'patch')
|
|
179
|
-
.option('--notes <text>', 'Comma-separated changelog notes (required in --ci mode)')
|
|
180
|
-
.option('--ci', 'Non-interactive mode: skip prompts, requires --notes', false)
|
|
181
|
-
.option('--dry-run', 'Show what would happen without making changes', false)
|
|
182
|
-
.action(async (opts) => {
|
|
183
|
-
try {
|
|
184
|
-
await publishTarget(opts as PublishOptions)
|
|
185
|
-
} catch (err: any) {
|
|
186
|
-
console.error('Error:', err.message)
|
|
187
|
-
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
188
|
-
process.exit(1)
|
|
189
|
-
}
|
|
190
|
-
})
|
|
191
|
-
}
|
|
@@ -1,268 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module cli/commands/status
|
|
4
|
-
* @audience installer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* `spine-framework status` — Show the current state of the Spine installation.
|
|
9
|
-
*
|
|
10
|
-
* Displays:
|
|
11
|
-
* - Database connection status
|
|
12
|
-
* - Installed apps and their seed state
|
|
13
|
-
* - Migration history
|
|
14
|
-
* - Webhook handler registrations
|
|
15
|
-
*
|
|
16
|
-
* **Usage:**
|
|
17
|
-
* ```bash
|
|
18
|
-
* spine-framework status
|
|
19
|
-
* spine-framework status --json
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { Command } from 'commander'
|
|
24
|
-
import { existsSync, readdirSync } from 'fs'
|
|
25
|
-
import { resolve, dirname } from 'path'
|
|
26
|
-
import { fileURLToPath } from 'url'
|
|
27
|
-
import { adminDb } from '../../functions/_shared/index.ts'
|
|
28
|
-
import { discoverManifests } from '../../functions/_shared/app-manifest.ts'
|
|
29
|
-
import { readEngagementData, ensureSerialData, shouldOfferClaim } from './claim.ts'
|
|
30
|
-
|
|
31
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
32
|
-
const __dirname = dirname(__filename)
|
|
33
|
-
const PROJECT_ROOT = resolve(__dirname, '../../../')
|
|
34
|
-
|
|
35
|
-
interface StatusOptions {
|
|
36
|
-
json: boolean
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
interface StatusReport {
|
|
40
|
-
database: { connected: boolean; error?: string }
|
|
41
|
-
installation: {
|
|
42
|
-
frameworkInstallSerial: string | null
|
|
43
|
-
engagementScore: number
|
|
44
|
-
claimStatus: 'anonymous' | 'claimed' | 'pending' | null
|
|
45
|
-
claimPrompt: { timing: string; message: string } | null
|
|
46
|
-
}
|
|
47
|
-
apps: Array<{
|
|
48
|
-
slug: string
|
|
49
|
-
name: string
|
|
50
|
-
hasManifest: boolean
|
|
51
|
-
hasSeed: boolean
|
|
52
|
-
installed: boolean
|
|
53
|
-
installedVersion: string | null
|
|
54
|
-
packageName: string | null
|
|
55
|
-
appInstallSerial: string | null
|
|
56
|
-
installTimestamp: string | null
|
|
57
|
-
}>
|
|
58
|
-
webhookHandlers: Array<{ name: string; functionName: string; isActive: boolean }>
|
|
59
|
-
typeCounts: { total: number; byApp: Record<string, number> }
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async function gatherStatus(): Promise<StatusReport> {
|
|
63
|
-
// Get installation/engagement data
|
|
64
|
-
let frameworkSerial: string | null = null
|
|
65
|
-
let engagementScore = 0
|
|
66
|
-
let claimStatus: 'anonymous' | 'claimed' | 'pending' | null = null
|
|
67
|
-
let claimPrompt: { timing: string; message: string } | null = null
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const serialData = ensureSerialData()
|
|
71
|
-
frameworkSerial = serialData.serial_number
|
|
72
|
-
const engagement = readEngagementData()
|
|
73
|
-
if (engagement) {
|
|
74
|
-
engagementScore = engagement.engagement_score
|
|
75
|
-
claimStatus = engagement.claim_status
|
|
76
|
-
const prompt = shouldOfferClaim()
|
|
77
|
-
if (prompt) {
|
|
78
|
-
claimPrompt = { timing: prompt.timing, message: prompt.message }
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
} catch {
|
|
82
|
-
// Silent fail - engagement tracking is optional
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const report: StatusReport = {
|
|
86
|
-
database: { connected: false },
|
|
87
|
-
installation: {
|
|
88
|
-
frameworkInstallSerial: frameworkSerial,
|
|
89
|
-
engagementScore,
|
|
90
|
-
claimStatus,
|
|
91
|
-
claimPrompt,
|
|
92
|
-
},
|
|
93
|
-
apps: [],
|
|
94
|
-
webhookHandlers: [],
|
|
95
|
-
typeCounts: { total: 0, byApp: {} },
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
// 1. Database connection
|
|
99
|
-
try {
|
|
100
|
-
const { data, error } = await adminDb
|
|
101
|
-
.from('apps')
|
|
102
|
-
.select('slug')
|
|
103
|
-
.limit(1)
|
|
104
|
-
|
|
105
|
-
if (error) {
|
|
106
|
-
report.database = { connected: false, error: error.message }
|
|
107
|
-
} else {
|
|
108
|
-
report.database = { connected: true }
|
|
109
|
-
}
|
|
110
|
-
} catch (err: any) {
|
|
111
|
-
report.database = { connected: false, error: err.message }
|
|
112
|
-
return report
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
// 2. Apps — merge filesystem + database
|
|
116
|
-
const manifests = discoverManifests()
|
|
117
|
-
const { data: dbApps } = await adminDb
|
|
118
|
-
.from('apps')
|
|
119
|
-
.select('slug, name, is_active')
|
|
120
|
-
.eq('is_active', true)
|
|
121
|
-
.order('slug')
|
|
122
|
-
|
|
123
|
-
const { data: installations } = await adminDb
|
|
124
|
-
.from('app_installations')
|
|
125
|
-
.select('app_slug, is_enabled, installed_version, package_name')
|
|
126
|
-
|
|
127
|
-
const installedSlugs = new Set(
|
|
128
|
-
(installations || []).filter(i => i.is_enabled).map(i => i.app_slug)
|
|
129
|
-
)
|
|
130
|
-
|
|
131
|
-
const installationMap = new Map(
|
|
132
|
-
(installations || []).map(i => [i.app_slug, i])
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
const allSlugs = new Set([
|
|
136
|
-
...manifests.map(m => m.slug),
|
|
137
|
-
...(dbApps || []).map(a => a.slug).filter(s => s !== 'spine-core'),
|
|
138
|
-
])
|
|
139
|
-
|
|
140
|
-
for (const slug of allSlugs) {
|
|
141
|
-
const manifest = manifests.find(m => m.slug === slug)
|
|
142
|
-
const dbApp = dbApps?.find(a => a.slug === slug)
|
|
143
|
-
const seedDir = resolve(PROJECT_ROOT, `custom/apps/${slug}/seed`)
|
|
144
|
-
|
|
145
|
-
const installation = installationMap.get(slug)
|
|
146
|
-
report.apps.push({
|
|
147
|
-
slug,
|
|
148
|
-
name: dbApp?.name || slug,
|
|
149
|
-
hasManifest: !!manifest,
|
|
150
|
-
hasSeed: existsSync(seedDir) && readdirSync(seedDir).some(f => f.endsWith('.json')),
|
|
151
|
-
installed: installedSlugs.has(slug),
|
|
152
|
-
installedVersion: installation?.installed_version || null,
|
|
153
|
-
packageName: installation?.package_name || null,
|
|
154
|
-
appInstallSerial: null, // Populated via signal system
|
|
155
|
-
installTimestamp: null, // Populated via signal system
|
|
156
|
-
})
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// 3. Webhook handlers
|
|
160
|
-
const { data: handlers } = await adminDb
|
|
161
|
-
.from('webhook_handlers')
|
|
162
|
-
.select('name, function_name, is_active')
|
|
163
|
-
.order('name')
|
|
164
|
-
|
|
165
|
-
report.webhookHandlers = (handlers || []).map(h => ({
|
|
166
|
-
name: h.name,
|
|
167
|
-
functionName: h.function_name,
|
|
168
|
-
isActive: h.is_active,
|
|
169
|
-
}))
|
|
170
|
-
|
|
171
|
-
// 4. Type counts by app
|
|
172
|
-
const { data: types } = await adminDb
|
|
173
|
-
.from('types')
|
|
174
|
-
.select('slug, app_id, apps!inner(slug)')
|
|
175
|
-
.eq('is_active', true)
|
|
176
|
-
|
|
177
|
-
report.typeCounts.total = types?.length || 0
|
|
178
|
-
for (const t of types || []) {
|
|
179
|
-
const appSlug = (t as any).apps?.slug || 'unassigned'
|
|
180
|
-
report.typeCounts.byApp[appSlug] = (report.typeCounts.byApp[appSlug] || 0) + 1
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return report
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
function printStatus(report: StatusReport): void {
|
|
187
|
-
console.log('\n🔍 Spine Framework Status\n')
|
|
188
|
-
|
|
189
|
-
// Installation / Claim Status
|
|
190
|
-
if (report.installation.frameworkInstallSerial) {
|
|
191
|
-
console.log(` 🔐 Installation: ${report.installation.frameworkInstallSerial}`)
|
|
192
|
-
if (report.installation.claimStatus === 'claimed') {
|
|
193
|
-
console.log(' ✅ Claimed: Yes')
|
|
194
|
-
} else {
|
|
195
|
-
console.log(` ⏳ Claim Status: ${report.installation.claimStatus || 'anonymous'}`)
|
|
196
|
-
console.log(` 📊 Engagement Score: ${report.installation.engagementScore}`)
|
|
197
|
-
if (report.installation.claimPrompt) {
|
|
198
|
-
console.log(` 💡 ${report.installation.claimPrompt.message}`)
|
|
199
|
-
console.log(` Run: spine-framework claim`)
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
console.log('')
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Database
|
|
206
|
-
if (report.database.connected) {
|
|
207
|
-
console.log(' 📡 Database: ✅ Connected')
|
|
208
|
-
} else {
|
|
209
|
-
console.log(` 📡 Database: ❌ ${report.database.error || 'Not connected'}`)
|
|
210
|
-
return
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
// Apps
|
|
214
|
-
console.log(`\n 📱 Apps (${report.apps.length}):`)
|
|
215
|
-
if (report.apps.length === 0) {
|
|
216
|
-
console.log(' (none)')
|
|
217
|
-
} else {
|
|
218
|
-
for (const app of report.apps) {
|
|
219
|
-
const versionStr = app.installedVersion ? `v${app.installedVersion}` : ''
|
|
220
|
-
const status = [
|
|
221
|
-
app.hasManifest ? '📋 manifest' : '',
|
|
222
|
-
app.hasSeed ? '🌱 seed' : '',
|
|
223
|
-
app.installed ? `✅ installed${versionStr ? ' ' + versionStr : ''}` : '⬜ not installed',
|
|
224
|
-
].filter(Boolean).join(' ')
|
|
225
|
-
console.log(` ${app.slug.padEnd(24)} ${status}`)
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Webhook handlers
|
|
230
|
-
console.log(`\n 🔗 Webhook Handlers (${report.webhookHandlers.length}):`)
|
|
231
|
-
if (report.webhookHandlers.length === 0) {
|
|
232
|
-
console.log(' (none)')
|
|
233
|
-
} else {
|
|
234
|
-
for (const h of report.webhookHandlers) {
|
|
235
|
-
const status = h.isActive ? '✅' : '❌'
|
|
236
|
-
console.log(` ${status} ${h.name.padEnd(24)} → ${h.functionName}`)
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Types
|
|
241
|
-
console.log(`\n 📊 Types (${report.typeCounts.total} total):`)
|
|
242
|
-
for (const [app, count] of Object.entries(report.typeCounts.byApp).sort()) {
|
|
243
|
-
console.log(` ${app.padEnd(24)} ${count} types`)
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
console.log('')
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export function registerStatusCommands(program: Command) {
|
|
250
|
-
program
|
|
251
|
-
.command('status')
|
|
252
|
-
.description('Show the current state of the Spine installation')
|
|
253
|
-
.option('--json', 'Output as JSON', false)
|
|
254
|
-
.action(async (opts: StatusOptions) => {
|
|
255
|
-
try {
|
|
256
|
-
const report = await gatherStatus()
|
|
257
|
-
if (opts.json) {
|
|
258
|
-
console.log(JSON.stringify(report, null, 2))
|
|
259
|
-
} else {
|
|
260
|
-
printStatus(report)
|
|
261
|
-
}
|
|
262
|
-
} catch (err: any) {
|
|
263
|
-
console.error('Error:', err.message)
|
|
264
|
-
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
265
|
-
process.exit(1)
|
|
266
|
-
}
|
|
267
|
-
})
|
|
268
|
-
}
|