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.
- package/.framework/cli/commands/create-app.ts +1 -1
- package/.framework/cli/commands/dev.ts +215 -33
- package/.framework/cli/index.ts +37 -75
- package/.framework/functions/_shared/app-manifest.ts +8 -15
- package/.framework/functions/_shared/db.ts +11 -15
- package/.framework/functions/_shared/index.ts +7 -16
- package/.framework/functions/_shared/principal.ts +4 -62
- package/.framework/functions/_shared/resolve-ids.ts +6 -6
- package/.framework/functions/_shared/webhook-registry.ts +43 -1
- package/.framework/functions/admin-funnel.ts +104 -0
- package/.framework/functions/admin-install-funnel.ts +225 -0
- package/.framework/functions/admin-signals.ts +241 -0
- package/.framework/functions/integration-routes.ts +3 -7
- package/.framework/migrations/001_schema.sql +406 -0
- package/.framework/migrations/002_rls_and_functions.sql +710 -0
- package/.framework/migrations/003_system_data.sql +520 -0
- package/.framework/migrations/004_enhancements.sql +43 -0
- package/.framework/public/_redirects +3 -0
- package/.framework/public/spine-logo.jpg +0 -0
- package/.framework/public/spine-logo.svg +3 -0
- package/{scripts → .framework/scripts}/assemble-frontend.sh +8 -5
- package/{scripts → .framework/scripts}/assemble-functions.sh +4 -47
- package/{scripts → .framework/scripts}/assemble.sh +2 -8
- package/{scripts → .framework/scripts}/boundary-check.sh +5 -9
- package/{scripts → .framework/scripts}/check-core-integrity.sh +0 -0
- package/.framework/seeds/link_types/account_cli_instances.json +9 -0
- package/.framework/seeds/types/funnel_signal_use.json +98 -0
- package/.framework/seeds/types/spine_instance.json +109 -0
- package/.framework/src/App.tsx +11 -21
- package/.framework/src/components/CustomAppLoader.tsx +2 -19
- package/.framework/src/components/ui/accordion.tsx +1 -1
- package/.framework/src/components/ui/alert-dialog.tsx +1 -1
- package/.framework/src/components/ui/aspect-ratio.tsx +1 -1
- package/.framework/src/components/ui/avatar.tsx +1 -1
- package/.framework/src/components/ui/badge.tsx +2 -2
- package/.framework/src/components/ui/breadcrumb.tsx +2 -2
- package/.framework/src/components/ui/button-group.tsx +2 -2
- package/.framework/src/components/ui/button.tsx +2 -2
- package/.framework/src/components/ui/chart.tsx +6 -1
- package/.framework/src/components/ui/checkbox.tsx +1 -1
- package/.framework/src/components/ui/collapsible.tsx +1 -1
- package/.framework/src/components/ui/context-menu.tsx +1 -1
- package/.framework/src/components/ui/dialog.tsx +1 -1
- package/.framework/src/components/ui/direction.tsx +17 -1
- package/.framework/src/components/ui/dropdown-menu.tsx +1 -1
- package/.framework/src/components/ui/hover-card.tsx +1 -1
- package/.framework/src/components/ui/item.tsx +2 -2
- package/.framework/src/components/ui/label.tsx +1 -1
- package/.framework/src/components/ui/menubar.tsx +1 -1
- package/.framework/src/components/ui/navigation-menu.tsx +1 -1
- package/.framework/src/components/ui/popover.tsx +1 -1
- package/.framework/src/components/ui/progress.tsx +1 -1
- package/.framework/src/components/ui/radio-group.tsx +1 -1
- package/.framework/src/components/ui/scroll-area.tsx +1 -1
- package/.framework/src/components/ui/select.tsx +1 -1
- package/.framework/src/components/ui/separator.tsx +1 -1
- package/.framework/src/components/ui/sheet.tsx +1 -1
- package/.framework/src/components/ui/sidebar.tsx +6 -6
- package/.framework/src/components/ui/slider.tsx +1 -1
- package/.framework/src/components/ui/switch.tsx +1 -1
- package/.framework/src/components/ui/tabs.tsx +1 -1
- package/.framework/src/components/ui/toggle-group.tsx +1 -1
- package/.framework/src/components/ui/toggle.tsx +1 -1
- package/.framework/src/components/ui/tooltip.tsx +1 -1
- package/.framework/src/pages/auth/LoginPage.tsx +10 -9
- package/LICENSE.md +63 -93
- package/README.md +254 -129
- package/custom/.devin/skills/install-app/SKILL.md +39 -0
- package/custom/.devin/workflows/app-install.yaml +83 -0
- package/custom/README.md +79 -0
- package/docs/APPS.md +232 -0
- package/docs/ARCHITECTURE.md +246 -0
- package/docs/INSTALL.md +155 -0
- package/docs/README.md +170 -0
- package/docs/design-schema-spec.md +1837 -0
- package/docs/dev-guides/01-create-an-app.md +183 -0
- package/docs/dev-guides/02-create-a-function.md +178 -0
- package/docs/dev-guides/03-create-a-component.md +178 -0
- package/docs/dev-guides/04-webhook-handlers.md +129 -0
- package/docs/dev-guides/05-testing.md +168 -0
- package/docs/dev-guides/README.md +40 -0
- package/docs/npm-packages-plan.md +288 -0
- package/docs/spine-core--api-reference.md +822 -0
- package/package.json +78 -150
- package/.framework/cli/bin.cjs +0 -14
- package/.framework/cli/commands/claim.ts +0 -430
- package/.framework/cli/commands/init.ts +0 -219
- package/.framework/cli/commands/install-app.ts +0 -685
- package/.framework/cli/commands/migrate.ts +0 -178
- package/.framework/cli/commands/publish.ts +0 -191
- package/.framework/cli/commands/status.ts +0 -268
- package/.framework/cli/commands/uninstall-app.ts +0 -166
- package/.framework/cli/commands/update-app.ts +0 -397
- package/.framework/cli/welcome.cjs +0 -45
- package/.framework/config/signal-client.ts +0 -147
- package/.framework/config/signals.ts +0 -70
- package/.framework/docs/STABILITY.md +0 -52
- package/.framework/docs/examples/custom-app/README.md +0 -77
- package/.framework/docs/examples/custom-function/README.md +0 -27
- package/.framework/docs/examples/custom-function/handler.ts +0 -48
- package/.framework/docs/examples/custom-webhook/README.md +0 -68
- package/.framework/functions/handler-registry.ts +0 -22
- package/.framework/functions/signal.ts +0 -168
- package/.framework/migrations/000_foundation.sql +0 -1302
- package/.framework/migrations/001_seed.sql +0 -163
- package/.framework/migrations/002_seed_constraints.sql +0 -19
- package/.framework/migrations/003_auth_user_trigger.sql +0 -67
- package/.framework/migrations/004_app_version_tracking.sql +0 -8
- package/.framework/migrations/005_pipelines_slug_stages.sql +0 -21
- package/.framework/src/pages/auth/RegisterPage.tsx +0 -163
- package/CHANGELOG.md +0 -68
- package/STRUCTURE.md +0 -150
- package/bin/spine-framework.cjs +0 -62
- package/bin/welcome.cjs +0 -58
- package/bin/ws-shim.ts +0 -10
- package/config/components.json +0 -25
- package/config/deno.lock +0 -108
- package/config/package-lock.json +0 -17183
- package/config/postcss.config.cjs +0 -10
- package/config/tailwind.config.cjs +0 -78
- package/config/tsconfig.build.json +0 -32
- package/config/tsconfig.cli.json +0 -18
- package/config/tsconfig.json +0 -41
- package/config/tsconfig.node.json +0 -17
- package/config/tsconfig.node.tsbuildinfo +0 -1
- package/config/tsconfig.tsbuildinfo +0 -1
- package/config/typedoc.json +0 -16
- package/config/vite.config.d.ts +0 -2
- package/config/vite.config.ts +0 -71
- package/dist/cli/commands/agents.d.ts +0 -39
- package/dist/cli/commands/agents.d.ts.map +0 -1
- package/dist/cli/commands/auth.d.ts +0 -36
- package/dist/cli/commands/auth.d.ts.map +0 -1
- package/dist/cli/commands/claim.d.ts +0 -38
- package/dist/cli/commands/claim.d.ts.map +0 -1
- package/dist/cli/commands/create-app.d.ts +0 -23
- package/dist/cli/commands/create-app.d.ts.map +0 -1
- package/dist/cli/commands/dev.d.ts +0 -24
- package/dist/cli/commands/dev.d.ts.map +0 -1
- package/dist/cli/commands/doctor.d.ts +0 -42
- package/dist/cli/commands/doctor.d.ts.map +0 -1
- package/dist/cli/commands/generate.d.ts +0 -36
- package/dist/cli/commands/generate.d.ts.map +0 -1
- package/dist/cli/commands/init.d.ts +0 -20
- package/dist/cli/commands/init.d.ts.map +0 -1
- package/dist/cli/commands/install-app.d.ts +0 -30
- package/dist/cli/commands/install-app.d.ts.map +0 -1
- package/dist/cli/commands/items.d.ts +0 -45
- package/dist/cli/commands/items.d.ts.map +0 -1
- package/dist/cli/commands/migrate.d.ts +0 -21
- package/dist/cli/commands/migrate.d.ts.map +0 -1
- package/dist/cli/commands/migrations.d.ts +0 -41
- package/dist/cli/commands/migrations.d.ts.map +0 -1
- package/dist/cli/commands/pipelines.d.ts +0 -40
- package/dist/cli/commands/pipelines.d.ts.map +0 -1
- package/dist/cli/commands/publish.d.ts +0 -23
- package/dist/cli/commands/publish.d.ts.map +0 -1
- package/dist/cli/commands/status.d.ts +0 -23
- package/dist/cli/commands/status.d.ts.map +0 -1
- package/dist/cli/commands/system.d.ts +0 -29
- package/dist/cli/commands/system.d.ts.map +0 -1
- package/dist/cli/commands/test.d.ts +0 -46
- package/dist/cli/commands/test.d.ts.map +0 -1
- package/dist/cli/commands/uninstall-app.d.ts +0 -23
- package/dist/cli/commands/uninstall-app.d.ts.map +0 -1
- package/dist/cli/commands/update-app.d.ts +0 -24
- package/dist/cli/commands/update-app.d.ts.map +0 -1
- package/dist/cli/context.d.ts +0 -88
- package/dist/cli/context.d.ts.map +0 -1
- package/dist/cli/env-loader.d.ts +0 -14
- package/dist/cli/env-loader.d.ts.map +0 -1
- package/dist/cli/index.d.ts +0 -44
- package/dist/cli/index.d.ts.map +0 -1
- package/dist/config/signals.d.ts +0 -82
- package/dist/config/signals.d.ts.map +0 -1
- package/dist/functions/_shared/agent-runner.d.ts +0 -156
- package/dist/functions/_shared/agent-runner.d.ts.map +0 -1
- package/dist/functions/_shared/app-manifest.d.ts +0 -68
- package/dist/functions/_shared/app-manifest.d.ts.map +0 -1
- package/dist/functions/_shared/audit.d.ts +0 -91
- package/dist/functions/_shared/audit.d.ts.map +0 -1
- package/dist/functions/_shared/db.d.ts +0 -125
- package/dist/functions/_shared/db.d.ts.map +0 -1
- package/dist/functions/_shared/index.d.ts +0 -299
- package/dist/functions/_shared/index.d.ts.map +0 -1
- package/dist/functions/_shared/middleware.d.ts +0 -315
- package/dist/functions/_shared/middleware.d.ts.map +0 -1
- package/dist/functions/_shared/permissions.d.ts +0 -626
- package/dist/functions/_shared/permissions.d.ts.map +0 -1
- package/dist/functions/_shared/pipeline-runner.d.ts +0 -124
- package/dist/functions/_shared/pipeline-runner.d.ts.map +0 -1
- package/dist/functions/_shared/principal.d.ts +0 -284
- package/dist/functions/_shared/principal.d.ts.map +0 -1
- package/dist/functions/_shared/resolve-ids.d.ts +0 -10
- package/dist/functions/_shared/resolve-ids.d.ts.map +0 -1
- package/dist/functions/_shared/schema-utils.d.ts +0 -181
- package/dist/functions/_shared/schema-utils.d.ts.map +0 -1
- package/dist/functions/_shared/testing.d.ts +0 -172
- package/dist/functions/_shared/testing.d.ts.map +0 -1
- package/dist/functions/_shared/trigger-engine.d.ts +0 -140
- package/dist/functions/_shared/trigger-engine.d.ts.map +0 -1
- package/dist/functions/_shared/webhook-registration.d.ts +0 -81
- package/dist/functions/_shared/webhook-registration.d.ts.map +0 -1
- package/dist/functions/_shared/webhook-registry.d.ts +0 -41
- package/dist/functions/_shared/webhook-registry.d.ts.map +0 -1
- package/dist/functions/account-nodes.d.ts +0 -48
- package/dist/functions/account-nodes.d.ts.map +0 -1
- package/dist/functions/admin-data.d.ts +0 -178
- package/dist/functions/admin-data.d.ts.map +0 -1
- package/dist/functions/ai-agents.d.ts +0 -125
- package/dist/functions/ai-agents.d.ts.map +0 -1
- package/dist/functions/api-keys.d.ts +0 -140
- package/dist/functions/api-keys.d.ts.map +0 -1
- package/dist/functions/apps.d.ts +0 -163
- package/dist/functions/apps.d.ts.map +0 -1
- package/dist/functions/auth.d.ts +0 -74
- package/dist/functions/auth.d.ts.map +0 -1
- package/dist/functions/debug-auth.d.ts +0 -33
- package/dist/functions/debug-auth.d.ts.map +0 -1
- package/dist/functions/embeddings.d.ts +0 -205
- package/dist/functions/embeddings.d.ts.map +0 -1
- package/dist/functions/handler-registry.d.ts +0 -22
- package/dist/functions/handler-registry.d.ts.map +0 -1
- package/dist/functions/integration-routes.d.ts +0 -45
- package/dist/functions/integration-routes.d.ts.map +0 -1
- package/dist/functions/integrations.d.ts +0 -124
- package/dist/functions/integrations.d.ts.map +0 -1
- package/dist/functions/item-progress.d.ts +0 -41
- package/dist/functions/item-progress.d.ts.map +0 -1
- package/dist/functions/logs.d.ts +0 -162
- package/dist/functions/logs.d.ts.map +0 -1
- package/dist/functions/observability.d.ts +0 -123
- package/dist/functions/observability.d.ts.map +0 -1
- package/dist/functions/pipeline-executions.d.ts +0 -190
- package/dist/functions/pipeline-executions.d.ts.map +0 -1
- package/dist/functions/pipelines.d.ts +0 -171
- package/dist/functions/pipelines.d.ts.map +0 -1
- package/dist/functions/prompt-configs.d.ts +0 -125
- package/dist/functions/prompt-configs.d.ts.map +0 -1
- package/dist/functions/roles.d.ts +0 -118
- package/dist/functions/roles.d.ts.map +0 -1
- package/dist/functions/signal.d.ts +0 -73
- package/dist/functions/signal.d.ts.map +0 -1
- package/dist/functions/system-cron.d.ts +0 -65
- package/dist/functions/system-cron.d.ts.map +0 -1
- package/dist/functions/system.d.ts +0 -29
- package/dist/functions/system.d.ts.map +0 -1
- package/dist/functions/tests.d.ts +0 -28
- package/dist/functions/tests.d.ts.map +0 -1
- package/dist/functions/timers.d.ts +0 -139
- package/dist/functions/timers.d.ts.map +0 -1
- package/dist/functions/triggers.d.ts +0 -203
- package/dist/functions/triggers.d.ts.map +0 -1
- package/dist/functions/types.d.ts +0 -151
- package/dist/functions/types.d.ts.map +0 -1
- package/dist/src/types/types.d.ts +0 -364
- package/dist/src/types/types.d.ts.map +0 -1
- package/index.html +0 -13
- package/netlify.toml +0 -36
- package/package-project.json +0 -71
- package/scripts/ingest-chunks.cjs +0 -202
- package/scripts/kb-chunk-parser.cjs +0 -312
- package/scripts/kb-chunk-parser.ts +0 -330
- package/scripts/load-test-app-install.ts +0 -484
- package/scripts/netlify-dev-wrapper.sh +0 -22
- package/vitest.config.ts +0 -45
- /package/{scripts → .framework/scripts}/app-install-cli.ts +0 -0
- /package/{scripts → .framework/scripts}/build-manifest.sh +0 -0
- /package/{scripts → .framework/scripts}/verify-integrity.sh +0 -0
|
@@ -1,430 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module cli/commands/claim
|
|
4
|
-
* @audience installer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* `spine-framework claim` — Claim framework installation via Spine Portal.
|
|
9
|
-
*
|
|
10
|
-
* Engagement-based claim system that offers contextual prompts based on
|
|
11
|
-
* user activity. Tracks engagement signals and triggers claim prompts
|
|
12
|
-
* at value-realization moments.
|
|
13
|
-
*
|
|
14
|
-
* **Usage:**
|
|
15
|
-
* ```bash
|
|
16
|
-
* spine-framework claim # Interactive claim flow
|
|
17
|
-
* spine-framework claim --status # Show claim status only
|
|
18
|
-
* spine-framework claim --token <t> # Headless claim with token
|
|
19
|
-
* ```
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import type { Command } from 'commander'
|
|
23
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'
|
|
24
|
-
import { resolve } from 'path'
|
|
25
|
-
import { createHash, randomBytes } from 'crypto'
|
|
26
|
-
import * as readline from 'readline'
|
|
27
|
-
import { signalConfig, claimTriggers, engagementSignals, type EngagementData, type ClaimPrompt, type EngagementSignalType } from '../../config/signals.ts'
|
|
28
|
-
|
|
29
|
-
const PROJECT_ROOT = process.cwd()
|
|
30
|
-
const SPINE_DIR = resolve(PROJECT_ROOT, '.spine')
|
|
31
|
-
const SERIAL_FILE = resolve(SPINE_DIR, 'serial.json')
|
|
32
|
-
const ENGAGEMENT_FILE = resolve(SPINE_DIR, 'engagement.json')
|
|
33
|
-
|
|
34
|
-
// ─── SERIAL NUMBER MANAGEMENT ────────────────────────────────────────────────
|
|
35
|
-
|
|
36
|
-
export interface SerialData {
|
|
37
|
-
serial_number: string
|
|
38
|
-
salt: string
|
|
39
|
-
created_at: string
|
|
40
|
-
framework_version: string
|
|
41
|
-
account_id: string | null
|
|
42
|
-
machine_fingerprint: string
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function generateMachineFingerprint(): string {
|
|
46
|
-
const hostname = process.env.HOSTNAME || process.env.COMPUTERNAME || 'unknown'
|
|
47
|
-
const platform = process.platform
|
|
48
|
-
const arch = process.arch
|
|
49
|
-
const data = `${hostname}-${platform}-${arch}-${process.env.USER || 'unknown'}`
|
|
50
|
-
return createHash('sha256').update(data).digest('hex').substring(0, 16)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function generateSerialNumber(version: string, machineId: string, salt: string): string {
|
|
54
|
-
const timestamp = Date.now().toString()
|
|
55
|
-
const data = `spine-framework-${version}-${machineId}-${timestamp}-${salt}`
|
|
56
|
-
const hash = createHash('sha256').update(data).digest('hex')
|
|
57
|
-
return `spn_fw_${hash.substring(0, 16).toUpperCase()}`
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export function ensureSerialData(): SerialData {
|
|
61
|
-
if (existsSync(SERIAL_FILE)) {
|
|
62
|
-
try {
|
|
63
|
-
return JSON.parse(readFileSync(SERIAL_FILE, 'utf8')) as SerialData
|
|
64
|
-
} catch { /* fall through to create new */ }
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
// Create .spine directory if needed
|
|
68
|
-
if (!existsSync(SPINE_DIR)) {
|
|
69
|
-
mkdirSync(SPINE_DIR, { recursive: true })
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Generate new serial
|
|
73
|
-
const salt = randomBytes(32).toString('hex')
|
|
74
|
-
const machineFingerprint = generateMachineFingerprint()
|
|
75
|
-
const frameworkVersion = getFrameworkVersion()
|
|
76
|
-
const serialNumber = generateSerialNumber(frameworkVersion, machineFingerprint, salt)
|
|
77
|
-
|
|
78
|
-
const serialData: SerialData = {
|
|
79
|
-
serial_number: serialNumber,
|
|
80
|
-
salt,
|
|
81
|
-
created_at: new Date().toISOString(),
|
|
82
|
-
framework_version: frameworkVersion,
|
|
83
|
-
account_id: null,
|
|
84
|
-
machine_fingerprint: machineFingerprint,
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
writeFileSync(SERIAL_FILE, JSON.stringify(serialData, null, 2) + '\n', 'utf8')
|
|
88
|
-
return serialData
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function getFrameworkVersion(): string {
|
|
92
|
-
const pkgJsonPath = resolve(PROJECT_ROOT, 'package.json')
|
|
93
|
-
if (existsSync(pkgJsonPath)) {
|
|
94
|
-
try {
|
|
95
|
-
const pkg = JSON.parse(readFileSync(pkgJsonPath, 'utf8'))
|
|
96
|
-
return pkg.version || '0.0.0'
|
|
97
|
-
} catch { /* fall through */ }
|
|
98
|
-
}
|
|
99
|
-
return '0.0.0'
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ─── ENGAGEMENT TRACKING ─────────────────────────────────────────────────────
|
|
103
|
-
|
|
104
|
-
export function readEngagementData(): EngagementData | null {
|
|
105
|
-
if (!existsSync(ENGAGEMENT_FILE)) return null
|
|
106
|
-
try {
|
|
107
|
-
return JSON.parse(readFileSync(ENGAGEMENT_FILE, 'utf8')) as EngagementData
|
|
108
|
-
} catch {
|
|
109
|
-
return null
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export function writeEngagementData(data: EngagementData): void {
|
|
114
|
-
if (!existsSync(SPINE_DIR)) {
|
|
115
|
-
mkdirSync(SPINE_DIR, { recursive: true })
|
|
116
|
-
}
|
|
117
|
-
writeFileSync(ENGAGEMENT_FILE, JSON.stringify(data, null, 2) + '\n', 'utf8')
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
export function recordEngagementSignal(signalType: EngagementSignalType): void {
|
|
121
|
-
const serialData = ensureSerialData()
|
|
122
|
-
let engagement = readEngagementData()
|
|
123
|
-
|
|
124
|
-
if (!engagement) {
|
|
125
|
-
engagement = {
|
|
126
|
-
framework_install_serial: serialData.serial_number,
|
|
127
|
-
engagement_score: 0,
|
|
128
|
-
signals: [],
|
|
129
|
-
claim_status: 'anonymous',
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
const signalDef = engagementSignals[signalType]
|
|
134
|
-
const entry = {
|
|
135
|
-
type: signalType,
|
|
136
|
-
timestamp: new Date().toISOString(),
|
|
137
|
-
score: signalDef.score,
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Avoid duplicate signals of the same type within 1 hour
|
|
141
|
-
const oneHourAgo = Date.now() - 3600000
|
|
142
|
-
const recentSignal = engagement.signals.find(
|
|
143
|
-
s => s.type === signalType && new Date(s.timestamp).getTime() > oneHourAgo
|
|
144
|
-
)
|
|
145
|
-
if (recentSignal) {
|
|
146
|
-
return // Skip duplicate
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
engagement.signals.push(entry)
|
|
150
|
-
engagement.engagement_score += signalDef.score
|
|
151
|
-
writeEngagementData(engagement)
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
export function shouldOfferClaim(): ClaimPrompt | null {
|
|
155
|
-
const engagement = readEngagementData()
|
|
156
|
-
if (!engagement) return null
|
|
157
|
-
if (engagement.claim_status !== 'anonymous') return null
|
|
158
|
-
|
|
159
|
-
const score = engagement.engagement_score
|
|
160
|
-
|
|
161
|
-
if (score >= claimTriggers.power_user) {
|
|
162
|
-
return {
|
|
163
|
-
timing: 'power_user',
|
|
164
|
-
message: "You're a power user! Unlock advanced features and priority support by claiming your installation.",
|
|
165
|
-
}
|
|
166
|
-
} else if (score >= claimTriggers.ecosystem_engaged) {
|
|
167
|
-
return {
|
|
168
|
-
timing: 'ecosystem_engaged',
|
|
169
|
-
message: "You're expanding your toolkit! Claim your install for app-specific guides and community access.",
|
|
170
|
-
}
|
|
171
|
-
} else if (score >= claimTriggers.value_realized) {
|
|
172
|
-
return {
|
|
173
|
-
timing: 'value_realized',
|
|
174
|
-
message: 'Database connected! Claim your install to unlock production deployment guides and audit tools.',
|
|
175
|
-
}
|
|
176
|
-
} else if (score >= claimTriggers.gentle_nudge) {
|
|
177
|
-
return {
|
|
178
|
-
timing: 'gentle_nudge',
|
|
179
|
-
message: 'Framework started! Ready to explore the full ecosystem? Claim your install for docs and support.',
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
return null
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
// ─── SIGNAL SENDING ───────────────────────────────────────────────────────────
|
|
187
|
-
|
|
188
|
-
export async function sendFrameworkInstallSignal(): Promise<void> {
|
|
189
|
-
const serialData = ensureSerialData()
|
|
190
|
-
|
|
191
|
-
const signal = {
|
|
192
|
-
stage: 'installed' as const,
|
|
193
|
-
source: 'use' as const,
|
|
194
|
-
action_type: 'framework_install',
|
|
195
|
-
action_value: 2 as const,
|
|
196
|
-
instance_id: serialData.serial_number,
|
|
197
|
-
anonymous_id: `anon_${randomBytes(8).toString('hex')}`,
|
|
198
|
-
environment: (process.env.NODE_ENV === 'production' ? 'production' : 'dev') as 'dev' | 'production',
|
|
199
|
-
occurred_at: new Date().toISOString(),
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
try {
|
|
203
|
-
await fetch(`${signalConfig.endpoint}/api/signal`, {
|
|
204
|
-
method: 'POST',
|
|
205
|
-
headers: { 'Content-Type': 'application/json' },
|
|
206
|
-
body: JSON.stringify(signal),
|
|
207
|
-
})
|
|
208
|
-
} catch {
|
|
209
|
-
// Silent fail - signal endpoint is best-effort
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
export async function sendAppInstallSignal(packageName: string, _version: string): Promise<void> {
|
|
214
|
-
const serialData = ensureSerialData()
|
|
215
|
-
|
|
216
|
-
const signal = {
|
|
217
|
-
stage: 'installed' as const,
|
|
218
|
-
source: 'use' as const,
|
|
219
|
-
action_type: 'app_install',
|
|
220
|
-
action_value: 2 as const,
|
|
221
|
-
instance_id: serialData.serial_number,
|
|
222
|
-
anonymous_id: `anon_${randomBytes(8).toString('hex')}`,
|
|
223
|
-
action_description: packageName,
|
|
224
|
-
environment: (process.env.NODE_ENV === 'production' ? 'production' : 'dev') as 'dev' | 'production',
|
|
225
|
-
occurred_at: new Date().toISOString(),
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
try {
|
|
229
|
-
await fetch(`${signalConfig.endpoint}/api/signal`, {
|
|
230
|
-
method: 'POST',
|
|
231
|
-
headers: { 'Content-Type': 'application/json' },
|
|
232
|
-
body: JSON.stringify(signal),
|
|
233
|
-
})
|
|
234
|
-
} catch {
|
|
235
|
-
// Silent fail
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
// ─── CLAIM FLOW ───────────────────────────────────────────────────────────────
|
|
240
|
-
|
|
241
|
-
function promptQuestion(question: string): Promise<string> {
|
|
242
|
-
return new Promise(resolve => {
|
|
243
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
244
|
-
rl.question(question, answer => {
|
|
245
|
-
rl.close()
|
|
246
|
-
resolve(answer.trim())
|
|
247
|
-
})
|
|
248
|
-
})
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
async function openPortalClaim(serialNumber: string): Promise<void> {
|
|
252
|
-
const claimUrl = `https://portal.spine-framework.com/claim?serial=${serialNumber}`
|
|
253
|
-
|
|
254
|
-
// Try to open browser
|
|
255
|
-
const platform = process.platform
|
|
256
|
-
const cmd = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open'
|
|
257
|
-
|
|
258
|
-
console.log(`\n🌐 Opening ${claimUrl}`)
|
|
259
|
-
console.log(' Create your account or log in to claim this installation.\n')
|
|
260
|
-
|
|
261
|
-
try {
|
|
262
|
-
const { execSync } = await import('child_process')
|
|
263
|
-
execSync(`${cmd} "${claimUrl}"`, { stdio: 'ignore' })
|
|
264
|
-
} catch {
|
|
265
|
-
console.log(` Could not open browser automatically.`)
|
|
266
|
-
console.log(` Please visit the URL manually.\n`)
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
async function tokenClaimFlow(serialNumber: string): Promise<void> {
|
|
271
|
-
console.log('\n🔑 Token-Based Claim')
|
|
272
|
-
console.log(' 1. Visit https://portal.spine-framework.com/settings/claim')
|
|
273
|
-
console.log(' 2. Copy your claim token\n')
|
|
274
|
-
|
|
275
|
-
const token = await promptQuestion(' Enter your claim token: ')
|
|
276
|
-
|
|
277
|
-
if (!token) {
|
|
278
|
-
console.log('\n ❌ No token provided. Claim cancelled.')
|
|
279
|
-
return
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
// Validate token with portal API
|
|
283
|
-
try {
|
|
284
|
-
const response = await fetch(`${signalConfig.endpoint}/api/claim`, {
|
|
285
|
-
method: 'POST',
|
|
286
|
-
headers: { 'Content-Type': 'application/json' },
|
|
287
|
-
body: JSON.stringify({
|
|
288
|
-
install_serial: serialNumber,
|
|
289
|
-
user_token: token,
|
|
290
|
-
}),
|
|
291
|
-
})
|
|
292
|
-
|
|
293
|
-
if (!response.ok) {
|
|
294
|
-
console.log('\n ❌ Invalid token or claim failed. Please try again.')
|
|
295
|
-
return
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
const result = await response.json() as { success: boolean; account_id?: string }
|
|
299
|
-
|
|
300
|
-
if (result.success) {
|
|
301
|
-
// Update local files
|
|
302
|
-
const serialData = ensureSerialData()
|
|
303
|
-
serialData.account_id = result.account_id || null
|
|
304
|
-
writeFileSync(SERIAL_FILE, JSON.stringify(serialData, null, 2) + '\n', 'utf8')
|
|
305
|
-
|
|
306
|
-
const engagement = readEngagementData()
|
|
307
|
-
if (engagement) {
|
|
308
|
-
engagement.claim_status = 'claimed'
|
|
309
|
-
engagement.account_id = result.account_id
|
|
310
|
-
writeEngagementData(engagement)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
console.log('\n ✅ Installation claimed successfully!')
|
|
314
|
-
console.log(' Welcome to the Spine ecosystem. 🎉\n')
|
|
315
|
-
} else {
|
|
316
|
-
console.log('\n ❌ Claim failed. Please check your token and try again.')
|
|
317
|
-
}
|
|
318
|
-
} catch {
|
|
319
|
-
console.log('\n ⚠️ Could not connect to portal. Please try again later.')
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
async function showClaimStatus(): Promise<void> {
|
|
324
|
-
const serialData = existsSync(SERIAL_FILE) ? JSON.parse(readFileSync(SERIAL_FILE, 'utf8')) as SerialData : null
|
|
325
|
-
const engagement = readEngagementData()
|
|
326
|
-
|
|
327
|
-
console.log('\n📊 Claim Status\n')
|
|
328
|
-
|
|
329
|
-
if (!serialData) {
|
|
330
|
-
console.log(' No installation serial found.')
|
|
331
|
-
console.log(' Run any spine-framework command to initialize.\n')
|
|
332
|
-
return
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
console.log(` Serial Number: ${serialData.serial_number}`)
|
|
336
|
-
console.log(` Framework Version: ${serialData.framework_version}`)
|
|
337
|
-
console.log(` Created: ${new Date(serialData.created_at).toLocaleDateString()}`)
|
|
338
|
-
|
|
339
|
-
if (engagement) {
|
|
340
|
-
console.log(`\n Engagement Score: ${engagement.engagement_score}`)
|
|
341
|
-
console.log(` Signals Recorded: ${engagement.signals.length}`)
|
|
342
|
-
console.log(` Claim Status: ${engagement.claim_status}`)
|
|
343
|
-
|
|
344
|
-
if (engagement.claim_status === 'anonymous') {
|
|
345
|
-
const prompt = shouldOfferClaim()
|
|
346
|
-
if (prompt) {
|
|
347
|
-
console.log(`\n 💡 ${prompt.message}`)
|
|
348
|
-
}
|
|
349
|
-
} else if (engagement.account_id) {
|
|
350
|
-
console.log(` Account ID: ${engagement.account_id}`)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
console.log('')
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
async function runClaimFlow(options: { status: boolean; token?: string }): Promise<void> {
|
|
358
|
-
// Ensure serial exists
|
|
359
|
-
const serialData = ensureSerialData()
|
|
360
|
-
|
|
361
|
-
// Send framework install signal on first claim check
|
|
362
|
-
recordEngagementSignal('framework_initialized')
|
|
363
|
-
await sendFrameworkInstallSignal()
|
|
364
|
-
|
|
365
|
-
if (options.status) {
|
|
366
|
-
await showClaimStatus()
|
|
367
|
-
return
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
// Check if already claimed
|
|
371
|
-
const engagement = readEngagementData()
|
|
372
|
-
if (engagement?.claim_status === 'claimed') {
|
|
373
|
-
console.log('\n✅ This installation is already claimed.')
|
|
374
|
-
console.log(` Account: ${engagement.account_id}\n`)
|
|
375
|
-
return
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
console.log('\n🔐 Claim Your Spine Installation\n')
|
|
379
|
-
console.log(` Serial: ${serialData.serial_number}`)
|
|
380
|
-
console.log('\n Claiming unlocks:')
|
|
381
|
-
console.log(' • Documentation and playbooks')
|
|
382
|
-
console.log(' • App packs and marketplace')
|
|
383
|
-
console.log(' • Support and community access')
|
|
384
|
-
console.log(' • Audit and compliance tools\n')
|
|
385
|
-
|
|
386
|
-
if (options.token) {
|
|
387
|
-
await tokenClaimFlow(serialData.serial_number)
|
|
388
|
-
return
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
console.log(' Choose a claim method:')
|
|
392
|
-
console.log(' 1. Portal flow (opens browser) - RECOMMENDED')
|
|
393
|
-
console.log(' 2. Token flow (paste token from portal)')
|
|
394
|
-
console.log(' 3. Check status only\n')
|
|
395
|
-
|
|
396
|
-
const choice = await promptQuestion(' Select (1-3): ')
|
|
397
|
-
|
|
398
|
-
switch (choice) {
|
|
399
|
-
case '1':
|
|
400
|
-
await openPortalClaim(serialData.serial_number)
|
|
401
|
-
break
|
|
402
|
-
case '2':
|
|
403
|
-
await tokenClaimFlow(serialData.serial_number)
|
|
404
|
-
break
|
|
405
|
-
case '3':
|
|
406
|
-
await showClaimStatus()
|
|
407
|
-
break
|
|
408
|
-
default:
|
|
409
|
-
console.log('\n Invalid choice. Run `spine-framework claim` to try again.\n')
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// ─── COMMAND REGISTRATION ───────────────────────────────────────────────────
|
|
414
|
-
|
|
415
|
-
export function registerClaimCommands(program: Command): void {
|
|
416
|
-
program
|
|
417
|
-
.command('claim')
|
|
418
|
-
.description('Claim framework installation via Spine Portal')
|
|
419
|
-
.option('--status', 'Show claim status only', false)
|
|
420
|
-
.option('--token <token>', 'Headless claim with token')
|
|
421
|
-
.action(async (opts) => {
|
|
422
|
-
try {
|
|
423
|
-
await runClaimFlow(opts)
|
|
424
|
-
} catch (err: any) {
|
|
425
|
-
console.error('Error:', err.message)
|
|
426
|
-
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
427
|
-
process.exit(1)
|
|
428
|
-
}
|
|
429
|
-
})
|
|
430
|
-
}
|
|
@@ -1,219 +0,0 @@
|
|
|
1
|
-
/// <reference types="node" />
|
|
2
|
-
/**
|
|
3
|
-
* @module cli/commands/init
|
|
4
|
-
* @audience installer
|
|
5
|
-
* @layer cli
|
|
6
|
-
* @stability stable
|
|
7
|
-
*
|
|
8
|
-
* `spine-framework init` — Write .env and scaffold the custom/ workspace.
|
|
9
|
-
*
|
|
10
|
-
* Intentionally does NOT touch the database — run `spine-framework migrate`
|
|
11
|
-
* after init to apply SQL migrations via a direct Postgres connection.
|
|
12
|
-
*
|
|
13
|
-
* **Usage:**
|
|
14
|
-
* ```bash
|
|
15
|
-
* spine-framework init --url https://xyz.supabase.co --anon-key eyJ... --service-role-key eyJ...
|
|
16
|
-
* spine-framework migrate --db-password <password>
|
|
17
|
-
* ```
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import type { Command } from 'commander'
|
|
21
|
-
import { existsSync, mkdirSync, writeFileSync, cpSync, readFileSync } from 'fs'
|
|
22
|
-
import { resolve, dirname } from 'path'
|
|
23
|
-
import { fileURLToPath } from 'url'
|
|
24
|
-
import { execSync } from 'child_process'
|
|
25
|
-
|
|
26
|
-
const __filename = fileURLToPath(import.meta.url)
|
|
27
|
-
const __dirname = dirname(__filename)
|
|
28
|
-
const PROJECT_ROOT = process.cwd()
|
|
29
|
-
// Package root: node_modules/spine-framework/ — 3 levels up from .framework/cli/commands/
|
|
30
|
-
const PACKAGE_ROOT = resolve(__dirname, '../../..')
|
|
31
|
-
|
|
32
|
-
interface InitOptions {
|
|
33
|
-
dryRun: boolean
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function scaffoldCustomWorkspace(dryRun: boolean): void {
|
|
37
|
-
const dirs = [
|
|
38
|
-
'custom/apps',
|
|
39
|
-
'custom/functions',
|
|
40
|
-
'custom/components',
|
|
41
|
-
'custom/migrations',
|
|
42
|
-
'custom/tests',
|
|
43
|
-
]
|
|
44
|
-
|
|
45
|
-
for (const dir of dirs) {
|
|
46
|
-
const fullPath = resolve(PROJECT_ROOT, dir)
|
|
47
|
-
if (existsSync(fullPath)) {
|
|
48
|
-
console.log(` ⏭️ ${dir}/ already exists`)
|
|
49
|
-
} else if (dryRun) {
|
|
50
|
-
console.log(` [dry-run] Would create: ${dir}/`)
|
|
51
|
-
} else {
|
|
52
|
-
mkdirSync(fullPath, { recursive: true })
|
|
53
|
-
writeFileSync(resolve(fullPath, '.gitkeep'), '')
|
|
54
|
-
console.log(` ✓ Created ${dir}/`)
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function writeEnvFile(dryRun: boolean): void {
|
|
60
|
-
const envPath = resolve(PROJECT_ROOT, '.env')
|
|
61
|
-
|
|
62
|
-
if (existsSync(envPath)) {
|
|
63
|
-
console.log(' ⏭️ .env already exists, skipping')
|
|
64
|
-
return
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const envContent = [
|
|
68
|
-
'# ─── Supabase ────────────────────────────────────────────────────────────────',
|
|
69
|
-
'# Get these from: https://supabase.com/dashboard/project/_/settings/api',
|
|
70
|
-
'SUPABASE_URL=https://your-project.supabase.co',
|
|
71
|
-
'SUPABASE_ANON_KEY=your-anon-key',
|
|
72
|
-
'SUPABASE_SERVICE_ROLE_KEY=your-service-role-key',
|
|
73
|
-
'',
|
|
74
|
-
'# DB password for running migrations (direct Postgres connection)',
|
|
75
|
-
'# Get from: https://supabase.com/dashboard/project/_/settings/database',
|
|
76
|
-
'SUPABASE_DB_PASSWORD=your-db-password',
|
|
77
|
-
'',
|
|
78
|
-
'# ─── Vite / Frontend ─────────────────────────────────────────────────────────',
|
|
79
|
-
'VITE_SUPABASE_URL=https://your-project.supabase.co',
|
|
80
|
-
'VITE_SUPABASE_ANON_KEY=your-anon-key',
|
|
81
|
-
'VITE_APP_NAME=Spine',
|
|
82
|
-
'',
|
|
83
|
-
'# ─── OpenAI ──────────────────────────────────────────────────────────────────',
|
|
84
|
-
'OPENAI_API_KEY=your-openai-api-key',
|
|
85
|
-
'',
|
|
86
|
-
'# ─── App config ──────────────────────────────────────────────────────────────',
|
|
87
|
-
'DB_SCHEMA=public',
|
|
88
|
-
].join('\n') + '\n'
|
|
89
|
-
|
|
90
|
-
if (dryRun) {
|
|
91
|
-
console.log(' [dry-run] Would write .env with placeholder values')
|
|
92
|
-
return
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
writeFileSync(envPath, envContent, 'utf8')
|
|
96
|
-
console.log(' ✓ Created .env')
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function copyFrameworkFiles(dryRun: boolean): void {
|
|
100
|
-
// Directories and files to copy from the package root into the consumer project root
|
|
101
|
-
const items = [
|
|
102
|
-
{ src: '.framework', dest: '.framework' },
|
|
103
|
-
{ src: 'bin', dest: 'bin' },
|
|
104
|
-
{ src: 'config', dest: 'config' },
|
|
105
|
-
{ src: 'scripts', dest: 'scripts' },
|
|
106
|
-
{ src: 'netlify.toml', dest: 'netlify.toml' },
|
|
107
|
-
{ src: 'vitest.config.ts', dest: 'vitest.config.ts' },
|
|
108
|
-
{ src: 'index.html', dest: 'index.html' },
|
|
109
|
-
{ src: 'package-project.json', dest: 'package.json', template: true },
|
|
110
|
-
{ src: 'STRUCTURE.md', dest: 'STRUCTURE.md' },
|
|
111
|
-
{ src: 'README.md', dest: 'README.md' },
|
|
112
|
-
{ src: 'LICENSE.md', dest: 'LICENSE.md' },
|
|
113
|
-
{ src: 'CHANGELOG.md', dest: 'CHANGELOG.md' },
|
|
114
|
-
]
|
|
115
|
-
|
|
116
|
-
// Resolve current framework version from the installed package
|
|
117
|
-
let frameworkVersion = '0.1.0'
|
|
118
|
-
try {
|
|
119
|
-
const fwPkg = JSON.parse(readFileSync(resolve(PACKAGE_ROOT, 'package.json'), 'utf8'))
|
|
120
|
-
frameworkVersion = fwPkg.version || frameworkVersion
|
|
121
|
-
} catch { /* fall through */ }
|
|
122
|
-
|
|
123
|
-
for (const item of items as Array<{ src: string; dest: string; template?: boolean }>) {
|
|
124
|
-
const src = resolve(PACKAGE_ROOT, item.src)
|
|
125
|
-
const dest = resolve(PROJECT_ROOT, item.dest)
|
|
126
|
-
|
|
127
|
-
if (!existsSync(src)) {
|
|
128
|
-
console.log(` ⏭️ ${item.src} not found in package, skipping`)
|
|
129
|
-
continue
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (existsSync(dest)) {
|
|
133
|
-
// For package.json: only skip if it already has scripts (i.e. it's a real project package.json)
|
|
134
|
-
// A bare stub created by `npm install` has no scripts and should be overwritten
|
|
135
|
-
if (item.template) {
|
|
136
|
-
try {
|
|
137
|
-
const existing = JSON.parse(readFileSync(dest, 'utf8'))
|
|
138
|
-
if (existing.scripts && Object.keys(existing.scripts).length > 0) {
|
|
139
|
-
console.log(` ⏭️ ${item.dest} already exists, skipping`)
|
|
140
|
-
continue
|
|
141
|
-
}
|
|
142
|
-
// Fall through — bare stub, overwrite it
|
|
143
|
-
} catch {
|
|
144
|
-
// Unparseable — overwrite it
|
|
145
|
-
}
|
|
146
|
-
} else {
|
|
147
|
-
console.log(` ⏭️ ${item.dest} already exists, skipping`)
|
|
148
|
-
continue
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
if (dryRun) {
|
|
153
|
-
console.log(` [dry-run] Would copy: ${item.src} → ./${item.dest}`)
|
|
154
|
-
continue
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (item.template) {
|
|
158
|
-
// Stamp the current framework version into the project package.json
|
|
159
|
-
const templateContent = JSON.parse(readFileSync(src, 'utf8'))
|
|
160
|
-
if (templateContent.dependencies?.['spine-framework']) {
|
|
161
|
-
templateContent.dependencies['spine-framework'] = `^${frameworkVersion}`
|
|
162
|
-
}
|
|
163
|
-
writeFileSync(dest, JSON.stringify(templateContent, null, 2) + '\n', 'utf8')
|
|
164
|
-
} else {
|
|
165
|
-
cpSync(src, dest, { recursive: true })
|
|
166
|
-
}
|
|
167
|
-
console.log(` ✓ Copied ${item.dest}`)
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
async function initCommand(options: InitOptions): Promise<void> {
|
|
172
|
-
console.log('\n🚀 Spine Framework — Init\n')
|
|
173
|
-
|
|
174
|
-
// Step 1: Copy framework files to project root
|
|
175
|
-
console.log('📦 Step 1: Installing framework files...')
|
|
176
|
-
copyFrameworkFiles(options.dryRun)
|
|
177
|
-
|
|
178
|
-
// Step 2: Install dev dependencies (vite, netlify-cli)
|
|
179
|
-
if (!options.dryRun) {
|
|
180
|
-
console.log('\n📦 Step 2: Installing dev dependencies...')
|
|
181
|
-
try {
|
|
182
|
-
execSync('npm install', { stdio: 'inherit', cwd: PROJECT_ROOT })
|
|
183
|
-
console.log(' ✓ Dev dependencies installed')
|
|
184
|
-
} catch (err) {
|
|
185
|
-
console.log(' ⚠️ npm install failed — run manually: npm install')
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// Step 3: Scaffold custom workspace
|
|
190
|
-
console.log('\n📁 Step 3: Scaffolding custom workspace...')
|
|
191
|
-
scaffoldCustomWorkspace(options.dryRun)
|
|
192
|
-
|
|
193
|
-
// Step 4: Write .env with placeholders
|
|
194
|
-
console.log('\n🔑 Step 4: Writing environment configuration...')
|
|
195
|
-
writeEnvFile(options.dryRun)
|
|
196
|
-
|
|
197
|
-
console.log('\n✅ Project initialized!')
|
|
198
|
-
console.log('\n Next steps:')
|
|
199
|
-
console.log(' 1. Fill in your Supabase credentials in .env')
|
|
200
|
-
console.log(' (get them from https://supabase.com/dashboard/project/_/settings/api)')
|
|
201
|
-
console.log(' 2. Run: npx spine-framework migrate')
|
|
202
|
-
console.log(' (ensure SUPABASE_DB_PASSWORD is set in .env)')
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function registerInitCommands(program: Command) {
|
|
206
|
-
program
|
|
207
|
-
.command('init')
|
|
208
|
-
.description('Install framework files, scaffold custom/ workspace, and write .env template')
|
|
209
|
-
.option('--dry-run', 'Show what would happen without making changes', false)
|
|
210
|
-
.action(async (opts) => {
|
|
211
|
-
try {
|
|
212
|
-
await initCommand(opts)
|
|
213
|
-
} catch (err: any) {
|
|
214
|
-
console.error('Error:', err.message)
|
|
215
|
-
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
216
|
-
process.exit(1)
|
|
217
|
-
}
|
|
218
|
-
})
|
|
219
|
-
}
|