spine-framework 0.3.17 → 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 +4 -8
- 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 -71
- 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,166 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module cli/commands/uninstall-app
|
|
4
|
-
* @audience installer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* `spine-framework uninstall-app <slug>` — Soft-uninstall an app.
|
|
9
|
-
*
|
|
10
|
-
* Deactivates the app's types, link types, and triggers in the database.
|
|
11
|
-
* Does NOT delete data (items, threads, etc.) — those remain for audit.
|
|
12
|
-
* Does NOT remove filesystem files — the app code stays in custom/apps/.
|
|
13
|
-
*
|
|
14
|
-
* To fully remove: manually delete the app directory after uninstall.
|
|
15
|
-
*
|
|
16
|
-
* **Usage:**
|
|
17
|
-
* ```bash
|
|
18
|
-
* spine-framework uninstall-app cortex
|
|
19
|
-
* spine-framework uninstall-app cortex --hard # Also deactivate items
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
import type { Command } from 'commander'
|
|
24
|
-
import { adminDb } from '../../functions/_shared/index.ts'
|
|
25
|
-
|
|
26
|
-
interface UninstallOptions {
|
|
27
|
-
hard: boolean
|
|
28
|
-
dryRun: boolean
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async function uninstallApp(slug: string, options: UninstallOptions): Promise<void> {
|
|
32
|
-
console.log(`\n🗑️ Uninstalling app '${slug}'...\n`)
|
|
33
|
-
|
|
34
|
-
// 1. Find the app
|
|
35
|
-
const { data: app, error: appErr } = await adminDb
|
|
36
|
-
.from('apps')
|
|
37
|
-
.select('id, name')
|
|
38
|
-
.eq('slug', slug)
|
|
39
|
-
.single()
|
|
40
|
-
|
|
41
|
-
if (appErr || !app) {
|
|
42
|
-
console.error(` ❌ App '${slug}' not found in database`)
|
|
43
|
-
process.exit(1)
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const appId = app.id
|
|
47
|
-
|
|
48
|
-
// 2. Deactivate types
|
|
49
|
-
if (options.dryRun) {
|
|
50
|
-
const { data: types } = await adminDb
|
|
51
|
-
.from('types').select('slug').eq('app_id', appId).eq('is_active', true)
|
|
52
|
-
console.log(` [dry-run] Would deactivate ${types?.length || 0} types`)
|
|
53
|
-
} else {
|
|
54
|
-
const { data: updated } = await adminDb
|
|
55
|
-
.from('types')
|
|
56
|
-
.update({ is_active: false })
|
|
57
|
-
.eq('app_id', appId)
|
|
58
|
-
.eq('is_active', true)
|
|
59
|
-
.select('id')
|
|
60
|
-
|
|
61
|
-
console.log(` ✓ Deactivated ${updated?.length || 0} types`)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// 3. Deactivate link types
|
|
65
|
-
if (options.dryRun) {
|
|
66
|
-
const { data: lt } = await adminDb
|
|
67
|
-
.from('link_types').select('slug').eq('app_id', appId).eq('is_active', true)
|
|
68
|
-
console.log(` [dry-run] Would deactivate ${lt?.length || 0} link types`)
|
|
69
|
-
} else {
|
|
70
|
-
const { data: updated } = await adminDb
|
|
71
|
-
.from('link_types')
|
|
72
|
-
.update({ is_active: false })
|
|
73
|
-
.eq('app_id', appId)
|
|
74
|
-
.eq('is_active', true)
|
|
75
|
-
.select('id')
|
|
76
|
-
|
|
77
|
-
console.log(` ✓ Deactivated ${updated?.length || 0} link types`)
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 4. Deactivate triggers
|
|
81
|
-
if (options.dryRun) {
|
|
82
|
-
const { data: triggers } = await adminDb
|
|
83
|
-
.from('triggers').select('name').eq('app_id', appId).eq('is_active', true)
|
|
84
|
-
console.log(` [dry-run] Would deactivate ${triggers?.length || 0} triggers`)
|
|
85
|
-
} else {
|
|
86
|
-
const { data: updated } = await adminDb
|
|
87
|
-
.from('triggers')
|
|
88
|
-
.update({ is_active: false })
|
|
89
|
-
.eq('app_id', appId)
|
|
90
|
-
.eq('is_active', true)
|
|
91
|
-
.select('id')
|
|
92
|
-
|
|
93
|
-
console.log(` ✓ Deactivated ${updated?.length || 0} triggers`)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// 5. Hard mode: deactivate items of this app's types
|
|
97
|
-
if (options.hard) {
|
|
98
|
-
if (options.dryRun) {
|
|
99
|
-
const { data: types } = await adminDb
|
|
100
|
-
.from('types').select('id').eq('app_id', appId)
|
|
101
|
-
const typeIds = types?.map(t => t.id) || []
|
|
102
|
-
if (typeIds.length > 0) {
|
|
103
|
-
const { data: items } = await adminDb
|
|
104
|
-
.from('items').select('id').in('type_id', typeIds).eq('is_active', true)
|
|
105
|
-
console.log(` [dry-run] Would deactivate ${items?.length || 0} items (--hard)`)
|
|
106
|
-
}
|
|
107
|
-
} else {
|
|
108
|
-
const { data: types } = await adminDb
|
|
109
|
-
.from('types').select('id').eq('app_id', appId)
|
|
110
|
-
const typeIds = types?.map(t => t.id) || []
|
|
111
|
-
|
|
112
|
-
if (typeIds.length > 0) {
|
|
113
|
-
const { data: updated } = await adminDb
|
|
114
|
-
.from('items')
|
|
115
|
-
.update({ is_active: false })
|
|
116
|
-
.in('type_id', typeIds)
|
|
117
|
-
.eq('is_active', true)
|
|
118
|
-
.select('id')
|
|
119
|
-
|
|
120
|
-
console.log(` ✓ Deactivated ${updated?.length || 0} items (--hard)`)
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 6. Mark installation as disabled
|
|
126
|
-
if (!options.dryRun) {
|
|
127
|
-
await adminDb
|
|
128
|
-
.from('app_installations')
|
|
129
|
-
.update({ is_enabled: false, updated_at: new Date().toISOString() })
|
|
130
|
-
.eq('app_slug', slug)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
// 7. Deactivate app record
|
|
134
|
-
if (!options.dryRun) {
|
|
135
|
-
await adminDb
|
|
136
|
-
.from('apps')
|
|
137
|
-
.update({ is_active: false })
|
|
138
|
-
.eq('id', appId)
|
|
139
|
-
|
|
140
|
-
console.log(` ✓ App '${slug}' deactivated`)
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
console.log(`\n✅ App '${app.name}' uninstalled (soft-delete)`)
|
|
144
|
-
if (!options.hard) {
|
|
145
|
-
console.log(` Items created by this app are preserved.`)
|
|
146
|
-
console.log(` Use --hard to also deactivate items.`)
|
|
147
|
-
}
|
|
148
|
-
console.log(` App files remain in custom/apps/${slug}/ — delete manually if desired.`)
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
export function registerUninstallAppCommands(program: Command) {
|
|
152
|
-
program
|
|
153
|
-
.command('uninstall-app <slug>')
|
|
154
|
-
.description('Soft-uninstall an app (deactivate types, triggers, link types)')
|
|
155
|
-
.option('--hard', 'Also deactivate items created by this app', false)
|
|
156
|
-
.option('--dry-run', 'Show what would happen without making changes', false)
|
|
157
|
-
.action(async (slug, opts) => {
|
|
158
|
-
try {
|
|
159
|
-
await uninstallApp(slug, opts)
|
|
160
|
-
} catch (err: any) {
|
|
161
|
-
console.error('Error:', err.message)
|
|
162
|
-
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
163
|
-
process.exit(1)
|
|
164
|
-
}
|
|
165
|
-
})
|
|
166
|
-
}
|
|
@@ -1,397 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module cli/commands/update-app
|
|
4
|
-
* @audience installer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* `spine-framework update-app <slug>` — Check for and assist with app updates.
|
|
9
|
-
*
|
|
10
|
-
* Design principle: the agentic IDE is the merge tool. This command never
|
|
11
|
-
* writes to custom/apps/. It only:
|
|
12
|
-
* 1. Gets the new version into node_modules/ (read-only upstream reference)
|
|
13
|
-
* 2. Gives the agent a complete picture: versions, changelog, file diff summary
|
|
14
|
-
* 3. Records the new version in DB once the agent signals it is done (--accept)
|
|
15
|
-
*
|
|
16
|
-
* **Usage:**
|
|
17
|
-
* ```bash
|
|
18
|
-
* spine-framework update-app cortex # check for updates
|
|
19
|
-
* spine-framework update-app cortex --dry-run # full plan: changelog + file diff
|
|
20
|
-
* spine-framework update-app cortex --accept # record new version in DB only
|
|
21
|
-
* ```
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
import type { Command } from 'commander'
|
|
25
|
-
import { existsSync, readFileSync, readdirSync, createReadStream } from 'fs'
|
|
26
|
-
import { resolve, dirname } from 'path'
|
|
27
|
-
import { fileURLToPath } from 'url'
|
|
28
|
-
import { execSync } from 'child_process'
|
|
29
|
-
import { adminDb } from '../../functions/_shared/index.ts'
|
|
30
|
-
|
|
31
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
32
|
-
const __dirname = dirname(__filename)
|
|
33
|
-
const PROJECT_ROOT = process.cwd()
|
|
34
|
-
|
|
35
|
-
interface UpdateOptions {
|
|
36
|
-
dryRun: boolean
|
|
37
|
-
accept: boolean
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
interface InstallationRecord {
|
|
41
|
-
app_slug: string
|
|
42
|
-
is_enabled: boolean
|
|
43
|
-
installed_version: string | null
|
|
44
|
-
package_name: string | null
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ─── NPM REGISTRY ─────────────────────────────────────────────────────────────
|
|
48
|
-
|
|
49
|
-
async function fetchLatestVersion(packageName: string): Promise<string | null> {
|
|
50
|
-
try {
|
|
51
|
-
const url = `https://registry.npmjs.org/${encodeURIComponent(packageName)}/latest`
|
|
52
|
-
const res = await fetch(url)
|
|
53
|
-
if (!res.ok) return null
|
|
54
|
-
const data = await res.json() as { version?: string }
|
|
55
|
-
return data.version || null
|
|
56
|
-
} catch {
|
|
57
|
-
return null
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// ─── SEMVER COMPARISON ────────────────────────────────────────────────────────
|
|
62
|
-
|
|
63
|
-
function parseSemver(v: string): [number, number, number] {
|
|
64
|
-
const parts = v.replace(/^v/, '').split('.').map(Number)
|
|
65
|
-
return [parts[0] || 0, parts[1] || 0, parts[2] || 0]
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function isNewer(candidate: string, current: string): boolean {
|
|
69
|
-
const [cMaj, cMin, cPatch] = parseSemver(candidate)
|
|
70
|
-
const [iMaj, iMin, iPatch] = parseSemver(current)
|
|
71
|
-
if (cMaj !== iMaj) return cMaj > iMaj
|
|
72
|
-
if (cMin !== iMin) return cMin > iMin
|
|
73
|
-
return cPatch > iPatch
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function changelogBetween(
|
|
77
|
-
changelog: Array<{ version: string; notes: string[] }>,
|
|
78
|
-
fromVersion: string,
|
|
79
|
-
toVersion: string,
|
|
80
|
-
): Array<{ version: string; notes: string[] }> {
|
|
81
|
-
return changelog.filter(entry => isNewer(entry.version, fromVersion) && !isNewer(entry.version, toVersion))
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// ─── FILE DIFF SUMMARY ────────────────────────────────────────────────────────
|
|
85
|
-
|
|
86
|
-
function readFileSafe(filePath: string): string | null {
|
|
87
|
-
try { return readFileSync(filePath, 'utf8') } catch { return null }
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
function diffAppFiles(
|
|
91
|
-
pkgDir: string,
|
|
92
|
-
localDir: string,
|
|
93
|
-
dirs: string[],
|
|
94
|
-
topFiles: string[],
|
|
95
|
-
): { changed: string[]; added: string[]; localOnly: string[] } {
|
|
96
|
-
const changed: string[] = []
|
|
97
|
-
const added: string[] = []
|
|
98
|
-
const localOnly: string[] = []
|
|
99
|
-
|
|
100
|
-
function diffFile(relPath: string) {
|
|
101
|
-
const upstreamContent = readFileSafe(resolve(pkgDir, relPath))
|
|
102
|
-
const localContent = readFileSafe(resolve(localDir, relPath))
|
|
103
|
-
if (upstreamContent === null && localContent !== null) {
|
|
104
|
-
localOnly.push(relPath)
|
|
105
|
-
} else if (upstreamContent !== null && localContent === null) {
|
|
106
|
-
added.push(relPath)
|
|
107
|
-
} else if (upstreamContent !== null && localContent !== null && upstreamContent !== localContent) {
|
|
108
|
-
changed.push(relPath)
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
for (const file of topFiles) {
|
|
113
|
-
diffFile(file)
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
for (const dir of dirs) {
|
|
117
|
-
const upstreamDirPath = resolve(pkgDir, dir)
|
|
118
|
-
if (!existsSync(upstreamDirPath)) continue
|
|
119
|
-
const walkDir = (base: string, rel: string) => {
|
|
120
|
-
const entries = readdirSync(resolve(base, rel), { withFileTypes: true })
|
|
121
|
-
for (const entry of entries) {
|
|
122
|
-
const entryRel = rel ? `${rel}/${entry.name}` : entry.name
|
|
123
|
-
if (entry.isDirectory()) {
|
|
124
|
-
walkDir(base, entryRel)
|
|
125
|
-
} else {
|
|
126
|
-
diffFile(`${dir}/${entryRel}`)
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
walkDir(upstreamDirPath, '')
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
return { changed, added, localOnly }
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// ─── SEED HELPERS (reused from install-app logic) ─────────────────────────────
|
|
137
|
-
|
|
138
|
-
async function resolveAppId(slug: string): Promise<string | null> {
|
|
139
|
-
const { data } = await adminDb.from('apps').select('id').eq('slug', slug).single()
|
|
140
|
-
return data?.id || null
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async function upsertSeedFile(
|
|
144
|
-
appId: string,
|
|
145
|
-
seedFile: string,
|
|
146
|
-
records: any[],
|
|
147
|
-
): Promise<void> {
|
|
148
|
-
const tableMap: Record<string, string> = {
|
|
149
|
-
'types.json': 'types',
|
|
150
|
-
'link-types.json': 'link_types',
|
|
151
|
-
'roles.json': 'roles',
|
|
152
|
-
'pipelines.json': 'pipelines',
|
|
153
|
-
}
|
|
154
|
-
const table = tableMap[seedFile]
|
|
155
|
-
if (!table) return
|
|
156
|
-
|
|
157
|
-
const conflictMap: Record<string, string> = {
|
|
158
|
-
types: 'app_id,kind,slug',
|
|
159
|
-
link_types: 'app_id,slug',
|
|
160
|
-
roles: 'app_id,slug',
|
|
161
|
-
pipelines: 'app_id,name',
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
for (const record of records) {
|
|
165
|
-
await adminDb.from(table).upsert(
|
|
166
|
-
{ ...record, app_id: appId },
|
|
167
|
-
{ onConflict: conflictMap[table], ignoreDuplicates: false },
|
|
168
|
-
)
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
async function reapplySeed(slug: string, pkgDir: string): Promise<void> {
|
|
173
|
-
const appId = await resolveAppId(slug)
|
|
174
|
-
if (!appId) {
|
|
175
|
-
console.log(` ⚠️ App '${slug}' not found in DB — skipping seed re-apply`)
|
|
176
|
-
return
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
const seedDir = resolve(pkgDir, 'seed')
|
|
180
|
-
if (!existsSync(seedDir)) return
|
|
181
|
-
|
|
182
|
-
const SEED_ORDER = ['accounts.json', 'types.json', 'link-types.json', 'roles.json', 'pipelines.json', 'triggers.json']
|
|
183
|
-
const allFiles = readdirSync(seedDir).filter(f => f.endsWith('.json'))
|
|
184
|
-
const seedFiles = [
|
|
185
|
-
...SEED_ORDER.filter(f => allFiles.includes(f)),
|
|
186
|
-
...allFiles.filter(f => !SEED_ORDER.includes(f)),
|
|
187
|
-
]
|
|
188
|
-
|
|
189
|
-
for (const file of seedFiles) {
|
|
190
|
-
try {
|
|
191
|
-
const records = JSON.parse(readFileSync(resolve(seedDir, file), 'utf8'))
|
|
192
|
-
if (Array.isArray(records)) {
|
|
193
|
-
await upsertSeedFile(appId, file, records)
|
|
194
|
-
console.log(` ✓ Re-seeded ${file}`)
|
|
195
|
-
}
|
|
196
|
-
} catch { /* ignore parse errors */ }
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// ─── MAIN ─────────────────────────────────────────────────────────────────────
|
|
201
|
-
|
|
202
|
-
async function updateApp(slug: string, options: UpdateOptions): Promise<void> {
|
|
203
|
-
console.log(`\n🔄 Update check for '${slug}'...\n`)
|
|
204
|
-
|
|
205
|
-
// 1. Read installation record from DB
|
|
206
|
-
const { data: installation, error: dbErr } = await adminDb
|
|
207
|
-
.from('app_installations')
|
|
208
|
-
.select('app_slug, is_enabled, installed_version, package_name')
|
|
209
|
-
.eq('app_slug', slug)
|
|
210
|
-
.maybeSingle() as { data: InstallationRecord | null; error: any }
|
|
211
|
-
|
|
212
|
-
if (dbErr) {
|
|
213
|
-
console.error(` ❌ DB error: ${dbErr.message}`)
|
|
214
|
-
console.error(` Has migration 004_app_version_tracking.sql been applied?`)
|
|
215
|
-
process.exit(1)
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
if (!installation) {
|
|
219
|
-
console.error(` ❌ App '${slug}' is not installed (not found in app_installations).`)
|
|
220
|
-
console.error(` Run: spine-framework install-app spine-framework-${slug}`)
|
|
221
|
-
process.exit(1)
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const packageName = installation.package_name || `spine-framework-${slug}`
|
|
225
|
-
const installedVersion = installation.installed_version
|
|
226
|
-
|
|
227
|
-
if (!installedVersion) {
|
|
228
|
-
console.log(` ⚠️ No installed_version recorded. Re-install to track version:`)
|
|
229
|
-
console.log(` spine-framework install-app ${packageName} --force`)
|
|
230
|
-
process.exit(1)
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// 2. Fetch latest from npm registry
|
|
234
|
-
process.stdout.write(` Checking npm registry for ${packageName}... `)
|
|
235
|
-
const latestVersion = await fetchLatestVersion(packageName)
|
|
236
|
-
if (!latestVersion) {
|
|
237
|
-
console.log(`❌`)
|
|
238
|
-
console.error(` Could not reach npm registry. Check your internet connection.`)
|
|
239
|
-
process.exit(1)
|
|
240
|
-
}
|
|
241
|
-
console.log(`done`)
|
|
242
|
-
|
|
243
|
-
console.log(` Installed : v${installedVersion}`)
|
|
244
|
-
console.log(` Latest : v${latestVersion}`)
|
|
245
|
-
|
|
246
|
-
if (!isNewer(latestVersion, installedVersion)) {
|
|
247
|
-
console.log(`\n✅ Already up to date.\n`)
|
|
248
|
-
return
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
console.log(` Status : update available\n`)
|
|
252
|
-
|
|
253
|
-
if (!options.dryRun && !options.accept) {
|
|
254
|
-
console.log(` Run with --dry-run to see changelog and file diff.`)
|
|
255
|
-
console.log(` Run with --accept after merging to record the new version.\n`)
|
|
256
|
-
return
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// ── --dry-run ──────────────────────────────────────────────────────────────
|
|
260
|
-
if (options.dryRun) {
|
|
261
|
-
console.log(`📥 Fetching ${packageName}@latest into node_modules (read-only reference)...`)
|
|
262
|
-
try {
|
|
263
|
-
execSync(`npm install ${packageName}@latest`, { cwd: PROJECT_ROOT, stdio: 'inherit' })
|
|
264
|
-
console.log(` ✓ Done\n`)
|
|
265
|
-
} catch (err: any) {
|
|
266
|
-
console.error(` ❌ npm install failed: ${err.message}`)
|
|
267
|
-
process.exit(1)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
const pkgDir = resolve(PROJECT_ROOT, `node_modules/${packageName}`)
|
|
271
|
-
const localDir = resolve(PROJECT_ROOT, `custom/apps/${slug}`)
|
|
272
|
-
|
|
273
|
-
// Read changelog from new upstream version
|
|
274
|
-
const upstreamManifestPath = resolve(pkgDir, 'manifest.json')
|
|
275
|
-
let changelog: Array<{ version: string; notes: string[] }> = []
|
|
276
|
-
let newVersion = latestVersion
|
|
277
|
-
if (existsSync(upstreamManifestPath)) {
|
|
278
|
-
try {
|
|
279
|
-
const m = JSON.parse(readFileSync(upstreamManifestPath, 'utf8'))
|
|
280
|
-
changelog = m.changelog || []
|
|
281
|
-
newVersion = m.version || latestVersion
|
|
282
|
-
} catch { /* ignore */ }
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// Print changelog entries between installed and latest
|
|
286
|
-
const relevantChangelog = changelogBetween(changelog, installedVersion, newVersion)
|
|
287
|
-
console.log(`📋 Changelog (v${installedVersion} → v${newVersion}):`)
|
|
288
|
-
if (relevantChangelog.length === 0) {
|
|
289
|
-
console.log(` (no changelog entries found between these versions)`)
|
|
290
|
-
} else {
|
|
291
|
-
for (const entry of relevantChangelog) {
|
|
292
|
-
console.log(`\n v${entry.version}:`)
|
|
293
|
-
for (const note of entry.notes) {
|
|
294
|
-
console.log(` • ${note}`)
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
// Read directories list from upstream manifest
|
|
300
|
-
let appDirs = ['pages', 'components', 'hooks', 'config', 'seed', 'api', 'functions', 'lib']
|
|
301
|
-
try {
|
|
302
|
-
const m = JSON.parse(readFileSync(upstreamManifestPath, 'utf8'))
|
|
303
|
-
if (Array.isArray(m.directories)) appDirs = m.directories.filter((d: string) => d !== 'migrations' && d !== 'tests')
|
|
304
|
-
} catch { /* use defaults */ }
|
|
305
|
-
|
|
306
|
-
const topFiles = ['index.tsx', 'manifest.json']
|
|
307
|
-
const diff = diffAppFiles(pkgDir, localDir, appDirs, topFiles)
|
|
308
|
-
|
|
309
|
-
console.log(`\n📁 File diff summary (upstream vs your version):`)
|
|
310
|
-
console.log(` Upstream (new) : node_modules/${packageName}/`)
|
|
311
|
-
console.log(` Your version : custom/apps/${slug}/\n`)
|
|
312
|
-
|
|
313
|
-
if (diff.changed.length === 0 && diff.added.length === 0 && diff.localOnly.length === 0) {
|
|
314
|
-
console.log(` (no file differences detected)`)
|
|
315
|
-
} else {
|
|
316
|
-
if (diff.changed.length > 0) {
|
|
317
|
-
console.log(` Modified (${diff.changed.length}) — review and merge:`)
|
|
318
|
-
diff.changed.forEach(f => console.log(` ~ ${f}`))
|
|
319
|
-
}
|
|
320
|
-
if (diff.added.length > 0) {
|
|
321
|
-
console.log(`\n New in upstream (${diff.added.length}) — copy to your version:`)
|
|
322
|
-
diff.added.forEach(f => console.log(` + ${f}`))
|
|
323
|
-
}
|
|
324
|
-
if (diff.localOnly.length > 0) {
|
|
325
|
-
console.log(`\n Only in your version (${diff.localOnly.length}) — your additions, keep them:`)
|
|
326
|
-
diff.localOnly.forEach(f => console.log(` → ${f}`))
|
|
327
|
-
}
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
console.log(`\n──────────────────────────────────────────────────────────────`)
|
|
331
|
-
console.log(` Next steps:`)
|
|
332
|
-
console.log(` 1. Review and merge changed files listed above`)
|
|
333
|
-
console.log(` 2. Run: spine-framework update-app ${slug} --accept`)
|
|
334
|
-
console.log(`──────────────────────────────────────────────────────────────\n`)
|
|
335
|
-
return
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// ── --accept ──────────────────────────────────────────────────────────────
|
|
339
|
-
if (options.accept) {
|
|
340
|
-
console.log(`📝 Recording update...`)
|
|
341
|
-
|
|
342
|
-
const pkgDir = resolve(PROJECT_ROOT, `node_modules/${packageName}`)
|
|
343
|
-
if (!existsSync(pkgDir)) {
|
|
344
|
-
console.error(` ❌ ${packageName} not found in node_modules.`)
|
|
345
|
-
console.error(` Run --dry-run first to fetch the new version.`)
|
|
346
|
-
process.exit(1)
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
// Read new version from upstream manifest
|
|
350
|
-
let newVersion = latestVersion
|
|
351
|
-
const upstreamManifestPath = resolve(pkgDir, 'manifest.json')
|
|
352
|
-
if (existsSync(upstreamManifestPath)) {
|
|
353
|
-
try {
|
|
354
|
-
newVersion = JSON.parse(readFileSync(upstreamManifestPath, 'utf8')).version || latestVersion
|
|
355
|
-
} catch { /* use latestVersion */ }
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
const { error: updateErr } = await adminDb
|
|
359
|
-
.from('app_installations')
|
|
360
|
-
.update({
|
|
361
|
-
installed_version: newVersion,
|
|
362
|
-
updated_at: new Date().toISOString(),
|
|
363
|
-
})
|
|
364
|
-
.eq('app_slug', slug)
|
|
365
|
-
|
|
366
|
-
if (updateErr) {
|
|
367
|
-
console.error(` ❌ Failed to update installed_version: ${updateErr.message}`)
|
|
368
|
-
process.exit(1)
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
console.log(` ✓ installed_version updated to v${newVersion}`)
|
|
372
|
-
|
|
373
|
-
// Re-apply seed (idempotent)
|
|
374
|
-
console.log(`\n🌱 Re-applying seed data (idempotent)...`)
|
|
375
|
-
await reapplySeed(slug, pkgDir)
|
|
376
|
-
|
|
377
|
-
console.log(`\n✅ Update recorded for '${slug}' → v${newVersion}`)
|
|
378
|
-
console.log(` Run 'spine-framework status' to verify.\n`)
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
export function registerUpdateAppCommands(program: Command) {
|
|
383
|
-
program
|
|
384
|
-
.command('update-app <slug>')
|
|
385
|
-
.description('Check for app updates and assist with agentic-IDE-driven merge')
|
|
386
|
-
.option('--dry-run', 'Fetch latest, show changelog + file diff (no writes to custom/apps/)', false)
|
|
387
|
-
.option('--accept', 'Record new version in DB after agent has merged files', false)
|
|
388
|
-
.action(async (slug, opts) => {
|
|
389
|
-
try {
|
|
390
|
-
await updateApp(slug, opts)
|
|
391
|
-
} catch (err: any) {
|
|
392
|
-
console.error('Error:', err.message)
|
|
393
|
-
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
394
|
-
process.exit(1)
|
|
395
|
-
}
|
|
396
|
-
})
|
|
397
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// Postinstall welcome message — runs automatically after npm install spine-framework.
|
|
3
|
-
// Tells the agent (or developer) exactly what to do next.
|
|
4
|
-
// Uses only Node built-ins — no dependencies.
|
|
5
|
-
|
|
6
|
-
const lines = [
|
|
7
|
-
'',
|
|
8
|
-
'╭──────────────────────────────────────────────────────────────╮',
|
|
9
|
-
'│ │',
|
|
10
|
-
'│ ███████╗██████╗ ██╗███╗ ██╗███████╗ │',
|
|
11
|
-
'│ ██╔════╝██╔══██╗██║████╗ ██║██╔════╝ │',
|
|
12
|
-
'│ ███████╗██████╔╝██║██╔██╗ ██║█████╗ │',
|
|
13
|
-
'│ ╚════██║██╔═══╝ ██║██║╚██╗██║██╔══╝ │',
|
|
14
|
-
'│ ███████║██║ ██║██║ ╚████║███████╗ │',
|
|
15
|
-
'│ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═══╝╚══════╝ Framework v0.1 │',
|
|
16
|
-
'│ │',
|
|
17
|
-
'╰──────────────────────────────────────────────────────────────╯',
|
|
18
|
-
'',
|
|
19
|
-
' Spine Framework installed successfully!',
|
|
20
|
-
'',
|
|
21
|
-
' To complete setup you need a Supabase project.',
|
|
22
|
-
' ─────────────────────────────────────────────',
|
|
23
|
-
' If you have one already, run:',
|
|
24
|
-
'',
|
|
25
|
-
' spine-framework init \\',
|
|
26
|
-
' --url https://<project>.supabase.co \\',
|
|
27
|
-
' --anon-key <anon-key> \\',
|
|
28
|
-
' --service-role-key <service-role-key>',
|
|
29
|
-
'',
|
|
30
|
-
' Your credentials are at:',
|
|
31
|
-
' Supabase Dashboard → Your Project → Settings → API',
|
|
32
|
-
'',
|
|
33
|
-
' ─────────────────────────────────────────────',
|
|
34
|
-
' If you need a Supabase project, create one at:',
|
|
35
|
-
' https://supabase.com/dashboard → New project',
|
|
36
|
-
'',
|
|
37
|
-
' After init:',
|
|
38
|
-
' spine-framework install-app <app-slug> # optional apps',
|
|
39
|
-
' npm run assemble && netlify dev # start dev server',
|
|
40
|
-
'',
|
|
41
|
-
' Docs: https://github.com/art-mojo-admin/spine',
|
|
42
|
-
'',
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
console.log(lines.join('\n'))
|