spine-framework 0.1.61 → 0.2.0
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 -63
- package/.framework/functions/_shared/app-manifest.ts +8 -15
- package/.framework/functions/_shared/db.ts +11 -15
- package/.framework/functions/_shared/index.ts +0 -9
- package/.framework/functions/_shared/principal.ts +4 -62
- package/.framework/functions/_shared/resolve-ids.ts +6 -6
- package/.framework/functions/_shared/webhook-registry.ts +1 -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/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 +1 -1
- package/{scripts → .framework/scripts}/assemble-functions.sh +1 -1
- package/{scripts → .framework/scripts}/assemble.sh +2 -8
- 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 +6 -4
- 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 +193 -0
- package/README.md +254 -0
- 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 -144
- package/.framework/cli/bin.cjs +0 -14
- package/.framework/cli/commands/init.ts +0 -186
- package/.framework/cli/commands/install-app.ts +0 -565
- package/.framework/cli/commands/migrate.ts +0 -139
- package/.framework/cli/commands/status.ts +0 -197
- package/.framework/cli/commands/uninstall-app.ts +0 -166
- package/.framework/cli/welcome.cjs +0 -45
- 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/migrations/000_foundation.sql +0 -1266
- 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/src/pages/auth/RegisterPage.tsx +0 -163
- package/STRUCTURE.md +0 -150
- package/bin/spine-framework.cjs +0 -62
- package/bin/welcome.cjs +0 -45
- package/bin/ws-shim.cjs +0 -8
- 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 -72
- 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/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/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/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 -41
- package/dist/cli/index.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 -57
- 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/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/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 -64
- 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}/boundary-check.sh +0 -0
- /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
- /package/{scripts → .framework/scripts}/verify-integrity.sh +0 -0
|
@@ -1,197 +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
|
-
|
|
30
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
31
|
-
const __dirname = dirname(__filename)
|
|
32
|
-
const PROJECT_ROOT = resolve(__dirname, '../../../')
|
|
33
|
-
|
|
34
|
-
interface StatusOptions {
|
|
35
|
-
json: boolean
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
interface StatusReport {
|
|
39
|
-
database: { connected: boolean; error?: string }
|
|
40
|
-
apps: Array<{ slug: string; name: string; hasManifest: boolean; hasSeed: boolean; installed: boolean }>
|
|
41
|
-
webhookHandlers: Array<{ name: string; functionName: string; isActive: boolean }>
|
|
42
|
-
typeCounts: { total: number; byApp: Record<string, number> }
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async function gatherStatus(): Promise<StatusReport> {
|
|
46
|
-
const report: StatusReport = {
|
|
47
|
-
database: { connected: false },
|
|
48
|
-
apps: [],
|
|
49
|
-
webhookHandlers: [],
|
|
50
|
-
typeCounts: { total: 0, byApp: {} },
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// 1. Database connection
|
|
54
|
-
try {
|
|
55
|
-
const { data, error } = await adminDb
|
|
56
|
-
.from('apps')
|
|
57
|
-
.select('slug')
|
|
58
|
-
.limit(1)
|
|
59
|
-
|
|
60
|
-
if (error) {
|
|
61
|
-
report.database = { connected: false, error: error.message }
|
|
62
|
-
} else {
|
|
63
|
-
report.database = { connected: true }
|
|
64
|
-
}
|
|
65
|
-
} catch (err: any) {
|
|
66
|
-
report.database = { connected: false, error: err.message }
|
|
67
|
-
return report
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 2. Apps — merge filesystem + database
|
|
71
|
-
const manifests = discoverManifests()
|
|
72
|
-
const { data: dbApps } = await adminDb
|
|
73
|
-
.from('apps')
|
|
74
|
-
.select('slug, name, is_active')
|
|
75
|
-
.eq('is_active', true)
|
|
76
|
-
.order('slug')
|
|
77
|
-
|
|
78
|
-
const { data: installations } = await adminDb
|
|
79
|
-
.from('app_installations')
|
|
80
|
-
.select('app_slug, is_enabled')
|
|
81
|
-
|
|
82
|
-
const installedSlugs = new Set(
|
|
83
|
-
(installations || []).filter(i => i.is_enabled).map(i => i.app_slug)
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
const allSlugs = new Set([
|
|
87
|
-
...manifests.map(m => m.slug),
|
|
88
|
-
...(dbApps || []).map(a => a.slug).filter(s => s !== 'spine-core'),
|
|
89
|
-
])
|
|
90
|
-
|
|
91
|
-
for (const slug of allSlugs) {
|
|
92
|
-
const manifest = manifests.find(m => m.slug === slug)
|
|
93
|
-
const dbApp = dbApps?.find(a => a.slug === slug)
|
|
94
|
-
const seedDir = resolve(PROJECT_ROOT, `custom/apps/${slug}/seed`)
|
|
95
|
-
|
|
96
|
-
report.apps.push({
|
|
97
|
-
slug,
|
|
98
|
-
name: dbApp?.name || slug,
|
|
99
|
-
hasManifest: !!manifest,
|
|
100
|
-
hasSeed: existsSync(seedDir) && readdirSync(seedDir).some(f => f.endsWith('.json')),
|
|
101
|
-
installed: installedSlugs.has(slug),
|
|
102
|
-
})
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// 3. Webhook handlers
|
|
106
|
-
const { data: handlers } = await adminDb
|
|
107
|
-
.from('webhook_handlers')
|
|
108
|
-
.select('name, function_name, is_active')
|
|
109
|
-
.order('name')
|
|
110
|
-
|
|
111
|
-
report.webhookHandlers = (handlers || []).map(h => ({
|
|
112
|
-
name: h.name,
|
|
113
|
-
functionName: h.function_name,
|
|
114
|
-
isActive: h.is_active,
|
|
115
|
-
}))
|
|
116
|
-
|
|
117
|
-
// 4. Type counts by app
|
|
118
|
-
const { data: types } = await adminDb
|
|
119
|
-
.from('types')
|
|
120
|
-
.select('slug, app_id, apps!inner(slug)')
|
|
121
|
-
.eq('is_active', true)
|
|
122
|
-
|
|
123
|
-
report.typeCounts.total = types?.length || 0
|
|
124
|
-
for (const t of types || []) {
|
|
125
|
-
const appSlug = (t as any).apps?.slug || 'unassigned'
|
|
126
|
-
report.typeCounts.byApp[appSlug] = (report.typeCounts.byApp[appSlug] || 0) + 1
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return report
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
function printStatus(report: StatusReport): void {
|
|
133
|
-
console.log('\n🔍 Spine Framework Status\n')
|
|
134
|
-
|
|
135
|
-
// Database
|
|
136
|
-
if (report.database.connected) {
|
|
137
|
-
console.log(' 📡 Database: ✅ Connected')
|
|
138
|
-
} else {
|
|
139
|
-
console.log(` 📡 Database: ❌ ${report.database.error || 'Not connected'}`)
|
|
140
|
-
return
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// Apps
|
|
144
|
-
console.log(`\n 📱 Apps (${report.apps.length}):`)
|
|
145
|
-
if (report.apps.length === 0) {
|
|
146
|
-
console.log(' (none)')
|
|
147
|
-
} else {
|
|
148
|
-
for (const app of report.apps) {
|
|
149
|
-
const status = [
|
|
150
|
-
app.hasManifest ? '📋 manifest' : '',
|
|
151
|
-
app.hasSeed ? '🌱 seed' : '',
|
|
152
|
-
app.installed ? '✅ installed' : '⬜ not installed',
|
|
153
|
-
].filter(Boolean).join(' ')
|
|
154
|
-
console.log(` ${app.slug.padEnd(24)} ${status}`)
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
// Webhook handlers
|
|
159
|
-
console.log(`\n 🔗 Webhook Handlers (${report.webhookHandlers.length}):`)
|
|
160
|
-
if (report.webhookHandlers.length === 0) {
|
|
161
|
-
console.log(' (none)')
|
|
162
|
-
} else {
|
|
163
|
-
for (const h of report.webhookHandlers) {
|
|
164
|
-
const status = h.isActive ? '✅' : '❌'
|
|
165
|
-
console.log(` ${status} ${h.name.padEnd(24)} → ${h.functionName}`)
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
// Types
|
|
170
|
-
console.log(`\n 📊 Types (${report.typeCounts.total} total):`)
|
|
171
|
-
for (const [app, count] of Object.entries(report.typeCounts.byApp).sort()) {
|
|
172
|
-
console.log(` ${app.padEnd(24)} ${count} types`)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
console.log('')
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
export function registerStatusCommands(program: Command) {
|
|
179
|
-
program
|
|
180
|
-
.command('status')
|
|
181
|
-
.description('Show the current state of the Spine installation')
|
|
182
|
-
.option('--json', 'Output as JSON', false)
|
|
183
|
-
.action(async (opts: StatusOptions) => {
|
|
184
|
-
try {
|
|
185
|
-
const report = await gatherStatus()
|
|
186
|
-
if (opts.json) {
|
|
187
|
-
console.log(JSON.stringify(report, null, 2))
|
|
188
|
-
} else {
|
|
189
|
-
printStatus(report)
|
|
190
|
-
}
|
|
191
|
-
} catch (err: any) {
|
|
192
|
-
console.error('Error:', err.message)
|
|
193
|
-
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
194
|
-
process.exit(1)
|
|
195
|
-
}
|
|
196
|
-
})
|
|
197
|
-
}
|
|
@@ -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,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'))
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
# Stability Policy
|
|
2
|
-
|
|
3
|
-
> Version target: **0.x** — breaking changes are allowed until 1.0.
|
|
4
|
-
|
|
5
|
-
## Stability Levels
|
|
6
|
-
|
|
7
|
-
Every exported symbol in `_shared/index.ts` is annotated with one of these levels:
|
|
8
|
-
|
|
9
|
-
| Level | Meaning | Breaking change policy |
|
|
10
|
-
|-------|---------|----------------------|
|
|
11
|
-
| **stable** | Safe for custom code to depend on | Minimum 1 minor version deprecation notice before removal |
|
|
12
|
-
| **evolving** | API may change between minor versions | No deprecation period required, but changelog entry mandatory |
|
|
13
|
-
| **internal** | Not exported; core-contributor only | May change without notice |
|
|
14
|
-
|
|
15
|
-
## Versioning
|
|
16
|
-
|
|
17
|
-
Spine follows [semver](https://semver.org/) with these conventions:
|
|
18
|
-
|
|
19
|
-
- **0.x.y** — Pre-stable. Breaking changes increment the minor version.
|
|
20
|
-
- **1.0.0+** — Stable. Breaking changes increment the major version.
|
|
21
|
-
|
|
22
|
-
## Deprecation Process
|
|
23
|
-
|
|
24
|
-
1. Mark the symbol with `@deprecated` JSDoc tag and a migration hint
|
|
25
|
-
2. Add a console warning on first use (once per process)
|
|
26
|
-
3. Remove after the next minor version bump
|
|
27
|
-
|
|
28
|
-
## What Counts as Breaking
|
|
29
|
-
|
|
30
|
-
- Removing or renaming an exported function/type from `_shared/index.ts`
|
|
31
|
-
- Changing function signatures (required params added, return type changed)
|
|
32
|
-
- Removing a CLI command or changing its required arguments
|
|
33
|
-
- Changing database table/column names in migration files
|
|
34
|
-
- Removing seed data keys from `seed/*.json` schemas
|
|
35
|
-
|
|
36
|
-
## What Does NOT Count as Breaking
|
|
37
|
-
|
|
38
|
-
- Adding new optional parameters to existing functions
|
|
39
|
-
- Adding new exports to `_shared/index.ts`
|
|
40
|
-
- Adding new CLI commands
|
|
41
|
-
- Adding new database tables or columns
|
|
42
|
-
- Performance improvements to existing functions
|
|
43
|
-
- Bug fixes (even if they change observable behavior that was clearly wrong)
|
|
44
|
-
|
|
45
|
-
## Release Checklist
|
|
46
|
-
|
|
47
|
-
1. `npm run test:boundary` — architectural boundaries intact
|
|
48
|
-
2. `npm run test:unit` — unit tests pass
|
|
49
|
-
3. Update `CHANGELOG.md` with breaking changes highlighted
|
|
50
|
-
4. Bump version in `package.json`
|
|
51
|
-
5. `npm run prepublishOnly` — build + test gate
|
|
52
|
-
6. `npm publish`
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
# Example: Custom App
|
|
2
|
-
|
|
3
|
-
A minimal React app with manifest and routing, installable via `spine-framework install-app`.
|
|
4
|
-
|
|
5
|
-
## Directory Structure
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
custom/apps/my-app/
|
|
9
|
-
├── manifest.json # App metadata, routes, nav items
|
|
10
|
-
├── index.tsx # React entry point (default export)
|
|
11
|
-
├── seed/
|
|
12
|
-
│ ├── types.json # Item types this app provides
|
|
13
|
-
│ └── triggers.json # Automation triggers
|
|
14
|
-
├── components/
|
|
15
|
-
│ └── Dashboard.tsx # App-specific components
|
|
16
|
-
└── package.json # npm package metadata (optional)
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## manifest.json
|
|
20
|
-
|
|
21
|
-
```json
|
|
22
|
-
{
|
|
23
|
-
"name": "My App",
|
|
24
|
-
"slug": "my-app",
|
|
25
|
-
"version": "0.1.0",
|
|
26
|
-
"description": "A custom app for demonstration",
|
|
27
|
-
"routes": [
|
|
28
|
-
{ "path": "/", "redirect": "/dashboard" },
|
|
29
|
-
{ "path": "/dashboard", "component": "Dashboard" }
|
|
30
|
-
],
|
|
31
|
-
"nav_items": [
|
|
32
|
-
{ "label": "Dashboard", "path": "/dashboard", "icon": "LayoutDashboard" }
|
|
33
|
-
],
|
|
34
|
-
"required_roles": ["member"]
|
|
35
|
-
}
|
|
36
|
-
```
|
|
37
|
-
|
|
38
|
-
## index.tsx
|
|
39
|
-
|
|
40
|
-
```tsx
|
|
41
|
-
import * as React from 'react'
|
|
42
|
-
import { Routes, Route, Navigate } from 'react-router-dom'
|
|
43
|
-
|
|
44
|
-
function Dashboard() {
|
|
45
|
-
return (
|
|
46
|
-
<div className="p-6">
|
|
47
|
-
<h1 className="text-2xl font-bold">My App</h1>
|
|
48
|
-
</div>
|
|
49
|
-
)
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export default function MyApp() {
|
|
53
|
-
return (
|
|
54
|
-
<Routes>
|
|
55
|
-
<Route path="/" element={<Navigate to="dashboard" replace />} />
|
|
56
|
-
<Route path="dashboard" element={<Dashboard />} />
|
|
57
|
-
</Routes>
|
|
58
|
-
)
|
|
59
|
-
}
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
## Installation
|
|
63
|
-
|
|
64
|
-
```bash
|
|
65
|
-
# Create the app scaffold
|
|
66
|
-
spine-framework create-app my-app
|
|
67
|
-
|
|
68
|
-
# Or install an existing app package
|
|
69
|
-
spine-framework install-app my-app
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
## How It Works
|
|
73
|
-
|
|
74
|
-
1. `CustomAppLoader` discovers apps via `import.meta.glob('custom/apps/*/index.tsx')`
|
|
75
|
-
2. The app's `manifest.json` provides routing and navigation metadata
|
|
76
|
-
3. Seed data is applied via `spine-framework install-app` (idempotent upserts)
|
|
77
|
-
4. The app is isolated — it cannot import from other apps or core internals
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
# Example: Custom Function
|
|
2
|
-
|
|
3
|
-
A minimal Netlify Function using Spine's core context system.
|
|
4
|
-
|
|
5
|
-
## Setup
|
|
6
|
-
|
|
7
|
-
1. Create `custom/functions/custom_my-handler.ts`
|
|
8
|
-
2. Import `createHandler` and `CoreContext` from `spine-framework/_shared`
|
|
9
|
-
3. Your handler receives a fully-resolved `CoreContext` with:
|
|
10
|
-
- `ctx.principal` — authenticated user/machine identity
|
|
11
|
-
- `ctx.db` — Supabase client (scoped to principal's permissions)
|
|
12
|
-
- `ctx.accountId` — resolved account UUID
|
|
13
|
-
- `ctx.requestId` — unique request trace ID
|
|
14
|
-
|
|
15
|
-
## Key Points
|
|
16
|
-
|
|
17
|
-
- **Naming convention:** Custom functions MUST be prefixed with `custom_`
|
|
18
|
-
- **No static imports from core:** Use the `spine-framework` package exports
|
|
19
|
-
- **Authentication is automatic:** `createHandler` validates the JWT/API key before your code runs
|
|
20
|
-
- **Database access:** Use `ctx.db` for RLS-scoped queries, or import `adminDb` for service-role operations
|
|
21
|
-
|
|
22
|
-
## API
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
GET/POST /.netlify/functions/custom_my-handler?action=list
|
|
26
|
-
Authorization: Bearer <jwt>
|
|
27
|
-
```
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example: Custom Netlify Function using Spine core context
|
|
3
|
-
*
|
|
4
|
-
* This demonstrates the minimal pattern for creating a custom function
|
|
5
|
-
* that leverages Spine's authentication, database, and principal system.
|
|
6
|
-
*
|
|
7
|
-
* File: custom/functions/custom_my-handler.ts
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { createHandler, adminDb } from 'spine-framework/_shared'
|
|
11
|
-
import type { CoreContext } from 'spine-framework/_shared'
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* A simple handler that lists items for the current account.
|
|
15
|
-
*/
|
|
16
|
-
async function listAccountItems(ctx: CoreContext) {
|
|
17
|
-
const { data, error } = await ctx.db
|
|
18
|
-
.from('items')
|
|
19
|
-
.select('id, title, type_id, created_at')
|
|
20
|
-
.eq('account_id', ctx.accountId)
|
|
21
|
-
.eq('is_active', true)
|
|
22
|
-
.order('created_at', { ascending: false })
|
|
23
|
-
.limit(50)
|
|
24
|
-
|
|
25
|
-
if (error) {
|
|
26
|
-
return { statusCode: 500, body: JSON.stringify({ error: error.message }) }
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return {
|
|
30
|
-
statusCode: 200,
|
|
31
|
-
body: JSON.stringify({ items: data }),
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Netlify Function entry point.
|
|
37
|
-
* `createHandler` resolves the principal, validates auth, and provides CoreContext.
|
|
38
|
-
*/
|
|
39
|
-
export const handler = createHandler(async (ctx) => {
|
|
40
|
-
const { action } = ctx.params
|
|
41
|
-
|
|
42
|
-
switch (action) {
|
|
43
|
-
case 'list':
|
|
44
|
-
return listAccountItems(ctx)
|
|
45
|
-
default:
|
|
46
|
-
return { statusCode: 400, body: JSON.stringify({ error: 'Unknown action' }) }
|
|
47
|
-
}
|
|
48
|
-
})
|
|
@@ -1,68 +0,0 @@
|
|
|
1
|
-
# Example: Custom Webhook Handler
|
|
2
|
-
|
|
3
|
-
Demonstrates the self-registration pattern for webhook handlers.
|
|
4
|
-
|
|
5
|
-
## How It Works
|
|
6
|
-
|
|
7
|
-
1. Create a handler function in `custom/functions/custom_my-webhook.ts`
|
|
8
|
-
2. Register it by calling `registerWebhookHandler()` in `custom/functions/custom_webhook-handlers.ts`
|
|
9
|
-
3. Core resolves handlers at runtime via the `webhook_handlers` DB table — no static imports needed
|
|
10
|
-
|
|
11
|
-
## Handler Function
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
// custom/functions/custom_my-webhook.ts
|
|
15
|
-
|
|
16
|
-
import { createHandler } from 'spine-framework/_shared'
|
|
17
|
-
import type { CoreContext } from 'spine-framework/_shared'
|
|
18
|
-
|
|
19
|
-
export const handler = createHandler(async (ctx: CoreContext) => {
|
|
20
|
-
const payload = ctx.body
|
|
21
|
-
|
|
22
|
-
// Process the webhook payload
|
|
23
|
-
console.log('Received webhook:', payload.event_type)
|
|
24
|
-
|
|
25
|
-
// Do something with the data
|
|
26
|
-
const { error } = await ctx.db
|
|
27
|
-
.from('items')
|
|
28
|
-
.insert({
|
|
29
|
-
title: `Webhook: ${payload.event_type}`,
|
|
30
|
-
type_id: await resolveTypeId('webhook_event'),
|
|
31
|
-
account_id: ctx.accountId,
|
|
32
|
-
data: payload,
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
if (error) {
|
|
36
|
-
return { statusCode: 500, body: JSON.stringify({ error: error.message }) }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
return { statusCode: 200, body: JSON.stringify({ ok: true }) }
|
|
40
|
-
})
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
## Registration
|
|
44
|
-
|
|
45
|
-
```ts
|
|
46
|
-
// In custom/functions/custom_webhook-handlers.ts
|
|
47
|
-
|
|
48
|
-
import { registerWebhookHandler } from 'spine-framework/_shared/webhook-registration'
|
|
49
|
-
|
|
50
|
-
registerWebhookHandler({
|
|
51
|
-
name: 'my-webhook',
|
|
52
|
-
functionName: 'custom_my-webhook',
|
|
53
|
-
description: 'Handles incoming webhooks from MyService',
|
|
54
|
-
events: ['integration.webhook'],
|
|
55
|
-
}).catch(console.error)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
## Integration Config
|
|
59
|
-
|
|
60
|
-
When configuring an integration in the admin panel, set:
|
|
61
|
-
- `handler.path` = `my-webhook` (matches the `name` in registration)
|
|
62
|
-
|
|
63
|
-
## Key Points
|
|
64
|
-
|
|
65
|
-
- **Self-registration is idempotent** — safe to call on every module load
|
|
66
|
-
- **No core changes needed** — just add your handler and register it
|
|
67
|
-
- **Runtime resolution** — core looks up handlers from `webhook_handlers` table via `resolveHandler()`
|
|
68
|
-
- **Events array** — currently `['integration.webhook']` is the standard event type
|