spine-framework 0.3.16 → 0.3.18

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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 +3 -7
  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 -68
  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,178 +0,0 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module cli/commands/migrate
4
- * @audience installer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * `spine-framework migrate` — Apply SQL migrations to a Supabase project
9
- * via a direct Postgres connection.
10
- *
11
- * Requires --db-password (the Supabase database password).
12
- * Get it from: https://supabase.com/dashboard/project/_/settings/database
13
- *
14
- * **Usage:**
15
- * ```bash
16
- * spine-framework migrate --db-password <password>
17
- * spine-framework migrate --db-password <password> --dry-run
18
- * ```
19
- */
20
-
21
- import type { Command } from 'commander'
22
- import { existsSync, readdirSync, readFileSync } from 'fs'
23
- import { resolve, dirname } from 'path'
24
- import { fileURLToPath } from 'url'
25
-
26
- const __filename = fileURLToPath(import.meta.url)
27
- const __dirname = dirname(__filename)
28
- const MIGRATIONS_DIR = resolve(__dirname, '../../migrations')
29
-
30
- interface MigrateOptions {
31
- dbPassword?: string
32
- dryRun: boolean
33
- }
34
-
35
- async function migrateCommand(options: MigrateOptions): Promise<void> {
36
- console.log('\n🗄️ Spine Framework — Migrate\n')
37
-
38
- const supabaseUrl = process.env.SUPABASE_URL
39
- if (!supabaseUrl || supabaseUrl === 'https://your-project.supabase.co') {
40
- console.error('❌ SUPABASE_URL not set. Fill in your .env file first.')
41
- process.exit(1)
42
- }
43
-
44
- const dbPassword = options.dbPassword || process.env.SUPABASE_DB_PASSWORD
45
- if (!dbPassword || dbPassword === 'your-db-password') {
46
- console.error('❌ SUPABASE_DB_PASSWORD not set.')
47
- console.error(' Add it to .env or pass --db-password <password>')
48
- console.error(' Get it from: https://supabase.com/dashboard/project/_/settings/database')
49
- process.exit(1)
50
- }
51
-
52
- const projectRef = supabaseUrl.replace('https://', '').split('.')[0]
53
- const connectionString = `postgresql://postgres:${dbPassword}@db.${projectRef}.supabase.co:5432/postgres`
54
-
55
- if (!existsSync(MIGRATIONS_DIR)) {
56
- console.error(`❌ Migrations directory not found: ${MIGRATIONS_DIR}`)
57
- process.exit(1)
58
- }
59
-
60
- const files = readdirSync(MIGRATIONS_DIR)
61
- .filter(f => f.endsWith('.sql'))
62
- .sort()
63
-
64
- if (files.length === 0) {
65
- console.log(' No migration files found.')
66
- return
67
- }
68
-
69
- console.log(` Found ${files.length} migration file(s)\n`)
70
-
71
- if (options.dryRun) {
72
- for (const file of files) {
73
- console.log(` [dry-run] Would apply: ${file}`)
74
- }
75
- return
76
- }
77
-
78
- // Dynamically import pg to avoid loading it at module init time
79
- const { default: pg } = await import('pg')
80
- const client = new pg.Client({ connectionString })
81
-
82
- try {
83
- await client.connect()
84
- console.log(' ✓ Connected to database\n')
85
- } catch (err: any) {
86
- console.error(`❌ Could not connect to database: ${err.message}`)
87
- console.error(' Check your --db-password and that the project is active.')
88
- process.exit(1)
89
- }
90
-
91
- let applied = 0
92
- let failed = 0
93
-
94
- for (const file of files) {
95
- const sql = readFileSync(resolve(MIGRATIONS_DIR, file), 'utf8')
96
- process.stdout.write(` Applying ${file}... `)
97
-
98
- // Split SQL into statements, being careful to preserve dollar-quoted blocks.
99
- // Dollar-quoted strings ($$ ... $$ or $tag$ ... $tag$) can contain semicolons
100
- // that are NOT statement terminators.
101
- function splitSqlStatements(sql: string): string[] {
102
- const dollarBlocks: string[] = []
103
- let blockIndex = 0
104
-
105
- // Step 1: Protect dollar-quoted blocks with placeholders (preserves internal semicolons)
106
- const protectedSql = sql.replace(/\$[A-Za-z_]*\$[\s\S]*?\$[A-Za-z_]*\$/g, (match) => {
107
- const placeholder = `__DOLLAR_BLOCK_${blockIndex}__`
108
- dollarBlocks[blockIndex] = match
109
- blockIndex++
110
- return placeholder
111
- })
112
-
113
- // Step 2: Strip single-line comments (prevents semicolons inside -- comments
114
- // from being treated as statement terminators)
115
- const noComments = protectedSql.replace(/--[^\n]*/g, '')
116
-
117
- // Step 3: Split by semicolons, restore dollar blocks, and filter empty statements
118
- return noComments
119
- .split(';')
120
- .map(s => {
121
- let restored = s.trim()
122
- // Use function form of replace — prevents JS from interpreting $$ as $
123
- dollarBlocks.forEach((block, idx) => {
124
- restored = restored.replace(`__DOLLAR_BLOCK_${idx}__`, () => block)
125
- })
126
- return restored
127
- })
128
- .filter(s => s.length > 0)
129
- .map(s => s + ';')
130
- }
131
-
132
- const statements = splitSqlStatements(sql)
133
-
134
- try {
135
- for (const stmt of statements) {
136
- await client.query(stmt)
137
- }
138
- console.log('✓')
139
- applied++
140
- } catch (err: any) {
141
- console.log('❌')
142
- console.error(` ${err.message}`)
143
- failed++
144
- // Continue applying remaining migrations
145
- }
146
- }
147
-
148
- await client.end()
149
-
150
- console.log(`\n ${applied} applied, ${failed} failed`)
151
-
152
- if (failed > 0) {
153
- console.error('\n❌ Some migrations failed. Check errors above.')
154
- process.exit(1)
155
- }
156
-
157
- console.log('\n✅ All migrations applied successfully!')
158
- console.log('\n Next steps:')
159
- console.log(' 1. spine-framework install-app <app-slug>')
160
- console.log(' 2. npm run assemble && netlify dev')
161
- }
162
-
163
- export function registerMigrateCommands(program: Command) {
164
- program
165
- .command('migrate')
166
- .description('Apply SQL migrations via direct Postgres connection')
167
- .option('--db-password <password>', 'DB password override (default: reads SUPABASE_DB_PASSWORD from .env)')
168
- .option('--dry-run', 'Show what would happen without making changes', false)
169
- .action(async (opts) => {
170
- try {
171
- await migrateCommand(opts)
172
- } catch (err: any) {
173
- console.error('Error:', err.message)
174
- if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
175
- process.exit(1)
176
- }
177
- })
178
- }
@@ -1,191 +0,0 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module cli/commands/publish
4
- * @audience developer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * `spine-framework publish` — Bump version, write changelog, publish to npm, commit.
9
- *
10
- * Run from the spine-ia coding repo. Handles both the framework package and
11
- * individual app packages (cortex, portal, etc.).
12
- *
13
- * **Usage:**
14
- * ```bash
15
- * spine-framework publish # publish framework (patch bump)
16
- * spine-framework publish --app cortex # publish cortex app (patch bump)
17
- * spine-framework publish --app cortex --bump minor
18
- * spine-framework publish --app cortex --bump patch --notes "Fix sidebar nav"
19
- * spine-framework publish --app cortex --ci --notes "Fix sidebar nav" --bump patch
20
- * ```
21
- */
22
-
23
- import type { Command } from 'commander'
24
- import { existsSync, readFileSync, writeFileSync } from 'fs'
25
- import { resolve, dirname } from 'path'
26
- import { fileURLToPath } from 'url'
27
- import { execSync } from 'child_process'
28
- import * as readline from 'readline'
29
-
30
- const __filename = fileURLToPath(import.meta.url)
31
- const __dirname = dirname(__filename)
32
- const PROJECT_ROOT = process.cwd()
33
-
34
- interface PublishOptions {
35
- app?: string
36
- bump: 'patch' | 'minor' | 'major'
37
- notes?: string
38
- ci: boolean
39
- dryRun: boolean
40
- }
41
-
42
- // ─── SEMVER BUMP ──────────────────────────────────────────────────────────────
43
-
44
- function bumpVersion(current: string, bump: 'patch' | 'minor' | 'major'): string {
45
- const [major, minor, patch] = current.replace(/^v/, '').split('.').map(Number)
46
- if (bump === 'major') return `${major + 1}.0.0`
47
- if (bump === 'minor') return `${major}.${minor + 1}.0`
48
- return `${major}.${minor}.${patch + 1}`
49
- }
50
-
51
- // ─── INTERACTIVE PROMPT ───────────────────────────────────────────────────────
52
-
53
- function promptNotes(): Promise<string> {
54
- return new Promise(resolve => {
55
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
56
- rl.question(' Changelog notes (comma-separated): ', answer => {
57
- rl.close()
58
- resolve(answer.trim())
59
- })
60
- })
61
- }
62
-
63
- // ─── PUBLISH LOGIC ────────────────────────────────────────────────────────────
64
-
65
- async function publishTarget(options: PublishOptions): Promise<void> {
66
- const isApp = !!options.app
67
- const slug = options.app
68
-
69
- let pkgJsonPath: string
70
- let manifestPath: string | null
71
- let publishDir: string
72
- let displayName: string
73
-
74
- if (isApp && slug) {
75
- pkgJsonPath = resolve(PROJECT_ROOT, `custom/apps/${slug}/package.json`)
76
- manifestPath = resolve(PROJECT_ROOT, `custom/apps/${slug}/manifest.json`)
77
- publishDir = resolve(PROJECT_ROOT, `custom/apps/${slug}`)
78
- displayName = `spine-framework-${slug}`
79
- } else {
80
- pkgJsonPath = resolve(PROJECT_ROOT, 'package.json')
81
- manifestPath = null
82
- publishDir = PROJECT_ROOT
83
- displayName = 'spine-framework'
84
- }
85
-
86
- if (!existsSync(pkgJsonPath)) {
87
- console.error(` ❌ package.json not found: ${pkgJsonPath}`)
88
- process.exit(1)
89
- }
90
-
91
- // 1. Read current version
92
- const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
93
- const currentVersion = pkgJson.version as string
94
- const newVersion = bumpVersion(currentVersion, options.bump)
95
-
96
- console.log(`\n📦 Publishing ${displayName}`)
97
- console.log(` ${currentVersion} → ${newVersion} (${options.bump} bump)\n`)
98
-
99
- // 2. Collect changelog notes
100
- let notesInput = options.notes || ''
101
- if (!notesInput) {
102
- if (options.ci) {
103
- console.error(` ❌ --ci mode requires --notes "..."`)
104
- process.exit(1)
105
- }
106
- notesInput = await promptNotes()
107
- }
108
- const notes = notesInput
109
- .split(',')
110
- .map(s => s.trim())
111
- .filter(Boolean)
112
-
113
- if (notes.length === 0) {
114
- console.error(` ❌ At least one changelog note is required.`)
115
- process.exit(1)
116
- }
117
-
118
- if (options.dryRun) {
119
- console.log(` [dry-run] Would bump ${displayName} ${currentVersion} → ${newVersion}`)
120
- console.log(` [dry-run] Changelog entry:`)
121
- console.log(` { "version": "${newVersion}", "notes": ${JSON.stringify(notes)} }`)
122
- console.log(` [dry-run] Would run: npm publish (from ${publishDir})`)
123
- console.log(` [dry-run] Would commit: chore: publish ${displayName}@${newVersion}`)
124
- return
125
- }
126
-
127
- // 3. Bump version in package.json
128
- pkgJson.version = newVersion
129
- writeFileSync(pkgJsonPath, JSON.stringify(pkgJson, null, 2) + '\n', 'utf8')
130
- console.log(` ✓ package.json version → ${newVersion}`)
131
-
132
- // 4. Prepend changelog entry to manifest.json (apps only)
133
- if (manifestPath && existsSync(manifestPath)) {
134
- const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
135
- const changelog: Array<{ version: string; notes: string[] }> = manifest.changelog || []
136
- changelog.unshift({ version: newVersion, notes })
137
- manifest.version = newVersion
138
- manifest.changelog = changelog
139
- writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf8')
140
- console.log(` ✓ manifest.json changelog prepended, version → ${newVersion}`)
141
- }
142
-
143
- // 5. npm publish
144
- console.log(`\n🚀 Running npm publish...`)
145
- try {
146
- execSync('npm publish --access public', { cwd: publishDir, stdio: 'inherit' })
147
- console.log(` ✓ Published ${displayName}@${newVersion}`)
148
- } catch (err: any) {
149
- console.error(` ❌ npm publish failed: ${err.message}`)
150
- console.error(` Version files have already been updated — commit them manually.`)
151
- process.exit(1)
152
- }
153
-
154
- // 6. Git commit
155
- console.log(`\n📝 Committing version bump...`)
156
- try {
157
- const filesToStage = [pkgJsonPath]
158
- if (manifestPath && existsSync(manifestPath)) filesToStage.push(manifestPath)
159
- execSync(`git add ${filesToStage.map(f => `"${f}"`).join(' ')}`, { cwd: PROJECT_ROOT })
160
- execSync(
161
- `git commit -m "chore: publish ${displayName}@${newVersion}"`,
162
- { cwd: PROJECT_ROOT, stdio: 'inherit' },
163
- )
164
- console.log(` ✓ Committed`)
165
- } catch (err: any) {
166
- console.error(` ⚠️ Git commit failed: ${err.message}`)
167
- console.error(` Published successfully — commit the version files manually.`)
168
- }
169
-
170
- console.log(`\n✅ ${displayName}@${newVersion} published successfully!\n`)
171
- }
172
-
173
- export function registerPublishCommands(program: Command) {
174
- program
175
- .command('publish')
176
- .description('Bump version, write changelog, publish to npm, commit (run from spine-ia)')
177
- .option('--app <slug>', 'App slug to publish (e.g. cortex). Omit to publish the framework itself.')
178
- .option('--bump <level>', 'Version bump level: patch | minor | major', 'patch')
179
- .option('--notes <text>', 'Comma-separated changelog notes (required in --ci mode)')
180
- .option('--ci', 'Non-interactive mode: skip prompts, requires --notes', false)
181
- .option('--dry-run', 'Show what would happen without making changes', false)
182
- .action(async (opts) => {
183
- try {
184
- await publishTarget(opts as PublishOptions)
185
- } catch (err: any) {
186
- console.error('Error:', err.message)
187
- if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
188
- process.exit(1)
189
- }
190
- })
191
- }
@@ -1,268 +0,0 @@
1
- /// <reference types="node" />
2
- /**
3
- * @module cli/commands/status
4
- * @audience installer
5
- * @layer cli
6
- * @stability stable
7
- *
8
- * `spine-framework status` — Show the current state of the Spine installation.
9
- *
10
- * Displays:
11
- * - Database connection status
12
- * - Installed apps and their seed state
13
- * - Migration history
14
- * - Webhook handler registrations
15
- *
16
- * **Usage:**
17
- * ```bash
18
- * spine-framework status
19
- * spine-framework status --json
20
- * ```
21
- */
22
-
23
- import type { Command } from 'commander'
24
- import { existsSync, readdirSync } from 'fs'
25
- import { resolve, dirname } from 'path'
26
- import { fileURLToPath } from 'url'
27
- import { adminDb } from '../../functions/_shared/index.ts'
28
- import { discoverManifests } from '../../functions/_shared/app-manifest.ts'
29
- import { readEngagementData, ensureSerialData, shouldOfferClaim } from './claim.ts'
30
-
31
- const __filename = fileURLToPath(import.meta.url)
32
- const __dirname = dirname(__filename)
33
- const PROJECT_ROOT = resolve(__dirname, '../../../')
34
-
35
- interface StatusOptions {
36
- json: boolean
37
- }
38
-
39
- interface StatusReport {
40
- database: { connected: boolean; error?: string }
41
- installation: {
42
- frameworkInstallSerial: string | null
43
- engagementScore: number
44
- claimStatus: 'anonymous' | 'claimed' | 'pending' | null
45
- claimPrompt: { timing: string; message: string } | null
46
- }
47
- apps: Array<{
48
- slug: string
49
- name: string
50
- hasManifest: boolean
51
- hasSeed: boolean
52
- installed: boolean
53
- installedVersion: string | null
54
- packageName: string | null
55
- appInstallSerial: string | null
56
- installTimestamp: string | null
57
- }>
58
- webhookHandlers: Array<{ name: string; functionName: string; isActive: boolean }>
59
- typeCounts: { total: number; byApp: Record<string, number> }
60
- }
61
-
62
- async function gatherStatus(): Promise<StatusReport> {
63
- // Get installation/engagement data
64
- let frameworkSerial: string | null = null
65
- let engagementScore = 0
66
- let claimStatus: 'anonymous' | 'claimed' | 'pending' | null = null
67
- let claimPrompt: { timing: string; message: string } | null = null
68
-
69
- try {
70
- const serialData = ensureSerialData()
71
- frameworkSerial = serialData.serial_number
72
- const engagement = readEngagementData()
73
- if (engagement) {
74
- engagementScore = engagement.engagement_score
75
- claimStatus = engagement.claim_status
76
- const prompt = shouldOfferClaim()
77
- if (prompt) {
78
- claimPrompt = { timing: prompt.timing, message: prompt.message }
79
- }
80
- }
81
- } catch {
82
- // Silent fail - engagement tracking is optional
83
- }
84
-
85
- const report: StatusReport = {
86
- database: { connected: false },
87
- installation: {
88
- frameworkInstallSerial: frameworkSerial,
89
- engagementScore,
90
- claimStatus,
91
- claimPrompt,
92
- },
93
- apps: [],
94
- webhookHandlers: [],
95
- typeCounts: { total: 0, byApp: {} },
96
- }
97
-
98
- // 1. Database connection
99
- try {
100
- const { data, error } = await adminDb
101
- .from('apps')
102
- .select('slug')
103
- .limit(1)
104
-
105
- if (error) {
106
- report.database = { connected: false, error: error.message }
107
- } else {
108
- report.database = { connected: true }
109
- }
110
- } catch (err: any) {
111
- report.database = { connected: false, error: err.message }
112
- return report
113
- }
114
-
115
- // 2. Apps — merge filesystem + database
116
- const manifests = discoverManifests()
117
- const { data: dbApps } = await adminDb
118
- .from('apps')
119
- .select('slug, name, is_active')
120
- .eq('is_active', true)
121
- .order('slug')
122
-
123
- const { data: installations } = await adminDb
124
- .from('app_installations')
125
- .select('app_slug, is_enabled, installed_version, package_name')
126
-
127
- const installedSlugs = new Set(
128
- (installations || []).filter(i => i.is_enabled).map(i => i.app_slug)
129
- )
130
-
131
- const installationMap = new Map(
132
- (installations || []).map(i => [i.app_slug, i])
133
- )
134
-
135
- const allSlugs = new Set([
136
- ...manifests.map(m => m.slug),
137
- ...(dbApps || []).map(a => a.slug).filter(s => s !== 'spine-core'),
138
- ])
139
-
140
- for (const slug of allSlugs) {
141
- const manifest = manifests.find(m => m.slug === slug)
142
- const dbApp = dbApps?.find(a => a.slug === slug)
143
- const seedDir = resolve(PROJECT_ROOT, `custom/apps/${slug}/seed`)
144
-
145
- const installation = installationMap.get(slug)
146
- report.apps.push({
147
- slug,
148
- name: dbApp?.name || slug,
149
- hasManifest: !!manifest,
150
- hasSeed: existsSync(seedDir) && readdirSync(seedDir).some(f => f.endsWith('.json')),
151
- installed: installedSlugs.has(slug),
152
- installedVersion: installation?.installed_version || null,
153
- packageName: installation?.package_name || null,
154
- appInstallSerial: null, // Populated via signal system
155
- installTimestamp: null, // Populated via signal system
156
- })
157
- }
158
-
159
- // 3. Webhook handlers
160
- const { data: handlers } = await adminDb
161
- .from('webhook_handlers')
162
- .select('name, function_name, is_active')
163
- .order('name')
164
-
165
- report.webhookHandlers = (handlers || []).map(h => ({
166
- name: h.name,
167
- functionName: h.function_name,
168
- isActive: h.is_active,
169
- }))
170
-
171
- // 4. Type counts by app
172
- const { data: types } = await adminDb
173
- .from('types')
174
- .select('slug, app_id, apps!inner(slug)')
175
- .eq('is_active', true)
176
-
177
- report.typeCounts.total = types?.length || 0
178
- for (const t of types || []) {
179
- const appSlug = (t as any).apps?.slug || 'unassigned'
180
- report.typeCounts.byApp[appSlug] = (report.typeCounts.byApp[appSlug] || 0) + 1
181
- }
182
-
183
- return report
184
- }
185
-
186
- function printStatus(report: StatusReport): void {
187
- console.log('\n🔍 Spine Framework Status\n')
188
-
189
- // Installation / Claim Status
190
- if (report.installation.frameworkInstallSerial) {
191
- console.log(` 🔐 Installation: ${report.installation.frameworkInstallSerial}`)
192
- if (report.installation.claimStatus === 'claimed') {
193
- console.log(' ✅ Claimed: Yes')
194
- } else {
195
- console.log(` ⏳ Claim Status: ${report.installation.claimStatus || 'anonymous'}`)
196
- console.log(` 📊 Engagement Score: ${report.installation.engagementScore}`)
197
- if (report.installation.claimPrompt) {
198
- console.log(` 💡 ${report.installation.claimPrompt.message}`)
199
- console.log(` Run: spine-framework claim`)
200
- }
201
- }
202
- console.log('')
203
- }
204
-
205
- // Database
206
- if (report.database.connected) {
207
- console.log(' 📡 Database: ✅ Connected')
208
- } else {
209
- console.log(` 📡 Database: ❌ ${report.database.error || 'Not connected'}`)
210
- return
211
- }
212
-
213
- // Apps
214
- console.log(`\n 📱 Apps (${report.apps.length}):`)
215
- if (report.apps.length === 0) {
216
- console.log(' (none)')
217
- } else {
218
- for (const app of report.apps) {
219
- const versionStr = app.installedVersion ? `v${app.installedVersion}` : ''
220
- const status = [
221
- app.hasManifest ? '📋 manifest' : '',
222
- app.hasSeed ? '🌱 seed' : '',
223
- app.installed ? `✅ installed${versionStr ? ' ' + versionStr : ''}` : '⬜ not installed',
224
- ].filter(Boolean).join(' ')
225
- console.log(` ${app.slug.padEnd(24)} ${status}`)
226
- }
227
- }
228
-
229
- // Webhook handlers
230
- console.log(`\n 🔗 Webhook Handlers (${report.webhookHandlers.length}):`)
231
- if (report.webhookHandlers.length === 0) {
232
- console.log(' (none)')
233
- } else {
234
- for (const h of report.webhookHandlers) {
235
- const status = h.isActive ? '✅' : '❌'
236
- console.log(` ${status} ${h.name.padEnd(24)} → ${h.functionName}`)
237
- }
238
- }
239
-
240
- // Types
241
- console.log(`\n 📊 Types (${report.typeCounts.total} total):`)
242
- for (const [app, count] of Object.entries(report.typeCounts.byApp).sort()) {
243
- console.log(` ${app.padEnd(24)} ${count} types`)
244
- }
245
-
246
- console.log('')
247
- }
248
-
249
- export function registerStatusCommands(program: Command) {
250
- program
251
- .command('status')
252
- .description('Show the current state of the Spine installation')
253
- .option('--json', 'Output as JSON', false)
254
- .action(async (opts: StatusOptions) => {
255
- try {
256
- const report = await gatherStatus()
257
- if (opts.json) {
258
- console.log(JSON.stringify(report, null, 2))
259
- } else {
260
- printStatus(report)
261
- }
262
- } catch (err: any) {
263
- console.error('Error:', err.message)
264
- if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
265
- process.exit(1)
266
- }
267
- })
268
- }