spine-framework 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.framework/README.md +129 -0
- package/.framework/cli/bin.cjs +14 -0
- package/.framework/cli/commands/agents.ts +153 -0
- package/.framework/cli/commands/auth.ts +94 -0
- package/.framework/cli/commands/create-app.ts +185 -0
- package/.framework/cli/commands/dev.ts +295 -0
- package/.framework/cli/commands/doctor.ts +442 -0
- package/.framework/cli/commands/generate.ts +332 -0
- package/.framework/cli/commands/init.ts +272 -0
- package/.framework/cli/commands/install-app.ts +391 -0
- package/.framework/cli/commands/items.ts +253 -0
- package/.framework/cli/commands/migrations.ts +141 -0
- package/.framework/cli/commands/pipelines.ts +166 -0
- package/.framework/cli/commands/status.ts +197 -0
- package/.framework/cli/commands/system.ts +184 -0
- package/.framework/cli/commands/test.ts +227 -0
- package/.framework/cli/commands/uninstall-app.ts +166 -0
- package/.framework/cli/context.ts +268 -0
- package/.framework/cli/env-loader.ts +36 -0
- package/.framework/cli/index.ts +106 -0
- package/.framework/cli/welcome.cjs +45 -0
- package/.framework/docs/API.md +384 -0
- package/.framework/docs/STABILITY.md +52 -0
- package/.framework/docs/admin-routes.md +76 -0
- package/.framework/docs/api-docs-progress.md +38 -0
- package/.framework/docs/api-governance.md +146 -0
- package/.framework/docs/api-testing-results.md +212 -0
- package/.framework/docs/apis/admin-configs.md +567 -0
- package/.framework/docs/apis/admin-data.md +272 -0
- package/.framework/docs/apis/index.md +231 -0
- package/.framework/docs/apis/internal.md +295 -0
- package/.framework/docs/apis/runtime.md +537 -0
- package/.framework/docs/assembly-launch-guide.md +138 -0
- package/.framework/docs/audit-results.md +590 -0
- package/.framework/docs/authorization-model.md +170 -0
- package/.framework/docs/db-api-inventory.md +95 -0
- package/.framework/docs/examples/custom-app/README.md +77 -0
- package/.framework/docs/examples/custom-function/README.md +27 -0
- package/.framework/docs/examples/custom-function/handler.ts +48 -0
- package/.framework/docs/examples/custom-webhook/README.md +68 -0
- package/.framework/docs/gap-remediation-backlog.md +103 -0
- package/.framework/docs/guides/cli-guide.md +224 -0
- package/.framework/docs/guides/getting-started.md +103 -0
- package/.framework/docs/guides/import-guide.md +193 -0
- package/.framework/docs/guides/testing-guide.md +229 -0
- package/.framework/docs/permission-examples.md +326 -0
- package/.framework/docs/ui-adoption-verification.md +111 -0
- package/.framework/docs/ui-api-coverage.md +84 -0
- package/.framework/docs/v2-compatibility-audit.md +228 -0
- package/.framework/functions/.gitkeep +1 -0
- package/.framework/functions/_shared/agent-runner.ts +1097 -0
- package/.framework/functions/_shared/app-manifest.ts +184 -0
- package/.framework/functions/_shared/audit.ts +150 -0
- package/.framework/functions/_shared/db.ts +174 -0
- package/.framework/functions/_shared/index.ts +382 -0
- package/.framework/functions/_shared/middleware.ts +490 -0
- package/.framework/functions/_shared/permissions.ts +1325 -0
- package/.framework/functions/_shared/pipeline-runner.ts +731 -0
- package/.framework/functions/_shared/principal.ts +760 -0
- package/.framework/functions/_shared/schema-utils.ts +967 -0
- package/.framework/functions/_shared/testing.ts +258 -0
- package/.framework/functions/_shared/trigger-engine.ts +425 -0
- package/.framework/functions/_shared/webhook-registration.ts +168 -0
- package/.framework/functions/_shared/webhook-registry.ts +129 -0
- package/.framework/functions/account-nodes.ts +111 -0
- package/.framework/functions/admin-data.ts +606 -0
- package/.framework/functions/ai-agents.ts +323 -0
- package/.framework/functions/api-keys.ts +376 -0
- package/.framework/functions/apps.ts +483 -0
- package/.framework/functions/auth.ts +196 -0
- package/.framework/functions/debug-auth.ts +107 -0
- package/.framework/functions/embeddings.ts +556 -0
- package/.framework/functions/integration-routes.ts +523 -0
- package/.framework/functions/integrations.ts +319 -0
- package/.framework/functions/item-progress.ts +272 -0
- package/.framework/functions/logs.ts +438 -0
- package/.framework/functions/observability.ts +275 -0
- package/.framework/functions/pipeline-executions.ts +494 -0
- package/.framework/functions/pipelines.ts +485 -0
- package/.framework/functions/prompt-configs.ts +339 -0
- package/.framework/functions/roles.ts +387 -0
- package/.framework/functions/system-cron.ts +742 -0
- package/.framework/functions/system.ts +323 -0
- package/.framework/functions/tests.ts +119 -0
- package/.framework/functions/timers.ts +357 -0
- package/.framework/functions/triggers.ts +563 -0
- package/.framework/functions/types.ts +604 -0
- package/.framework/migrations/000_foundation.sql +1256 -0
- package/.framework/migrations/001_seed.sql +92 -0
- package/.framework/migrations/002_seed_constraints.sql +13 -0
- package/.framework/migrations/003_auth_user_trigger.sql +59 -0
- package/.framework/src/App.tsx +126 -0
- package/.framework/src/apps/admin/index.tsx +173 -0
- package/.framework/src/components/AppWrapper.tsx +56 -0
- package/.framework/src/components/CustomAppLoader.tsx +116 -0
- package/.framework/src/components/admin/AdminListPage.tsx +151 -0
- package/.framework/src/components/admin/AdminSidebar.tsx +166 -0
- package/.framework/src/components/admin/AdminStatsCard.tsx +62 -0
- package/.framework/src/components/admin/SortableTableHeader.tsx +42 -0
- package/.framework/src/components/app-shell/GenericAppShell.tsx +181 -0
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +200 -0
- package/.framework/src/components/app-shell/GenericListPage.tsx +116 -0
- package/.framework/src/components/app-sidebar.tsx +228 -0
- package/.framework/src/components/auth/ProtectedRoute.tsx +88 -0
- package/.framework/src/components/layout/AppShell.tsx +91 -0
- package/.framework/src/components/layout/Header.tsx +88 -0
- package/.framework/src/components/layout/Layout.tsx +95 -0
- package/.framework/src/components/layout/Sidebar.tsx +329 -0
- package/.framework/src/components/runtime/DataDetailHeader.tsx +77 -0
- package/.framework/src/components/runtime/DataDetailPage.tsx +171 -0
- package/.framework/src/components/runtime/DataFilters.tsx +91 -0
- package/.framework/src/components/runtime/DataHeader.tsx +68 -0
- package/.framework/src/components/runtime/DataListPage.tsx +124 -0
- package/.framework/src/components/runtime/DataStats.tsx +70 -0
- package/.framework/src/components/runtime/DataTable.tsx +174 -0
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +134 -0
- package/.framework/src/components/runtime/index.ts +18 -0
- package/.framework/src/components/search-form.tsx +29 -0
- package/.framework/src/components/shared/AgentView.tsx +213 -0
- package/.framework/src/components/shared/FieldRenderer.tsx +478 -0
- package/.framework/src/components/shared/SchemaFields.tsx +226 -0
- package/.framework/src/components/ui/DataTable.tsx +343 -0
- package/.framework/src/components/ui/Form.tsx +281 -0
- package/.framework/src/components/ui/ItemCard.tsx +296 -0
- package/.framework/src/components/ui/ItemListView.tsx +308 -0
- package/.framework/src/components/ui/LoadingSpinner.tsx +52 -0
- package/.framework/src/components/ui/Modal.tsx +61 -0
- package/.framework/src/components/ui/RichTextEditor.tsx +210 -0
- package/.framework/src/components/ui/accordion.tsx +82 -0
- package/.framework/src/components/ui/alert-dialog.tsx +197 -0
- package/.framework/src/components/ui/alert.tsx +76 -0
- package/.framework/src/components/ui/aspect-ratio.tsx +11 -0
- package/.framework/src/components/ui/avatar.tsx +110 -0
- package/.framework/src/components/ui/badge.tsx +49 -0
- package/.framework/src/components/ui/breadcrumb.tsx +122 -0
- package/.framework/src/components/ui/button-group.tsx +83 -0
- package/.framework/src/components/ui/button.tsx +65 -0
- package/.framework/src/components/ui/calendar.tsx +222 -0
- package/.framework/src/components/ui/card.tsx +100 -0
- package/.framework/src/components/ui/carousel.tsx +240 -0
- package/.framework/src/components/ui/chart.tsx +373 -0
- package/.framework/src/components/ui/checkbox.tsx +31 -0
- package/.framework/src/components/ui/collapsible.tsx +33 -0
- package/.framework/src/components/ui/combobox.tsx +299 -0
- package/.framework/src/components/ui/command.tsx +193 -0
- package/.framework/src/components/ui/context-menu.tsx +261 -0
- package/.framework/src/components/ui/dialog.tsx +165 -0
- package/.framework/src/components/ui/direction.tsx +22 -0
- package/.framework/src/components/ui/drawer.tsx +132 -0
- package/.framework/src/components/ui/dropdown-menu.tsx +269 -0
- package/.framework/src/components/ui/empty.tsx +104 -0
- package/.framework/src/components/ui/field.tsx +238 -0
- package/.framework/src/components/ui/hover-card.tsx +42 -0
- package/.framework/src/components/ui/input-group.tsx +153 -0
- package/.framework/src/components/ui/input-otp.tsx +87 -0
- package/.framework/src/components/ui/input.tsx +19 -0
- package/.framework/src/components/ui/item.tsx +196 -0
- package/.framework/src/components/ui/kbd.tsx +26 -0
- package/.framework/src/components/ui/label.tsx +22 -0
- package/.framework/src/components/ui/menubar.tsx +277 -0
- package/.framework/src/components/ui/native-select.tsx +61 -0
- package/.framework/src/components/ui/navigation-menu.tsx +164 -0
- package/.framework/src/components/ui/pagination.tsx +129 -0
- package/.framework/src/components/ui/popover.tsx +87 -0
- package/.framework/src/components/ui/progress.tsx +31 -0
- package/.framework/src/components/ui/radio-group.tsx +42 -0
- package/.framework/src/components/ui/resizable.tsx +50 -0
- package/.framework/src/components/ui/scroll-area.tsx +53 -0
- package/.framework/src/components/ui/select.tsx +195 -0
- package/.framework/src/components/ui/separator.tsx +26 -0
- package/.framework/src/components/ui/sheet.tsx +145 -0
- package/.framework/src/components/ui/sidebar.tsx +706 -0
- package/.framework/src/components/ui/skeleton.tsx +13 -0
- package/.framework/src/components/ui/slider.tsx +59 -0
- package/.framework/src/components/ui/sonner.tsx +47 -0
- package/.framework/src/components/ui/spinner.tsx +10 -0
- package/.framework/src/components/ui/switch.tsx +33 -0
- package/.framework/src/components/ui/table-primitives.tsx +141 -0
- package/.framework/src/components/ui/table.tsx +114 -0
- package/.framework/src/components/ui/tabs.tsx +90 -0
- package/.framework/src/components/ui/textarea.tsx +18 -0
- package/.framework/src/components/ui/toggle-group.tsx +89 -0
- package/.framework/src/components/ui/toggle.tsx +45 -0
- package/.framework/src/components/ui/tooltip.tsx +57 -0
- package/.framework/src/contexts/AppContext.tsx +133 -0
- package/.framework/src/contexts/AuthContext.tsx +371 -0
- package/.framework/src/hooks/use-mobile.ts +19 -0
- package/.framework/src/hooks/useApi.ts +526 -0
- package/.framework/src/hooks/useApps.ts +114 -0
- package/.framework/src/hooks/useEntityList.ts +190 -0
- package/.framework/src/hooks/useEntityRecord.ts +308 -0
- package/.framework/src/hooks/useForm.ts +307 -0
- package/.framework/src/hooks/useListSchema.ts +264 -0
- package/.framework/src/hooks/useSchemaRecord.ts +223 -0
- package/.framework/src/index.css +128 -0
- package/.framework/src/lib/api.ts +156 -0
- package/.framework/src/lib/supabase.ts +94 -0
- package/.framework/src/lib/utils.ts +317 -0
- package/.framework/src/main.tsx +27 -0
- package/.framework/src/pages/DashboardPage.tsx +181 -0
- package/.framework/src/pages/NotFoundPage.tsx +39 -0
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +161 -0
- package/.framework/src/pages/admin/AIAgentsPage.tsx +318 -0
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +199 -0
- package/.framework/src/pages/admin/APIKeysPage.tsx +303 -0
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +523 -0
- package/.framework/src/pages/admin/AppDetailPage.tsx +493 -0
- package/.framework/src/pages/admin/AppsPage.tsx +355 -0
- package/.framework/src/pages/admin/DesignedPage.tsx +491 -0
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +534 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +424 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +176 -0
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +109 -0
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +402 -0
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +187 -0
- package/.framework/src/pages/admin/IntegrationsPage.tsx +301 -0
- package/.framework/src/pages/admin/LogsPage.tsx +283 -0
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +85 -0
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +470 -0
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +183 -0
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +279 -0
- package/.framework/src/pages/admin/PipelinesPage.tsx +390 -0
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +299 -0
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +292 -0
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +434 -0
- package/.framework/src/pages/admin/RoleDetailPage.tsx +273 -0
- package/.framework/src/pages/admin/RolesPage.tsx +292 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +61 -0
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +588 -0
- package/.framework/src/pages/admin/SimpleDashboard.tsx +387 -0
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +172 -0
- package/.framework/src/pages/admin/TestingDashboard.tsx +257 -0
- package/.framework/src/pages/admin/TimerDetailPage.tsx +151 -0
- package/.framework/src/pages/admin/TimersPage.tsx +376 -0
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +149 -0
- package/.framework/src/pages/admin/TriggersPage.tsx +381 -0
- package/.framework/src/pages/admin/TypeDetailPage.tsx +694 -0
- package/.framework/src/pages/admin/TypesPage.tsx +295 -0
- package/.framework/src/pages/auth/LoginPage.tsx +188 -0
- package/.framework/src/pages/auth/RegisterPage.tsx +163 -0
- package/.framework/src/pages/spine-framework/APIPage.tsx +17 -0
- package/.framework/src/pages/spine-framework/CLIPage.tsx +25 -0
- package/.framework/src/types/auth.ts +125 -0
- package/.framework/src/types/types.ts +407 -0
- package/STRUCTURE.md +150 -0
- package/config/components.json +25 -0
- package/config/deno.lock +108 -0
- package/config/package-lock.json +17183 -0
- package/config/postcss.config.cjs +10 -0
- package/config/tailwind.config.cjs +78 -0
- package/config/tsconfig.build.json +32 -0
- package/config/tsconfig.cli.json +18 -0
- package/config/tsconfig.json +41 -0
- package/config/tsconfig.node.json +17 -0
- package/config/tsconfig.node.tsbuildinfo +1 -0
- package/config/tsconfig.tsbuildinfo +1 -0
- package/config/typedoc.json +16 -0
- package/config/vite.config.d.ts +2 -0
- package/config/vite.config.ts +72 -0
- package/dist/cli/commands/agents.d.ts +39 -0
- package/dist/cli/commands/agents.d.ts.map +1 -0
- package/dist/cli/commands/auth.d.ts +36 -0
- package/dist/cli/commands/auth.d.ts.map +1 -0
- package/dist/cli/commands/create-app.d.ts +23 -0
- package/dist/cli/commands/create-app.d.ts.map +1 -0
- package/dist/cli/commands/dev.d.ts +39 -0
- package/dist/cli/commands/dev.d.ts.map +1 -0
- package/dist/cli/commands/doctor.d.ts +42 -0
- package/dist/cli/commands/doctor.d.ts.map +1 -0
- package/dist/cli/commands/generate.d.ts +36 -0
- package/dist/cli/commands/generate.d.ts.map +1 -0
- package/dist/cli/commands/init.d.ts +30 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/install-app.d.ts +30 -0
- package/dist/cli/commands/install-app.d.ts.map +1 -0
- package/dist/cli/commands/items.d.ts +45 -0
- package/dist/cli/commands/items.d.ts.map +1 -0
- package/dist/cli/commands/migrations.d.ts +41 -0
- package/dist/cli/commands/migrations.d.ts.map +1 -0
- package/dist/cli/commands/pipelines.d.ts +40 -0
- package/dist/cli/commands/pipelines.d.ts.map +1 -0
- package/dist/cli/commands/status.d.ts +23 -0
- package/dist/cli/commands/status.d.ts.map +1 -0
- package/dist/cli/commands/system.d.ts +29 -0
- package/dist/cli/commands/system.d.ts.map +1 -0
- package/dist/cli/commands/test.d.ts +46 -0
- package/dist/cli/commands/test.d.ts.map +1 -0
- package/dist/cli/commands/uninstall-app.d.ts +23 -0
- package/dist/cli/commands/uninstall-app.d.ts.map +1 -0
- package/dist/cli/context.d.ts +88 -0
- package/dist/cli/context.d.ts.map +1 -0
- package/dist/cli/env-loader.d.ts +14 -0
- package/dist/cli/env-loader.d.ts.map +1 -0
- package/dist/cli/index.d.ts +41 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/functions/_shared/agent-runner.d.ts +156 -0
- package/dist/functions/_shared/agent-runner.d.ts.map +1 -0
- package/dist/functions/_shared/app-manifest.d.ts +68 -0
- package/dist/functions/_shared/app-manifest.d.ts.map +1 -0
- package/dist/functions/_shared/audit.d.ts +91 -0
- package/dist/functions/_shared/audit.d.ts.map +1 -0
- package/dist/functions/_shared/db.d.ts +125 -0
- package/dist/functions/_shared/db.d.ts.map +1 -0
- package/dist/functions/_shared/index.d.ts +298 -0
- package/dist/functions/_shared/index.d.ts.map +1 -0
- package/dist/functions/_shared/middleware.d.ts +315 -0
- package/dist/functions/_shared/middleware.d.ts.map +1 -0
- package/dist/functions/_shared/permissions.d.ts +626 -0
- package/dist/functions/_shared/permissions.d.ts.map +1 -0
- package/dist/functions/_shared/pipeline-runner.d.ts +124 -0
- package/dist/functions/_shared/pipeline-runner.d.ts.map +1 -0
- package/dist/functions/_shared/principal.d.ts +284 -0
- package/dist/functions/_shared/principal.d.ts.map +1 -0
- package/dist/functions/_shared/schema-utils.d.ts +181 -0
- package/dist/functions/_shared/schema-utils.d.ts.map +1 -0
- package/dist/functions/_shared/testing.d.ts +172 -0
- package/dist/functions/_shared/testing.d.ts.map +1 -0
- package/dist/functions/_shared/trigger-engine.d.ts +140 -0
- package/dist/functions/_shared/trigger-engine.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registration.d.ts +81 -0
- package/dist/functions/_shared/webhook-registration.d.ts.map +1 -0
- package/dist/functions/_shared/webhook-registry.d.ts +57 -0
- package/dist/functions/_shared/webhook-registry.d.ts.map +1 -0
- package/dist/functions/account-nodes.d.ts +48 -0
- package/dist/functions/account-nodes.d.ts.map +1 -0
- package/dist/functions/admin-data.d.ts +178 -0
- package/dist/functions/admin-data.d.ts.map +1 -0
- package/dist/functions/ai-agents.d.ts +125 -0
- package/dist/functions/ai-agents.d.ts.map +1 -0
- package/dist/functions/api-keys.d.ts +140 -0
- package/dist/functions/api-keys.d.ts.map +1 -0
- package/dist/functions/apps.d.ts +163 -0
- package/dist/functions/apps.d.ts.map +1 -0
- package/dist/functions/auth.d.ts +74 -0
- package/dist/functions/auth.d.ts.map +1 -0
- package/dist/functions/debug-auth.d.ts +33 -0
- package/dist/functions/debug-auth.d.ts.map +1 -0
- package/dist/functions/embeddings.d.ts +205 -0
- package/dist/functions/embeddings.d.ts.map +1 -0
- package/dist/functions/integration-routes.d.ts +45 -0
- package/dist/functions/integration-routes.d.ts.map +1 -0
- package/dist/functions/integrations.d.ts +124 -0
- package/dist/functions/integrations.d.ts.map +1 -0
- package/dist/functions/item-progress.d.ts +41 -0
- package/dist/functions/item-progress.d.ts.map +1 -0
- package/dist/functions/logs.d.ts +162 -0
- package/dist/functions/logs.d.ts.map +1 -0
- package/dist/functions/observability.d.ts +123 -0
- package/dist/functions/observability.d.ts.map +1 -0
- package/dist/functions/pipeline-executions.d.ts +190 -0
- package/dist/functions/pipeline-executions.d.ts.map +1 -0
- package/dist/functions/pipelines.d.ts +171 -0
- package/dist/functions/pipelines.d.ts.map +1 -0
- package/dist/functions/prompt-configs.d.ts +125 -0
- package/dist/functions/prompt-configs.d.ts.map +1 -0
- package/dist/functions/roles.d.ts +118 -0
- package/dist/functions/roles.d.ts.map +1 -0
- package/dist/functions/system-cron.d.ts +65 -0
- package/dist/functions/system-cron.d.ts.map +1 -0
- package/dist/functions/system.d.ts +29 -0
- package/dist/functions/system.d.ts.map +1 -0
- package/dist/functions/tests.d.ts +28 -0
- package/dist/functions/tests.d.ts.map +1 -0
- package/dist/functions/timers.d.ts +139 -0
- package/dist/functions/timers.d.ts.map +1 -0
- package/dist/functions/triggers.d.ts +203 -0
- package/dist/functions/triggers.d.ts.map +1 -0
- package/dist/functions/types.d.ts +151 -0
- package/dist/functions/types.d.ts.map +1 -0
- package/dist/src/types/types.d.ts +364 -0
- package/dist/src/types/types.d.ts.map +1 -0
- package/package.json +192 -0
- package/scripts/app-install-cli.ts +286 -0
- package/scripts/assemble-frontend.sh +79 -0
- package/scripts/assemble-functions.sh +62 -0
- package/scripts/assemble.sh +35 -0
- package/scripts/boundary-check.sh +106 -0
- package/scripts/build-manifest.sh +80 -0
- package/scripts/check-core-integrity.sh +82 -0
- package/scripts/ingest-chunks.cjs +202 -0
- package/scripts/kb-chunk-parser.cjs +312 -0
- package/scripts/kb-chunk-parser.ts +330 -0
- package/scripts/load-test-app-install.ts +484 -0
- package/scripts/netlify-dev-wrapper.sh +22 -0
- package/scripts/verify-integrity.sh +69 -0
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/commands/generate
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* `spine generate` command ā code scaffolding for extending Spine safely.
|
|
9
|
+
* Generates type definitions, handlers, and UI pages in v2-custom/ only.
|
|
10
|
+
*
|
|
11
|
+
* **Commands:**
|
|
12
|
+
* | Subcommand | Description |
|
|
13
|
+
* |-------------------------|-------------------------------------------------------|
|
|
14
|
+
* | `generate type <slug>` | Create new type with handler and UI stubs |
|
|
15
|
+
* | `generate function <name>` | Create new Netlify function stub |
|
|
16
|
+
*
|
|
17
|
+
* **What gets generated for `type`:**
|
|
18
|
+
* 1. Type record in database (item type by default)
|
|
19
|
+
* 2. Handler stub: `v2-custom/functions/{slug}.ts`
|
|
20
|
+
* 3. UI page stub: `v2-custom/src/pages/{slug}/index.tsx`
|
|
21
|
+
*
|
|
22
|
+
* **What gets generated for `function`:**
|
|
23
|
+
* 1. Netlify function stub: `v2-custom/functions/{name}.ts`
|
|
24
|
+
* 2. Basic handler structure with middleware imports
|
|
25
|
+
*
|
|
26
|
+
* **Usage:**
|
|
27
|
+
* ```bash
|
|
28
|
+
* spine generate type ticket-v2
|
|
29
|
+
* spine generate function webhook-handler
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @seeAlso functions/types.ts (type CRUD API)
|
|
33
|
+
* @seeAlso functions/_shared/middleware.ts (handler structure)
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
import type { Command } from 'commander'
|
|
37
|
+
import { buildCliContext, handleError } from '../context.ts'
|
|
38
|
+
import { adminDb } from '../../functions/_shared/index.ts'
|
|
39
|
+
import { existsSync, mkdirSync, writeFileSync } from 'fs'
|
|
40
|
+
import { resolve, dirname } from 'path'
|
|
41
|
+
import { fileURLToPath } from 'url'
|
|
42
|
+
|
|
43
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
44
|
+
const __dirname = dirname(__filename)
|
|
45
|
+
const PROJECT_ROOT = resolve(__dirname, '../../../')
|
|
46
|
+
|
|
47
|
+
// āāā TYPE GENERATION āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
48
|
+
|
|
49
|
+
async function generateType(ctx: any, slug: string, options: any): Promise<void> {
|
|
50
|
+
const name = options.name || slug.split('-').map(s => s.charAt(0).toUpperCase() + s.slice(1)).join(' ')
|
|
51
|
+
const kind = options.kind || 'item'
|
|
52
|
+
const description = options.description || `${name} type generated by spine CLI`
|
|
53
|
+
|
|
54
|
+
console.log(`\nš§ Generating type '${slug}'...`)
|
|
55
|
+
|
|
56
|
+
// Step 1: Create type record in database
|
|
57
|
+
console.log(' Step 1: Creating type record in database...')
|
|
58
|
+
|
|
59
|
+
const { data: existing, error: checkErr } = await adminDb
|
|
60
|
+
.from('types')
|
|
61
|
+
.select('id')
|
|
62
|
+
.eq('slug', slug)
|
|
63
|
+
.eq('kind', kind)
|
|
64
|
+
.single()
|
|
65
|
+
|
|
66
|
+
if (checkErr && checkErr.code !== 'PGRST116') { // PGRST116 = not found
|
|
67
|
+
throw new Error(`Failed to check existing type: ${checkErr.message}`)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (existing) {
|
|
71
|
+
console.log(` ā ļø Type '${slug}' already exists (skipping DB creation)`)
|
|
72
|
+
} else {
|
|
73
|
+
const designSchema = {
|
|
74
|
+
fields: {
|
|
75
|
+
title: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
required: true,
|
|
78
|
+
label: 'Title',
|
|
79
|
+
description: `Title for this ${name}`
|
|
80
|
+
},
|
|
81
|
+
status: {
|
|
82
|
+
type: 'select',
|
|
83
|
+
options: ['open', 'in_progress', 'closed'],
|
|
84
|
+
default: 'open',
|
|
85
|
+
label: 'Status'
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
layout: {
|
|
89
|
+
detail: ['title', 'status'],
|
|
90
|
+
list: ['title', 'status']
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const { error: insertErr } = await adminDb
|
|
95
|
+
.from('types')
|
|
96
|
+
.insert({
|
|
97
|
+
kind,
|
|
98
|
+
slug,
|
|
99
|
+
name,
|
|
100
|
+
description,
|
|
101
|
+
design_schema: designSchema,
|
|
102
|
+
validation_schema: {},
|
|
103
|
+
ownership: 'system',
|
|
104
|
+
is_active: true
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
if (insertErr) {
|
|
108
|
+
throw new Error(`Failed to create type: ${insertErr.message}`)
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(' ā Type record created')
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Step 2: Generate handler stub
|
|
115
|
+
console.log(' Step 2: Generating handler stub...')
|
|
116
|
+
const handlerPath = resolve(PROJECT_ROOT, `v2-custom/functions/${slug}.ts`)
|
|
117
|
+
|
|
118
|
+
if (existsSync(handlerPath) && !options.force) {
|
|
119
|
+
console.log(` ā ļø Handler stub already exists: ${handlerPath}`)
|
|
120
|
+
} else {
|
|
121
|
+
const handlerCode = generateHandlerStub(slug, kind, name)
|
|
122
|
+
writeFileSync(handlerPath, handlerCode)
|
|
123
|
+
console.log(` ā Handler stub created: v2-custom/functions/${slug}.ts`)
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Step 3: Generate UI page stub
|
|
127
|
+
console.log(' Step 3: Generating UI page stub...')
|
|
128
|
+
const pageDir = resolve(PROJECT_ROOT, `v2-custom/src/pages/${slug}`)
|
|
129
|
+
const pagePath = resolve(pageDir, 'index.tsx')
|
|
130
|
+
|
|
131
|
+
if (existsSync(pagePath) && !options.force) {
|
|
132
|
+
console.log(` ā ļø UI page stub already exists: ${pagePath}`)
|
|
133
|
+
} else {
|
|
134
|
+
mkdirSync(pageDir, { recursive: true })
|
|
135
|
+
const pageCode = generatePageStub(slug, name, kind)
|
|
136
|
+
writeFileSync(pagePath, pageCode)
|
|
137
|
+
console.log(` ā UI page stub created: v2-custom/src/pages/${slug}/index.tsx`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log(`\nā
Type '${slug}' generation complete!`)
|
|
141
|
+
console.log(` Next steps:`)
|
|
142
|
+
console.log(` 1. Edit v2-custom/functions/${slug}.ts to implement custom logic`)
|
|
143
|
+
console.log(` 2. Edit v2-custom/src/pages/${slug}/index.tsx to customize the UI`)
|
|
144
|
+
console.log(` 3. Run 'spine dev' to see your changes`)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function generateHandlerStub(slug: string, kind: string, name: string): string {
|
|
148
|
+
return `/**
|
|
149
|
+
* @module ${slug}
|
|
150
|
+
* @audience custom
|
|
151
|
+
* @layer api-handler
|
|
152
|
+
* @stability experimental
|
|
153
|
+
*
|
|
154
|
+
* Custom handler for ${name} (${kind} type).
|
|
155
|
+
* Generated by spine CLI on ${new Date().toISOString()}.
|
|
156
|
+
*
|
|
157
|
+
* Extend this handler to add custom logic for your type.
|
|
158
|
+
*/
|
|
159
|
+
|
|
160
|
+
import { createHandler } from '../v2-core/functions/_shared/middleware'
|
|
161
|
+
|
|
162
|
+
export const handler = createHandler(async (ctx) => {
|
|
163
|
+
const method = ctx.query?.method || 'GET'
|
|
164
|
+
|
|
165
|
+
switch (method) {
|
|
166
|
+
case 'GET':
|
|
167
|
+
// TODO: Implement GET logic
|
|
168
|
+
return { message: '${name} handler - GET not implemented' }
|
|
169
|
+
|
|
170
|
+
case 'POST':
|
|
171
|
+
// TODO: Implement POST logic
|
|
172
|
+
return { message: '${name} handler - POST not implemented' }
|
|
173
|
+
|
|
174
|
+
case 'PATCH':
|
|
175
|
+
// TODO: Implement PATCH logic
|
|
176
|
+
return { message: '${name} handler - PATCH not implemented' }
|
|
177
|
+
|
|
178
|
+
case 'DELETE':
|
|
179
|
+
// TODO: Implement DELETE logic
|
|
180
|
+
return { message: '${name} handler - DELETE not implemented' }
|
|
181
|
+
|
|
182
|
+
default:
|
|
183
|
+
throw new Error('Unsupported method: ' + method)
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
`
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
function generatePageStub(slug: string, name: string, kind: string): string {
|
|
190
|
+
return `/**
|
|
191
|
+
* @page ${slug}/index
|
|
192
|
+
* @audience custom
|
|
193
|
+
* @layer page
|
|
194
|
+
* @stability experimental
|
|
195
|
+
*
|
|
196
|
+
* UI page for ${name} (${kind} type).
|
|
197
|
+
* Generated by spine CLI on ${new Date().toISOString()}.
|
|
198
|
+
*/
|
|
199
|
+
|
|
200
|
+
import React from 'react'
|
|
201
|
+
import { useParams } from 'react-router-dom'
|
|
202
|
+
import { useApi } from '../../v2-core/src/hooks/useApi'
|
|
203
|
+
import { apiFetch } from '../../v2-core/src/lib/api'
|
|
204
|
+
|
|
205
|
+
export default function ${name.replace(/\s+/g, '')}Page() {
|
|
206
|
+
const { id } = useParams()
|
|
207
|
+
|
|
208
|
+
const { data, loading, error } = useApi(
|
|
209
|
+
() => apiFetch('/.netlify/functions/${slug}' + (id ? '?id=' + id : '')),
|
|
210
|
+
{ immediate: true }
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
if (loading) return <div>Loading...</div>
|
|
214
|
+
if (error) return <div>Error: {error.message}</div>
|
|
215
|
+
|
|
216
|
+
return (
|
|
217
|
+
<div className="p-6">
|
|
218
|
+
<h1 className="text-2xl font-bold mb-4">${name}</h1>
|
|
219
|
+
<pre className="bg-gray-100 p-4 rounded">
|
|
220
|
+
{JSON.stringify(data, null, 2)}
|
|
221
|
+
</pre>
|
|
222
|
+
<p className="mt-4 text-gray-600">
|
|
223
|
+
Edit v2-custom/src/pages/${slug}/index.tsx to customize this page.
|
|
224
|
+
</p>
|
|
225
|
+
</div>
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
`
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// āāā FUNCTION GENERATION āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
232
|
+
|
|
233
|
+
async function generateFunction(name: string, options: any): Promise<void> {
|
|
234
|
+
console.log(`\nš§ Generating function '${name}'...`)
|
|
235
|
+
|
|
236
|
+
const handlerPath = resolve(PROJECT_ROOT, `v2-custom/functions/${name}.ts`)
|
|
237
|
+
|
|
238
|
+
if (existsSync(handlerPath) && !options.force) {
|
|
239
|
+
console.log(` ā ļø Function already exists: ${handlerPath}`)
|
|
240
|
+
console.log(` Use --force to overwrite`)
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const handlerCode = generateFunctionStub(name, options.description)
|
|
245
|
+
writeFileSync(handlerPath, handlerCode)
|
|
246
|
+
|
|
247
|
+
console.log(`ā
Function '${name}' created!`)
|
|
248
|
+
console.log(` Location: v2-custom/functions/${name}.ts`)
|
|
249
|
+
console.log(` Next steps:`)
|
|
250
|
+
console.log(` 1. Edit the handler to implement your logic`)
|
|
251
|
+
console.log(` 2. Run 'spine dev' to see your changes`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function generateFunctionStub(name: string, description?: string): string {
|
|
255
|
+
return `/**
|
|
256
|
+
* @module ${name}
|
|
257
|
+
* @audience custom
|
|
258
|
+
* @layer api-handler
|
|
259
|
+
* @stability experimental
|
|
260
|
+
*
|
|
261
|
+
* ${description || `Custom Netlify function: ${name}`}
|
|
262
|
+
* Generated by spine CLI on ${new Date().toISOString()}.
|
|
263
|
+
*/
|
|
264
|
+
|
|
265
|
+
import { createHandler } from '../v2-core/functions/_shared/middleware'
|
|
266
|
+
|
|
267
|
+
export const handler = createHandler(async (ctx) => {
|
|
268
|
+
// Access principal: ctx.principal (human, machine, or system)
|
|
269
|
+
// Access database: ctx.db (RLS-scoped) or import { adminDb } for admin access
|
|
270
|
+
// Access query params: ctx.query
|
|
271
|
+
|
|
272
|
+
const q = ctx.query || {}
|
|
273
|
+
const action = q.action || 'default'
|
|
274
|
+
|
|
275
|
+
switch (action) {
|
|
276
|
+
case 'default':
|
|
277
|
+
return {
|
|
278
|
+
message: 'Hello from ${name}',
|
|
279
|
+
principal: ctx.principal?.id,
|
|
280
|
+
timestamp: new Date().toISOString()
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// TODO: Add more actions here
|
|
284
|
+
// case 'custom':
|
|
285
|
+
// return { data: await yourCustomLogic(ctx) }
|
|
286
|
+
|
|
287
|
+
default:
|
|
288
|
+
return {
|
|
289
|
+
error: 'Unknown action: ' + action,
|
|
290
|
+
available_actions: ['default']
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
})
|
|
294
|
+
`
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// āāā COMMAND REGISTRATION āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
298
|
+
|
|
299
|
+
export function registerGenerateCommands(program: Command) {
|
|
300
|
+
const generate = program
|
|
301
|
+
.command('generate')
|
|
302
|
+
.description('Generate code stubs for extending Spine')
|
|
303
|
+
|
|
304
|
+
generate
|
|
305
|
+
.command('type <slug>')
|
|
306
|
+
.description('Generate a new type with handler and UI stubs')
|
|
307
|
+
.option('--name <name>', 'Display name for the type')
|
|
308
|
+
.option('--kind <kind>', 'Type kind (item, account, person)', 'item')
|
|
309
|
+
.option('--description <desc>', 'Type description')
|
|
310
|
+
.option('--force', 'Overwrite existing files', false)
|
|
311
|
+
.action(async (slug, opts) => {
|
|
312
|
+
try {
|
|
313
|
+
const ctx = await buildCliContext()
|
|
314
|
+
await generateType(ctx, slug, opts)
|
|
315
|
+
} catch (err: any) {
|
|
316
|
+
handleError(err)
|
|
317
|
+
}
|
|
318
|
+
})
|
|
319
|
+
|
|
320
|
+
generate
|
|
321
|
+
.command('function <name>')
|
|
322
|
+
.description('Generate a new Netlify function stub')
|
|
323
|
+
.option('--description <desc>', 'Function description')
|
|
324
|
+
.option('--force', 'Overwrite existing file', false)
|
|
325
|
+
.action(async (name, opts) => {
|
|
326
|
+
try {
|
|
327
|
+
await generateFunction(name, opts)
|
|
328
|
+
} catch (err: any) {
|
|
329
|
+
handleError(err)
|
|
330
|
+
}
|
|
331
|
+
})
|
|
332
|
+
}
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/commands/init
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* `spine-framework init` ā Bootstrap a fresh Spine installation.
|
|
9
|
+
*
|
|
10
|
+
* Runs the foundation schema (000_foundation.sql) and seed data (001_seed.sql)
|
|
11
|
+
* against the configured Supabase project, then scaffolds the custom/ workspace
|
|
12
|
+
* if it doesn't already exist.
|
|
13
|
+
*
|
|
14
|
+
* **Usage:**
|
|
15
|
+
* ```bash
|
|
16
|
+
* # Fresh install (agent provides credentials from Supabase dashboard)
|
|
17
|
+
* spine-framework init --url https://xyz.supabase.co --anon-key eyJ... --service-role-key eyJ...
|
|
18
|
+
*
|
|
19
|
+
* # Already have .env configured
|
|
20
|
+
* spine-framework init
|
|
21
|
+
*
|
|
22
|
+
* # DB only, no filesystem changes
|
|
23
|
+
* spine-framework init --skip-scaffold
|
|
24
|
+
* ```
|
|
25
|
+
*
|
|
26
|
+
* When --url/--anon-key/--service-role-key are provided, init writes .env before
|
|
27
|
+
* running migrations ā so it works on a completely fresh checkout with no prior config.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { Command } from 'commander'
|
|
31
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'
|
|
32
|
+
import { resolve, dirname } from 'path'
|
|
33
|
+
import { fileURLToPath } from 'url'
|
|
34
|
+
import { adminDb } from '../../functions/_shared/index.ts'
|
|
35
|
+
|
|
36
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
37
|
+
const __dirname = dirname(__filename)
|
|
38
|
+
const PROJECT_ROOT = resolve(__dirname, '../../../')
|
|
39
|
+
const MIGRATIONS_DIR = resolve(__dirname, '../../migrations')
|
|
40
|
+
|
|
41
|
+
interface InitOptions {
|
|
42
|
+
skipScaffold: boolean
|
|
43
|
+
dryRun: boolean
|
|
44
|
+
url?: string
|
|
45
|
+
anonKey?: string
|
|
46
|
+
serviceRoleKey?: string
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function runMigration(filename: string, dryRun: boolean): Promise<boolean> {
|
|
50
|
+
const filePath = resolve(MIGRATIONS_DIR, filename)
|
|
51
|
+
if (!existsSync(filePath)) {
|
|
52
|
+
console.error(` ā Migration file not found: ${filename}`)
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const sql = readFileSync(filePath, 'utf8')
|
|
57
|
+
|
|
58
|
+
if (dryRun) {
|
|
59
|
+
console.log(` [dry-run] Would execute: ${filename} (${sql.length} chars)`)
|
|
60
|
+
return true
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Use the Supabase SQL endpoint to execute raw DDL.
|
|
64
|
+
// This works on blank projects (no exec_sql RPC needed).
|
|
65
|
+
const supabaseUrl = process.env.SUPABASE_URL
|
|
66
|
+
const serviceKey = process.env.SUPABASE_SERVICE_ROLE_KEY
|
|
67
|
+
|
|
68
|
+
if (!supabaseUrl || !serviceKey) {
|
|
69
|
+
console.error(` ā Missing SUPABASE_URL or SUPABASE_SERVICE_ROLE_KEY`)
|
|
70
|
+
return false
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Try exec_sql RPC first (works if schema already has it)
|
|
74
|
+
const { error } = await adminDb.rpc('exec_sql', { query: sql }).single()
|
|
75
|
+
if (!error) return true
|
|
76
|
+
|
|
77
|
+
// Fallback: use the Supabase /pg/query endpoint for raw SQL execution
|
|
78
|
+
console.log(` ā ļø exec_sql not available, using direct SQL endpoint...`)
|
|
79
|
+
|
|
80
|
+
const pgResponse = await fetch(`${supabaseUrl}/pg/query`, {
|
|
81
|
+
method: 'POST',
|
|
82
|
+
headers: {
|
|
83
|
+
'Content-Type': 'application/json',
|
|
84
|
+
'apikey': serviceKey,
|
|
85
|
+
'Authorization': `Bearer ${serviceKey}`,
|
|
86
|
+
},
|
|
87
|
+
body: JSON.stringify({ query: sql })
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
if (pgResponse.ok) return true
|
|
91
|
+
|
|
92
|
+
// Second fallback: use the Supabase Management API SQL endpoint
|
|
93
|
+
// Extract project ref from URL (e.g. https://abcdef.supabase.co ā abcdef)
|
|
94
|
+
const projectRef = supabaseUrl.replace('https://', '').split('.')[0]
|
|
95
|
+
|
|
96
|
+
const mgmtResponse = await fetch(
|
|
97
|
+
`https://api.supabase.com/v1/projects/${projectRef}/database/query`,
|
|
98
|
+
{
|
|
99
|
+
method: 'POST',
|
|
100
|
+
headers: {
|
|
101
|
+
'Content-Type': 'application/json',
|
|
102
|
+
'Authorization': `Bearer ${serviceKey}`,
|
|
103
|
+
},
|
|
104
|
+
body: JSON.stringify({ query: sql })
|
|
105
|
+
}
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
if (mgmtResponse.ok) return true
|
|
109
|
+
|
|
110
|
+
const text = await mgmtResponse.text()
|
|
111
|
+
console.error(` ā Migration ${filename} failed: ${text}`)
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function scaffoldCustomWorkspace(dryRun: boolean): void {
|
|
116
|
+
const dirs = [
|
|
117
|
+
'custom/apps',
|
|
118
|
+
'custom/functions',
|
|
119
|
+
'custom/components',
|
|
120
|
+
'custom/migrations',
|
|
121
|
+
'custom/tests',
|
|
122
|
+
]
|
|
123
|
+
|
|
124
|
+
for (const dir of dirs) {
|
|
125
|
+
const fullPath = resolve(PROJECT_ROOT, dir)
|
|
126
|
+
if (existsSync(fullPath)) {
|
|
127
|
+
console.log(` āļø ${dir}/ already exists`)
|
|
128
|
+
} else if (dryRun) {
|
|
129
|
+
console.log(` [dry-run] Would create: ${dir}/`)
|
|
130
|
+
} else {
|
|
131
|
+
mkdirSync(fullPath, { recursive: true })
|
|
132
|
+
writeFileSync(resolve(fullPath, '.gitkeep'), '')
|
|
133
|
+
console.log(` ā Created ${dir}/`)
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
async function checkAlreadyInitialized(): Promise<boolean> {
|
|
139
|
+
try {
|
|
140
|
+
const { data, error } = await adminDb
|
|
141
|
+
.from('apps')
|
|
142
|
+
.select('slug')
|
|
143
|
+
.eq('slug', 'spine-core')
|
|
144
|
+
.single()
|
|
145
|
+
|
|
146
|
+
return !error && !!data
|
|
147
|
+
} catch {
|
|
148
|
+
return false
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function writeEnvFile(url: string, anonKey: string, serviceRoleKey: string, dryRun: boolean): void {
|
|
153
|
+
const envPath = resolve(PROJECT_ROOT, '.env')
|
|
154
|
+
const envContent = [
|
|
155
|
+
`VITE_SUPABASE_URL=${url}`,
|
|
156
|
+
`VITE_SUPABASE_ANON_KEY=${anonKey}`,
|
|
157
|
+
`SUPABASE_URL=${url}`,
|
|
158
|
+
`SUPABASE_ANON_KEY=${anonKey}`,
|
|
159
|
+
`SUPABASE_SERVICE_ROLE_KEY=${serviceRoleKey}`,
|
|
160
|
+
`VITE_APP_NAME=Spine`,
|
|
161
|
+
`DB_SCHEMA=public`,
|
|
162
|
+
].join('\n') + '\n'
|
|
163
|
+
|
|
164
|
+
if (dryRun) {
|
|
165
|
+
console.log(` [dry-run] Would write .env with SUPABASE_URL=${url}`)
|
|
166
|
+
return
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const existed = existsSync(envPath)
|
|
170
|
+
writeFileSync(envPath, envContent, 'utf8')
|
|
171
|
+
console.log(` ā ${existed ? 'Updated' : 'Created'} .env`)
|
|
172
|
+
|
|
173
|
+
// Also inject into process.env for the rest of this run
|
|
174
|
+
process.env.SUPABASE_URL = url
|
|
175
|
+
process.env.SUPABASE_ANON_KEY = anonKey
|
|
176
|
+
process.env.SUPABASE_SERVICE_ROLE_KEY = serviceRoleKey
|
|
177
|
+
process.env.VITE_SUPABASE_URL = url
|
|
178
|
+
process.env.VITE_SUPABASE_ANON_KEY = anonKey
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function initCommand(options: InitOptions): Promise<void> {
|
|
182
|
+
console.log('\nš Spine Framework ā Init\n')
|
|
183
|
+
|
|
184
|
+
// Step 0: Write .env if credentials were provided via flags
|
|
185
|
+
if (options.url && options.anonKey && options.serviceRoleKey) {
|
|
186
|
+
console.log('š Step 0: Writing environment configuration...')
|
|
187
|
+
writeEnvFile(options.url, options.anonKey, options.serviceRoleKey, options.dryRun)
|
|
188
|
+
} else if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_KEY) {
|
|
189
|
+
console.error('ā No Supabase credentials found.')
|
|
190
|
+
console.error(' Provide them via flags:')
|
|
191
|
+
console.error(' spine-framework init --url <url> --anon-key <key> --service-role-key <key>')
|
|
192
|
+
console.error(' Or set SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY in .env')
|
|
193
|
+
process.exit(1)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check if already initialized
|
|
197
|
+
const alreadyInit = await checkAlreadyInitialized()
|
|
198
|
+
if (alreadyInit) {
|
|
199
|
+
console.log(' ā ļø Database already initialized (spine-core app exists)')
|
|
200
|
+
console.log(' Skipping schema migration. Use "spine-framework migrations" to manage updates.\n')
|
|
201
|
+
|
|
202
|
+
if (!options.skipScaffold) {
|
|
203
|
+
console.log('š Checking workspace scaffold...')
|
|
204
|
+
scaffoldCustomWorkspace(options.dryRun)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log('\nā
Init complete (already initialized)')
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Step 1: Foundation schema
|
|
212
|
+
console.log('š¦ Step 1: Applying foundation schema (000_foundation.sql)...')
|
|
213
|
+
const foundationOk = await runMigration('000_foundation.sql', options.dryRun)
|
|
214
|
+
if (!foundationOk && !options.dryRun) {
|
|
215
|
+
console.error('\nā Init failed at foundation schema. Check your Supabase connection.')
|
|
216
|
+
process.exit(1)
|
|
217
|
+
}
|
|
218
|
+
console.log(' ā Foundation schema applied')
|
|
219
|
+
|
|
220
|
+
// Step 2: Seed data
|
|
221
|
+
console.log('\nš± Step 2: Applying seed data (001_seed.sql)...')
|
|
222
|
+
const seedOk = await runMigration('001_seed.sql', options.dryRun)
|
|
223
|
+
if (!seedOk && !options.dryRun) {
|
|
224
|
+
console.error('\nā Init failed at seed data.')
|
|
225
|
+
process.exit(1)
|
|
226
|
+
}
|
|
227
|
+
console.log(' ā Seed data applied')
|
|
228
|
+
|
|
229
|
+
// Step 3: Record migration versions
|
|
230
|
+
if (!options.dryRun) {
|
|
231
|
+
console.log('\nš Step 3: Recording migration versions...')
|
|
232
|
+
await adminDb
|
|
233
|
+
.from('schema_migrations')
|
|
234
|
+
.upsert([
|
|
235
|
+
{ version: '000_foundation', applied_at: new Date().toISOString() },
|
|
236
|
+
{ version: '001_seed', applied_at: new Date().toISOString() },
|
|
237
|
+
], { onConflict: 'version' })
|
|
238
|
+
console.log(' ā Migration versions recorded')
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Step 4: Scaffold custom workspace
|
|
242
|
+
if (!options.skipScaffold) {
|
|
243
|
+
console.log('\nš Step 4: Scaffolding custom workspace...')
|
|
244
|
+
scaffoldCustomWorkspace(options.dryRun)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
console.log('\nā
Spine initialized successfully!')
|
|
248
|
+
console.log('\n Next steps:')
|
|
249
|
+
console.log(' 1. spine-framework install-app <app-slug> # Install an app')
|
|
250
|
+
console.log(' 2. spine-framework create-app my-app # Or scaffold a new one')
|
|
251
|
+
console.log(' 3. npm run assemble && netlify dev # Start developing')
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
export function registerInitCommands(program: Command) {
|
|
255
|
+
program
|
|
256
|
+
.command('init')
|
|
257
|
+
.description('Initialize a fresh Spine installation (schema + seed + scaffold)')
|
|
258
|
+
.option('--url <url>', 'Supabase project URL (writes .env if provided)')
|
|
259
|
+
.option('--anon-key <key>', 'Supabase anon key')
|
|
260
|
+
.option('--service-role-key <key>', 'Supabase service role key')
|
|
261
|
+
.option('--skip-scaffold', 'Skip creating custom/ workspace directories', false)
|
|
262
|
+
.option('--dry-run', 'Show what would happen without making changes', false)
|
|
263
|
+
.action(async (opts) => {
|
|
264
|
+
try {
|
|
265
|
+
await initCommand(opts)
|
|
266
|
+
} catch (err: any) {
|
|
267
|
+
console.error('Error:', err.message)
|
|
268
|
+
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
269
|
+
process.exit(1)
|
|
270
|
+
}
|
|
271
|
+
})
|
|
272
|
+
}
|