spine-framework 0.3.17 → 0.3.19

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.
Files changed (270) hide show
  1. package/.framework/cli/bin.mjs +16 -0
  2. package/.framework/cli/commands/create-app.ts +1 -1
  3. package/.framework/cli/commands/dev.ts +215 -33
  4. package/.framework/cli/index.ts +37 -75
  5. package/.framework/functions/_shared/app-manifest.ts +8 -15
  6. package/.framework/functions/_shared/db.ts +11 -15
  7. package/.framework/functions/_shared/index.ts +7 -16
  8. package/.framework/functions/_shared/principal.ts +4 -62
  9. package/.framework/functions/_shared/resolve-ids.ts +6 -6
  10. package/.framework/functions/_shared/webhook-registry.ts +43 -1
  11. package/.framework/functions/admin-funnel.ts +104 -0
  12. package/.framework/functions/admin-install-funnel.ts +225 -0
  13. package/.framework/functions/admin-signals.ts +241 -0
  14. package/.framework/functions/integration-routes.ts +4 -8
  15. package/.framework/migrations/001_schema.sql +406 -0
  16. package/.framework/migrations/002_rls_and_functions.sql +710 -0
  17. package/.framework/migrations/003_system_data.sql +520 -0
  18. package/.framework/migrations/004_enhancements.sql +43 -0
  19. package/.framework/public/_redirects +3 -0
  20. package/.framework/public/spine-logo.jpg +0 -0
  21. package/.framework/public/spine-logo.svg +3 -0
  22. package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
  23. package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
  24. package/{scripts → .framework/scripts}/assemble.sh +2 -8
  25. package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
  26. package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
  27. package/.framework/seeds/link_types/account_cli_instances.json +9 -0
  28. package/.framework/seeds/types/funnel_signal_use.json +98 -0
  29. package/.framework/seeds/types/spine_instance.json +109 -0
  30. package/.framework/src/App.tsx +11 -21
  31. package/.framework/src/components/CustomAppLoader.tsx +2 -19
  32. package/.framework/src/components/ui/accordion.tsx +1 -1
  33. package/.framework/src/components/ui/alert-dialog.tsx +1 -1
  34. package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
  35. package/.framework/src/components/ui/avatar.tsx +1 -1
  36. package/.framework/src/components/ui/badge.tsx +2 -2
  37. package/.framework/src/components/ui/breadcrumb.tsx +2 -2
  38. package/.framework/src/components/ui/button-group.tsx +2 -2
  39. package/.framework/src/components/ui/button.tsx +2 -2
  40. package/.framework/src/components/ui/chart.tsx +6 -1
  41. package/.framework/src/components/ui/checkbox.tsx +1 -1
  42. package/.framework/src/components/ui/collapsible.tsx +1 -1
  43. package/.framework/src/components/ui/context-menu.tsx +1 -1
  44. package/.framework/src/components/ui/dialog.tsx +1 -1
  45. package/.framework/src/components/ui/direction.tsx +17 -1
  46. package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
  47. package/.framework/src/components/ui/hover-card.tsx +1 -1
  48. package/.framework/src/components/ui/item.tsx +2 -2
  49. package/.framework/src/components/ui/label.tsx +1 -1
  50. package/.framework/src/components/ui/menubar.tsx +1 -1
  51. package/.framework/src/components/ui/navigation-menu.tsx +1 -1
  52. package/.framework/src/components/ui/popover.tsx +1 -1
  53. package/.framework/src/components/ui/progress.tsx +1 -1
  54. package/.framework/src/components/ui/radio-group.tsx +1 -1
  55. package/.framework/src/components/ui/scroll-area.tsx +1 -1
  56. package/.framework/src/components/ui/select.tsx +1 -1
  57. package/.framework/src/components/ui/separator.tsx +1 -1
  58. package/.framework/src/components/ui/sheet.tsx +1 -1
  59. package/.framework/src/components/ui/sidebar.tsx +6 -6
  60. package/.framework/src/components/ui/slider.tsx +1 -1
  61. package/.framework/src/components/ui/switch.tsx +1 -1
  62. package/.framework/src/components/ui/tabs.tsx +1 -1
  63. package/.framework/src/components/ui/toggle-group.tsx +1 -1
  64. package/.framework/src/components/ui/toggle.tsx +1 -1
  65. package/.framework/src/components/ui/tooltip.tsx +1 -1
  66. package/.framework/src/pages/auth/LoginPage.tsx +10 -9
  67. package/LICENSE.md +63 -93
  68. package/README.md +254 -129
  69. package/custom/.devin/skills/install-app/SKILL.md +39 -0
  70. package/custom/.devin/workflows/app-install.yaml +83 -0
  71. package/custom/README.md +79 -0
  72. package/docs/APPS.md +232 -0
  73. package/docs/ARCHITECTURE.md +246 -0
  74. package/docs/INSTALL.md +155 -0
  75. package/docs/README.md +170 -0
  76. package/docs/design-schema-spec.md +1837 -0
  77. package/docs/dev-guides/01-create-an-app.md +183 -0
  78. package/docs/dev-guides/02-create-a-function.md +178 -0
  79. package/docs/dev-guides/03-create-a-component.md +178 -0
  80. package/docs/dev-guides/04-webhook-handlers.md +129 -0
  81. package/docs/dev-guides/05-testing.md +168 -0
  82. package/docs/dev-guides/README.md +40 -0
  83. package/docs/npm-packages-plan.md +288 -0
  84. package/docs/spine-core--api-reference.md +822 -0
  85. package/package.json +79 -150
  86. package/.framework/cli/bin.cjs +0 -14
  87. package/.framework/cli/commands/claim.ts +0 -430
  88. package/.framework/cli/commands/init.ts +0 -219
  89. package/.framework/cli/commands/install-app.ts +0 -685
  90. package/.framework/cli/commands/migrate.ts +0 -178
  91. package/.framework/cli/commands/publish.ts +0 -191
  92. package/.framework/cli/commands/status.ts +0 -268
  93. package/.framework/cli/commands/uninstall-app.ts +0 -166
  94. package/.framework/cli/commands/update-app.ts +0 -397
  95. package/.framework/cli/welcome.cjs +0 -45
  96. package/.framework/config/signal-client.ts +0 -147
  97. package/.framework/config/signals.ts +0 -70
  98. package/.framework/docs/STABILITY.md +0 -52
  99. package/.framework/docs/examples/custom-app/README.md +0 -77
  100. package/.framework/docs/examples/custom-function/README.md +0 -27
  101. package/.framework/docs/examples/custom-function/handler.ts +0 -48
  102. package/.framework/docs/examples/custom-webhook/README.md +0 -68
  103. package/.framework/functions/handler-registry.ts +0 -22
  104. package/.framework/functions/signal.ts +0 -168
  105. package/.framework/migrations/000_foundation.sql +0 -1302
  106. package/.framework/migrations/001_seed.sql +0 -163
  107. package/.framework/migrations/002_seed_constraints.sql +0 -19
  108. package/.framework/migrations/003_auth_user_trigger.sql +0 -67
  109. package/.framework/migrations/004_app_version_tracking.sql +0 -8
  110. package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
  111. package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
  112. package/CHANGELOG.md +0 -71
  113. package/STRUCTURE.md +0 -150
  114. package/bin/spine-framework.cjs +0 -62
  115. package/bin/welcome.cjs +0 -58
  116. package/bin/ws-shim.ts +0 -10
  117. package/config/components.json +0 -25
  118. package/config/deno.lock +0 -108
  119. package/config/package-lock.json +0 -17183
  120. package/config/postcss.config.cjs +0 -10
  121. package/config/tailwind.config.cjs +0 -78
  122. package/config/tsconfig.build.json +0 -32
  123. package/config/tsconfig.cli.json +0 -18
  124. package/config/tsconfig.json +0 -41
  125. package/config/tsconfig.node.json +0 -17
  126. package/config/tsconfig.node.tsbuildinfo +0 -1
  127. package/config/tsconfig.tsbuildinfo +0 -1
  128. package/config/typedoc.json +0 -16
  129. package/config/vite.config.d.ts +0 -2
  130. package/config/vite.config.ts +0 -71
  131. package/dist/cli/commands/agents.d.ts +0 -39
  132. package/dist/cli/commands/agents.d.ts.map +0 -1
  133. package/dist/cli/commands/auth.d.ts +0 -36
  134. package/dist/cli/commands/auth.d.ts.map +0 -1
  135. package/dist/cli/commands/claim.d.ts +0 -38
  136. package/dist/cli/commands/claim.d.ts.map +0 -1
  137. package/dist/cli/commands/create-app.d.ts +0 -23
  138. package/dist/cli/commands/create-app.d.ts.map +0 -1
  139. package/dist/cli/commands/dev.d.ts +0 -24
  140. package/dist/cli/commands/dev.d.ts.map +0 -1
  141. package/dist/cli/commands/doctor.d.ts +0 -42
  142. package/dist/cli/commands/doctor.d.ts.map +0 -1
  143. package/dist/cli/commands/generate.d.ts +0 -36
  144. package/dist/cli/commands/generate.d.ts.map +0 -1
  145. package/dist/cli/commands/init.d.ts +0 -20
  146. package/dist/cli/commands/init.d.ts.map +0 -1
  147. package/dist/cli/commands/install-app.d.ts +0 -30
  148. package/dist/cli/commands/install-app.d.ts.map +0 -1
  149. package/dist/cli/commands/items.d.ts +0 -45
  150. package/dist/cli/commands/items.d.ts.map +0 -1
  151. package/dist/cli/commands/migrate.d.ts +0 -21
  152. package/dist/cli/commands/migrate.d.ts.map +0 -1
  153. package/dist/cli/commands/migrations.d.ts +0 -41
  154. package/dist/cli/commands/migrations.d.ts.map +0 -1
  155. package/dist/cli/commands/pipelines.d.ts +0 -40
  156. package/dist/cli/commands/pipelines.d.ts.map +0 -1
  157. package/dist/cli/commands/publish.d.ts +0 -23
  158. package/dist/cli/commands/publish.d.ts.map +0 -1
  159. package/dist/cli/commands/status.d.ts +0 -23
  160. package/dist/cli/commands/status.d.ts.map +0 -1
  161. package/dist/cli/commands/system.d.ts +0 -29
  162. package/dist/cli/commands/system.d.ts.map +0 -1
  163. package/dist/cli/commands/test.d.ts +0 -46
  164. package/dist/cli/commands/test.d.ts.map +0 -1
  165. package/dist/cli/commands/uninstall-app.d.ts +0 -23
  166. package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
  167. package/dist/cli/commands/update-app.d.ts +0 -24
  168. package/dist/cli/commands/update-app.d.ts.map +0 -1
  169. package/dist/cli/context.d.ts +0 -88
  170. package/dist/cli/context.d.ts.map +0 -1
  171. package/dist/cli/env-loader.d.ts +0 -14
  172. package/dist/cli/env-loader.d.ts.map +0 -1
  173. package/dist/cli/index.d.ts +0 -44
  174. package/dist/cli/index.d.ts.map +0 -1
  175. package/dist/config/signals.d.ts +0 -82
  176. package/dist/config/signals.d.ts.map +0 -1
  177. package/dist/functions/_shared/agent-runner.d.ts +0 -156
  178. package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
  179. package/dist/functions/_shared/app-manifest.d.ts +0 -68
  180. package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
  181. package/dist/functions/_shared/audit.d.ts +0 -91
  182. package/dist/functions/_shared/audit.d.ts.map +0 -1
  183. package/dist/functions/_shared/db.d.ts +0 -125
  184. package/dist/functions/_shared/db.d.ts.map +0 -1
  185. package/dist/functions/_shared/index.d.ts +0 -299
  186. package/dist/functions/_shared/index.d.ts.map +0 -1
  187. package/dist/functions/_shared/middleware.d.ts +0 -315
  188. package/dist/functions/_shared/middleware.d.ts.map +0 -1
  189. package/dist/functions/_shared/permissions.d.ts +0 -626
  190. package/dist/functions/_shared/permissions.d.ts.map +0 -1
  191. package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
  192. package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
  193. package/dist/functions/_shared/principal.d.ts +0 -284
  194. package/dist/functions/_shared/principal.d.ts.map +0 -1
  195. package/dist/functions/_shared/resolve-ids.d.ts +0 -10
  196. package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
  197. package/dist/functions/_shared/schema-utils.d.ts +0 -181
  198. package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
  199. package/dist/functions/_shared/testing.d.ts +0 -172
  200. package/dist/functions/_shared/testing.d.ts.map +0 -1
  201. package/dist/functions/_shared/trigger-engine.d.ts +0 -140
  202. package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
  203. package/dist/functions/_shared/webhook-registration.d.ts +0 -81
  204. package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
  205. package/dist/functions/_shared/webhook-registry.d.ts +0 -41
  206. package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
  207. package/dist/functions/account-nodes.d.ts +0 -48
  208. package/dist/functions/account-nodes.d.ts.map +0 -1
  209. package/dist/functions/admin-data.d.ts +0 -178
  210. package/dist/functions/admin-data.d.ts.map +0 -1
  211. package/dist/functions/ai-agents.d.ts +0 -125
  212. package/dist/functions/ai-agents.d.ts.map +0 -1
  213. package/dist/functions/api-keys.d.ts +0 -140
  214. package/dist/functions/api-keys.d.ts.map +0 -1
  215. package/dist/functions/apps.d.ts +0 -163
  216. package/dist/functions/apps.d.ts.map +0 -1
  217. package/dist/functions/auth.d.ts +0 -74
  218. package/dist/functions/auth.d.ts.map +0 -1
  219. package/dist/functions/debug-auth.d.ts +0 -33
  220. package/dist/functions/debug-auth.d.ts.map +0 -1
  221. package/dist/functions/embeddings.d.ts +0 -205
  222. package/dist/functions/embeddings.d.ts.map +0 -1
  223. package/dist/functions/handler-registry.d.ts +0 -22
  224. package/dist/functions/handler-registry.d.ts.map +0 -1
  225. package/dist/functions/integration-routes.d.ts +0 -45
  226. package/dist/functions/integration-routes.d.ts.map +0 -1
  227. package/dist/functions/integrations.d.ts +0 -124
  228. package/dist/functions/integrations.d.ts.map +0 -1
  229. package/dist/functions/item-progress.d.ts +0 -41
  230. package/dist/functions/item-progress.d.ts.map +0 -1
  231. package/dist/functions/logs.d.ts +0 -162
  232. package/dist/functions/logs.d.ts.map +0 -1
  233. package/dist/functions/observability.d.ts +0 -123
  234. package/dist/functions/observability.d.ts.map +0 -1
  235. package/dist/functions/pipeline-executions.d.ts +0 -190
  236. package/dist/functions/pipeline-executions.d.ts.map +0 -1
  237. package/dist/functions/pipelines.d.ts +0 -171
  238. package/dist/functions/pipelines.d.ts.map +0 -1
  239. package/dist/functions/prompt-configs.d.ts +0 -125
  240. package/dist/functions/prompt-configs.d.ts.map +0 -1
  241. package/dist/functions/roles.d.ts +0 -118
  242. package/dist/functions/roles.d.ts.map +0 -1
  243. package/dist/functions/signal.d.ts +0 -73
  244. package/dist/functions/signal.d.ts.map +0 -1
  245. package/dist/functions/system-cron.d.ts +0 -65
  246. package/dist/functions/system-cron.d.ts.map +0 -1
  247. package/dist/functions/system.d.ts +0 -29
  248. package/dist/functions/system.d.ts.map +0 -1
  249. package/dist/functions/tests.d.ts +0 -28
  250. package/dist/functions/tests.d.ts.map +0 -1
  251. package/dist/functions/timers.d.ts +0 -139
  252. package/dist/functions/timers.d.ts.map +0 -1
  253. package/dist/functions/triggers.d.ts +0 -203
  254. package/dist/functions/triggers.d.ts.map +0 -1
  255. package/dist/functions/types.d.ts +0 -151
  256. package/dist/functions/types.d.ts.map +0 -1
  257. package/dist/src/types/types.d.ts +0 -364
  258. package/dist/src/types/types.d.ts.map +0 -1
  259. package/index.html +0 -13
  260. package/netlify.toml +0 -36
  261. package/package-project.json +0 -71
  262. package/scripts/ingest-chunks.cjs +0 -202
  263. package/scripts/kb-chunk-parser.cjs +0 -312
  264. package/scripts/kb-chunk-parser.ts +0 -330
  265. package/scripts/load-test-app-install.ts +0 -484
  266. package/scripts/netlify-dev-wrapper.sh +0 -22
  267. package/vitest.config.ts +0 -45
  268. /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
  269. /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
  270. /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'))