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