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,227 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/commands/test
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* `spine test` command — unified test runner for all 4 test surfaces.
|
|
9
|
+
* Runs Vitest tests and persists results to Supabase via the custom reporter.
|
|
10
|
+
*
|
|
11
|
+
* **Commands:**
|
|
12
|
+
* | Subcommand | Description |
|
|
13
|
+
* |-------------------------|-------------------------------------------------------|
|
|
14
|
+
* | `test` | Run all test suites |
|
|
15
|
+
* | `test unit` | Run unit tests only |
|
|
16
|
+
* | `test integration` | Run integration tests only |
|
|
17
|
+
* | `test api` | Run API tests only |
|
|
18
|
+
* | `test ui` | Run UI/Playwright tests only |
|
|
19
|
+
* | `test --json` | Output structured JSON summary |
|
|
20
|
+
* | `test --watch` | Run in watch mode (for unit/integration) |
|
|
21
|
+
*
|
|
22
|
+
* **Test surfaces:**
|
|
23
|
+
* - **unit**: Fast tests, no DB (v2-core/tests/unit/)
|
|
24
|
+
* - **integration**: DB tests with fixtures (v2-core/tests/integration/)
|
|
25
|
+
* - **api**: HTTP fetch tests against localhost:8888 (v2-core/tests/api/)
|
|
26
|
+
* - **ui**: Playwright browser tests (v2-core/tests/ui/)
|
|
27
|
+
*
|
|
28
|
+
* **Results:**
|
|
29
|
+
* All test runs are persisted to `public.test_runs` and `public.test_results`
|
|
30
|
+
* via the custom Vitest reporter (v2-core/tests/reporter.ts).
|
|
31
|
+
*
|
|
32
|
+
* **Usage:**
|
|
33
|
+
* ```bash
|
|
34
|
+
* spine test
|
|
35
|
+
* spine test unit --watch
|
|
36
|
+
* spine test integration --json
|
|
37
|
+
* spine test api
|
|
38
|
+
* spine test ui
|
|
39
|
+
* ```
|
|
40
|
+
*
|
|
41
|
+
* @seeAlso tests/reporter.ts (custom Vitest reporter)
|
|
42
|
+
* @seeAlso functions/tests.ts (API endpoint for querying results)
|
|
43
|
+
* @seeAlso pages/admin/TestingDashboard.tsx (UI for viewing results)
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
import type { Command } from 'commander'
|
|
47
|
+
import { spawn } from 'child_process'
|
|
48
|
+
import { resolve, dirname } from 'path'
|
|
49
|
+
import { fileURLToPath } from 'url'
|
|
50
|
+
|
|
51
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
52
|
+
const __dirname = dirname(__filename)
|
|
53
|
+
const PROJECT_ROOT = resolve(__dirname, '../../../')
|
|
54
|
+
|
|
55
|
+
// ─── TYPES ─────────────────────────────────────────────────────────────────
|
|
56
|
+
|
|
57
|
+
type TestSuite = 'unit' | 'integration' | 'api' | 'ui' | 'all'
|
|
58
|
+
|
|
59
|
+
// ─── SUITE CONFIGURATION ───────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
const SUITES: Record<string, { pattern: string; runner: 'vitest' | 'playwright' | 'node' }> = {
|
|
62
|
+
unit: { pattern: 'v2-core/tests/unit', runner: 'vitest' },
|
|
63
|
+
integration: { pattern: 'v2-core/tests/integration', runner: 'vitest' },
|
|
64
|
+
api: { pattern: 'v2-core/tests/api', runner: 'vitest' },
|
|
65
|
+
ui: { pattern: 'v2-core/tests/ui', runner: 'playwright' }
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ─── COMMAND REGISTRATION ──────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
export function registerTestCommands(program: Command) {
|
|
71
|
+
const testCmd = program
|
|
72
|
+
.command('test [suite]')
|
|
73
|
+
.description('Run Spine test suites')
|
|
74
|
+
.option('--json', 'Output as JSON')
|
|
75
|
+
.option('--watch', 'Watch mode (unit/integration only)')
|
|
76
|
+
.option('--reporter <name>', 'Vitest reporter', 'default')
|
|
77
|
+
.action(async (suiteArg: string | undefined, opts) => {
|
|
78
|
+
const suite: TestSuite = (suiteArg || 'all') as TestSuite
|
|
79
|
+
|
|
80
|
+
// Validate suite
|
|
81
|
+
if (suite !== 'all' && !SUITES[suite]) {
|
|
82
|
+
console.error(`\n❌ Unknown test suite: ${suite}`)
|
|
83
|
+
console.log('Valid suites: unit, integration, api, ui, all')
|
|
84
|
+
process.exit(1)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Determine which suites to run
|
|
88
|
+
const suitesToRun = suite === 'all'
|
|
89
|
+
? ['unit', 'integration', 'api', 'ui']
|
|
90
|
+
: [suite]
|
|
91
|
+
|
|
92
|
+
console.log(`\n🧪 Running Spine test suites: ${suitesToRun.join(', ')}\n`)
|
|
93
|
+
|
|
94
|
+
const results: Array<{ suite: string; exitCode: number; passed: boolean }> = []
|
|
95
|
+
|
|
96
|
+
for (const s of suitesToRun) {
|
|
97
|
+
const config = SUITES[s]
|
|
98
|
+
console.log(`\n▶️ ${s.toUpperCase()} tests (${config.runner})`)
|
|
99
|
+
console.log('-'.repeat(40))
|
|
100
|
+
|
|
101
|
+
try {
|
|
102
|
+
let exitCode: number
|
|
103
|
+
|
|
104
|
+
if (config.runner === 'vitest') {
|
|
105
|
+
exitCode = await runVitest(s, opts)
|
|
106
|
+
} else if (config.runner === 'playwright') {
|
|
107
|
+
exitCode = await runPlaywright(s, opts)
|
|
108
|
+
} else {
|
|
109
|
+
exitCode = await runNode(s, opts)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
results.push({ suite: s, exitCode, passed: exitCode === 0 })
|
|
113
|
+
|
|
114
|
+
if (exitCode === 0) {
|
|
115
|
+
console.log(`✓ ${s} tests passed\n`)
|
|
116
|
+
} else {
|
|
117
|
+
console.error(`✗ ${s} tests failed (exit ${exitCode})\n`)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
} catch (err: any) {
|
|
121
|
+
console.error(`\n❌ Error running ${s} tests:`, err.message)
|
|
122
|
+
results.push({ suite: s, exitCode: 1, passed: false })
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Summary
|
|
127
|
+
console.log('\n' + '='.repeat(50))
|
|
128
|
+
console.log('Test Run Summary')
|
|
129
|
+
console.log('='.repeat(50))
|
|
130
|
+
|
|
131
|
+
const totalPassed = results.filter(r => r.passed).length
|
|
132
|
+
const totalFailed = results.filter(r => !r.passed).length
|
|
133
|
+
|
|
134
|
+
for (const r of results) {
|
|
135
|
+
const icon = r.passed ? '✓' : '✗'
|
|
136
|
+
const status = r.passed ? 'PASSED' : 'FAILED'
|
|
137
|
+
console.log(`${icon} ${r.suite.padEnd(12)} ${status}`)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
console.log()
|
|
141
|
+
console.log(`Total: ${totalPassed} passed, ${totalFailed} failed`)
|
|
142
|
+
|
|
143
|
+
// JSON output
|
|
144
|
+
if (opts.json) {
|
|
145
|
+
const jsonOutput = {
|
|
146
|
+
suites: results.map(r => ({
|
|
147
|
+
suite: r.suite,
|
|
148
|
+
status: r.passed ? 'passed' : 'failed',
|
|
149
|
+
exit_code: r.exitCode
|
|
150
|
+
})),
|
|
151
|
+
summary: {
|
|
152
|
+
total: results.length,
|
|
153
|
+
passed: totalPassed,
|
|
154
|
+
failed: totalFailed
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
console.log('\n' + JSON.stringify(jsonOutput, null, 2))
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Exit code
|
|
161
|
+
process.exit(totalFailed > 0 ? 1 : 0)
|
|
162
|
+
})
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── RUNNER IMPLEMENTATIONS ────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
async function runVitest(suite: string, opts: any): Promise<number> {
|
|
168
|
+
const config = SUITES[suite]
|
|
169
|
+
const args = [
|
|
170
|
+
'run',
|
|
171
|
+
config.pattern,
|
|
172
|
+
'--config', resolve(PROJECT_ROOT, 'vitest.config.ts')
|
|
173
|
+
]
|
|
174
|
+
|
|
175
|
+
if (opts.watch) {
|
|
176
|
+
args[0] = 'watch' // Replace 'run' with 'watch'
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Custom reporters - include both default and our custom reporter
|
|
180
|
+
if (opts.reporter === 'default') {
|
|
181
|
+
args.push('--reporter=default')
|
|
182
|
+
args.push('--reporter=' + resolve(PROJECT_ROOT, 'v2-core/tests/reporter.ts'))
|
|
183
|
+
} else {
|
|
184
|
+
args.push(`--reporter=${opts.reporter}`)
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return runCommand('npx', ['vitest', ...args], PROJECT_ROOT)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function runPlaywright(suite: string, opts: any): Promise<number> {
|
|
191
|
+
// Playwright tests run against a running dev server
|
|
192
|
+
// We need to ensure the server is running or start it
|
|
193
|
+
console.log(' (Playwright tests require dev server on localhost:8888)')
|
|
194
|
+
|
|
195
|
+
const args = ['playwright', 'test', SUITES[suite].pattern]
|
|
196
|
+
|
|
197
|
+
if (opts.json) {
|
|
198
|
+
args.push('--reporter=json')
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return runCommand('npx', args, PROJECT_ROOT)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function runNode(suite: string, opts: any): Promise<number> {
|
|
205
|
+
// For custom test scripts that aren't Vitest or Playwright
|
|
206
|
+
const args = [SUITES[suite].pattern]
|
|
207
|
+
return runCommand('node', args, PROJECT_ROOT)
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function runCommand(command: string, args: string[], cwd: string): Promise<number> {
|
|
211
|
+
return new Promise((resolve, reject) => {
|
|
212
|
+
const proc = spawn(command, args, {
|
|
213
|
+
cwd,
|
|
214
|
+
stdio: 'inherit',
|
|
215
|
+
env: process.env,
|
|
216
|
+
shell: true
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
proc.on('exit', (code) => {
|
|
220
|
+
resolve(code || 0)
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
proc.on('error', (err) => {
|
|
224
|
+
reject(err)
|
|
225
|
+
})
|
|
226
|
+
})
|
|
227
|
+
}
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/commands/uninstall-app
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* `spine-framework uninstall-app <slug>` — Soft-uninstall an app.
|
|
9
|
+
*
|
|
10
|
+
* Deactivates the app's types, link types, and triggers in the database.
|
|
11
|
+
* Does NOT delete data (items, threads, etc.) — those remain for audit.
|
|
12
|
+
* Does NOT remove filesystem files — the app code stays in custom/apps/.
|
|
13
|
+
*
|
|
14
|
+
* To fully remove: manually delete the app directory after uninstall.
|
|
15
|
+
*
|
|
16
|
+
* **Usage:**
|
|
17
|
+
* ```bash
|
|
18
|
+
* spine-framework uninstall-app cortex
|
|
19
|
+
* spine-framework uninstall-app cortex --hard # Also deactivate items
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import type { Command } from 'commander'
|
|
24
|
+
import { adminDb } from '../../functions/_shared/index.ts'
|
|
25
|
+
|
|
26
|
+
interface UninstallOptions {
|
|
27
|
+
hard: boolean
|
|
28
|
+
dryRun: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function uninstallApp(slug: string, options: UninstallOptions): Promise<void> {
|
|
32
|
+
console.log(`\n🗑️ Uninstalling app '${slug}'...\n`)
|
|
33
|
+
|
|
34
|
+
// 1. Find the app
|
|
35
|
+
const { data: app, error: appErr } = await adminDb
|
|
36
|
+
.from('apps')
|
|
37
|
+
.select('id, name')
|
|
38
|
+
.eq('slug', slug)
|
|
39
|
+
.single()
|
|
40
|
+
|
|
41
|
+
if (appErr || !app) {
|
|
42
|
+
console.error(` ❌ App '${slug}' not found in database`)
|
|
43
|
+
process.exit(1)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const appId = app.id
|
|
47
|
+
|
|
48
|
+
// 2. Deactivate types
|
|
49
|
+
if (options.dryRun) {
|
|
50
|
+
const { data: types } = await adminDb
|
|
51
|
+
.from('types').select('slug').eq('app_id', appId).eq('is_active', true)
|
|
52
|
+
console.log(` [dry-run] Would deactivate ${types?.length || 0} types`)
|
|
53
|
+
} else {
|
|
54
|
+
const { data: updated } = await adminDb
|
|
55
|
+
.from('types')
|
|
56
|
+
.update({ is_active: false })
|
|
57
|
+
.eq('app_id', appId)
|
|
58
|
+
.eq('is_active', true)
|
|
59
|
+
.select('id')
|
|
60
|
+
|
|
61
|
+
console.log(` ✓ Deactivated ${updated?.length || 0} types`)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// 3. Deactivate link types
|
|
65
|
+
if (options.dryRun) {
|
|
66
|
+
const { data: lt } = await adminDb
|
|
67
|
+
.from('link_types').select('slug').eq('app_id', appId).eq('is_active', true)
|
|
68
|
+
console.log(` [dry-run] Would deactivate ${lt?.length || 0} link types`)
|
|
69
|
+
} else {
|
|
70
|
+
const { data: updated } = await adminDb
|
|
71
|
+
.from('link_types')
|
|
72
|
+
.update({ is_active: false })
|
|
73
|
+
.eq('app_id', appId)
|
|
74
|
+
.eq('is_active', true)
|
|
75
|
+
.select('id')
|
|
76
|
+
|
|
77
|
+
console.log(` ✓ Deactivated ${updated?.length || 0} link types`)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// 4. Deactivate triggers
|
|
81
|
+
if (options.dryRun) {
|
|
82
|
+
const { data: triggers } = await adminDb
|
|
83
|
+
.from('triggers').select('name').eq('app_id', appId).eq('is_active', true)
|
|
84
|
+
console.log(` [dry-run] Would deactivate ${triggers?.length || 0} triggers`)
|
|
85
|
+
} else {
|
|
86
|
+
const { data: updated } = await adminDb
|
|
87
|
+
.from('triggers')
|
|
88
|
+
.update({ is_active: false })
|
|
89
|
+
.eq('app_id', appId)
|
|
90
|
+
.eq('is_active', true)
|
|
91
|
+
.select('id')
|
|
92
|
+
|
|
93
|
+
console.log(` ✓ Deactivated ${updated?.length || 0} triggers`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 5. Hard mode: deactivate items of this app's types
|
|
97
|
+
if (options.hard) {
|
|
98
|
+
if (options.dryRun) {
|
|
99
|
+
const { data: types } = await adminDb
|
|
100
|
+
.from('types').select('id').eq('app_id', appId)
|
|
101
|
+
const typeIds = types?.map(t => t.id) || []
|
|
102
|
+
if (typeIds.length > 0) {
|
|
103
|
+
const { data: items } = await adminDb
|
|
104
|
+
.from('items').select('id').in('type_id', typeIds).eq('is_active', true)
|
|
105
|
+
console.log(` [dry-run] Would deactivate ${items?.length || 0} items (--hard)`)
|
|
106
|
+
}
|
|
107
|
+
} else {
|
|
108
|
+
const { data: types } = await adminDb
|
|
109
|
+
.from('types').select('id').eq('app_id', appId)
|
|
110
|
+
const typeIds = types?.map(t => t.id) || []
|
|
111
|
+
|
|
112
|
+
if (typeIds.length > 0) {
|
|
113
|
+
const { data: updated } = await adminDb
|
|
114
|
+
.from('items')
|
|
115
|
+
.update({ is_active: false })
|
|
116
|
+
.in('type_id', typeIds)
|
|
117
|
+
.eq('is_active', true)
|
|
118
|
+
.select('id')
|
|
119
|
+
|
|
120
|
+
console.log(` ✓ Deactivated ${updated?.length || 0} items (--hard)`)
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// 6. Mark installation as disabled
|
|
126
|
+
if (!options.dryRun) {
|
|
127
|
+
await adminDb
|
|
128
|
+
.from('app_installations')
|
|
129
|
+
.update({ is_enabled: false, updated_at: new Date().toISOString() })
|
|
130
|
+
.eq('app_slug', slug)
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// 7. Deactivate app record
|
|
134
|
+
if (!options.dryRun) {
|
|
135
|
+
await adminDb
|
|
136
|
+
.from('apps')
|
|
137
|
+
.update({ is_active: false })
|
|
138
|
+
.eq('id', appId)
|
|
139
|
+
|
|
140
|
+
console.log(` ✓ App '${slug}' deactivated`)
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
console.log(`\n✅ App '${app.name}' uninstalled (soft-delete)`)
|
|
144
|
+
if (!options.hard) {
|
|
145
|
+
console.log(` Items created by this app are preserved.`)
|
|
146
|
+
console.log(` Use --hard to also deactivate items.`)
|
|
147
|
+
}
|
|
148
|
+
console.log(` App files remain in custom/apps/${slug}/ — delete manually if desired.`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function registerUninstallAppCommands(program: Command) {
|
|
152
|
+
program
|
|
153
|
+
.command('uninstall-app <slug>')
|
|
154
|
+
.description('Soft-uninstall an app (deactivate types, triggers, link types)')
|
|
155
|
+
.option('--hard', 'Also deactivate items created by this app', false)
|
|
156
|
+
.option('--dry-run', 'Show what would happen without making changes', false)
|
|
157
|
+
.action(async (slug, opts) => {
|
|
158
|
+
try {
|
|
159
|
+
await uninstallApp(slug, opts)
|
|
160
|
+
} catch (err: any) {
|
|
161
|
+
console.error('Error:', err.message)
|
|
162
|
+
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
163
|
+
process.exit(1)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/context
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* CLI context builder and output utilities. This is the CLI equivalent of
|
|
9
|
+
* `createHandler()` in `middleware.ts` — it resolves the principal, picks the
|
|
10
|
+
* correct Supabase client, and returns a `CoreContext` that every CLI command
|
|
11
|
+
* passes directly to core functions.
|
|
12
|
+
*
|
|
13
|
+
* **Environment variables read from `.xenv` or `process.env`:**
|
|
14
|
+
* | Variable | Required | Purpose |
|
|
15
|
+
* |---------------------------|----------|------------------------------------|
|
|
16
|
+
* | `SUPABASE_URL` | yes | Project API URL |
|
|
17
|
+
* | `SUPABASE_SERVICE_ROLE_KEY` | yes | Service-role client (admin ops) |
|
|
18
|
+
* | `SUPABASE_ANON_KEY` | yes | User-scoped client (JWT mode) |
|
|
19
|
+
* | `SPINE_CLI_ACCOUNT_ID` | no | Default account scope |
|
|
20
|
+
* | `SPINE_CLI_JWT` | no | Human principal (Supabase JWT) |
|
|
21
|
+
* | `SPINE_CLI_API_KEY` | no | Machine principal (hashed key) |
|
|
22
|
+
* | `SPINE_CLI_DEBUG` | no | Print stack traces on error |
|
|
23
|
+
*
|
|
24
|
+
* **Principal resolution priority** (first match wins):
|
|
25
|
+
* 1. `SPINE_CLI_JWT` → human principal (RLS-scoped `getUserDb`)
|
|
26
|
+
* 2. `SPINE_CLI_API_KEY` → machine principal (`adminDb`)
|
|
27
|
+
* 3. Fallback → `SYSTEM_PRINCIPAL` (`adminDb` — admin ops only)
|
|
28
|
+
*
|
|
29
|
+
* @seeAlso functions/_shared/middleware.ts (createHandler — server-side equivalent)
|
|
30
|
+
* @seeAlso functions/_shared/principal.ts (Principal type and SYSTEM_PRINCIPAL)
|
|
31
|
+
* @seeAlso cli/index.ts (entry point that imports all commands)
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import { readFileSync, existsSync } from 'fs'
|
|
35
|
+
import { resolve, dirname } from 'path'
|
|
36
|
+
import { fileURLToPath } from 'url'
|
|
37
|
+
import { CoreContext, adminDb, SYSTEM_PRINCIPAL, Principal, getUserDb } from '../functions/_shared/index.ts'
|
|
38
|
+
|
|
39
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
40
|
+
const __dirname = dirname(__filename)
|
|
41
|
+
|
|
42
|
+
// ─── ENV LOADING ────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
// ─── CHUNK_START: CLI_CONTEXT_LOAD_ENV ──────────────────────────────────────────────
|
|
45
|
+
/**
|
|
46
|
+
* @chunk-id CLI_CONTEXT_LOAD_ENV_1_0_0
|
|
47
|
+
* @version 1.0.0
|
|
48
|
+
* @hash 0adefa52a0c93821ee538ffe32a4069061bd75e7cada7175b11be3e11027e369
|
|
49
|
+
* @macro Environment Variable Loader
|
|
50
|
+
* @micro Reads .xenv file and populates process.env with missing vars
|
|
51
|
+
* @inputs none — reads from .xenv file path
|
|
52
|
+
* @outputs void — mutates process.env
|
|
53
|
+
* @depends-on [fs, path]
|
|
54
|
+
* @depended-by [buildCliContext]
|
|
55
|
+
* @side-effects [mutates process.env, file system reads]
|
|
56
|
+
* @tags environment, configuration, cli, dotenv
|
|
57
|
+
*/
|
|
58
|
+
function loadEnv() {
|
|
59
|
+
const envPath = resolve(__dirname, '../.xenv')
|
|
60
|
+
if (existsSync(envPath)) {
|
|
61
|
+
const lines = readFileSync(envPath, 'utf8').split('\n')
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
const trimmed = line.trim()
|
|
64
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
65
|
+
const eqIdx = trimmed.indexOf('=')
|
|
66
|
+
if (eqIdx === -1) continue
|
|
67
|
+
const key = trimmed.slice(0, eqIdx).trim()
|
|
68
|
+
const value = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '')
|
|
69
|
+
if (!process.env[key]) {
|
|
70
|
+
process.env[key] = value
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// ─── CHUNK_END: CLI_CONTEXT_LOAD_ENV ────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
// ─── TYPES ──────────────────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Options passed from CLI command `.action()` callbacks to `buildCliContext`.
|
|
81
|
+
*
|
|
82
|
+
* @inputSpec account: string | undefined — UUID of the target account; overrides
|
|
83
|
+
* `SPINE_CLI_ACCOUNT_ID` if both are present.
|
|
84
|
+
* @calledBy All `registerXxxCommands` functions before calling core functions
|
|
85
|
+
*/
|
|
86
|
+
export interface CliOptions {
|
|
87
|
+
account?: string
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── CONTEXT BUILDER ─────────────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
// ─── CHUNK_START: CLI_CONTEXT_BUILD ──────────────────────────────────────────────
|
|
93
|
+
/**
|
|
94
|
+
* @chunk-id CLI_CONTEXT_BUILD_1_0_0
|
|
95
|
+
* @version 1.0.0
|
|
96
|
+
* @hash 501162983b1b24e9a15267ca7b8398b3733f540b2a65a96d8277ec3d780f2003
|
|
97
|
+
* @macro CLI Context Builder
|
|
98
|
+
* @micro Constructs CoreContext with principal resolution and Supabase client
|
|
99
|
+
* @inputs opts: CliOptions — Optional overrides including account ID
|
|
100
|
+
* @outputs CoreContext — Fully resolved context with principal, DB client, and request ID
|
|
101
|
+
* @depends-on [loadEnv, adminDb, getUserDb, SYSTEM_PRINCIPAL]
|
|
102
|
+
* @depended-by [All CLI command handlers]
|
|
103
|
+
* @side-effects [DB queries, environment reads, crypto.randomUUID]
|
|
104
|
+
* @tags cli, authentication, principal-resolution, context
|
|
105
|
+
*/
|
|
106
|
+
export async function buildCliContext(opts: CliOptions = {}): Promise<CoreContext> {
|
|
107
|
+
loadEnv()
|
|
108
|
+
|
|
109
|
+
if (!process.env.SUPABASE_URL || !process.env.SUPABASE_SERVICE_ROLE_KEY) {
|
|
110
|
+
throw new Error(
|
|
111
|
+
'Missing required env vars: SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY.\n' +
|
|
112
|
+
'Set them in v2-core/.xenv or export them before running spine commands.'
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const accountId = opts.account || process.env.SPINE_CLI_ACCOUNT_ID || null
|
|
117
|
+
const requestId = crypto.randomUUID()
|
|
118
|
+
|
|
119
|
+
// Machine API key auth
|
|
120
|
+
const apiKey = process.env.SPINE_CLI_API_KEY
|
|
121
|
+
if (apiKey) {
|
|
122
|
+
const { data: keyRecord } = await adminDb
|
|
123
|
+
.from('api_keys')
|
|
124
|
+
.select('id, account_id, scopes, principal_id')
|
|
125
|
+
.eq('key_hash', apiKey)
|
|
126
|
+
.eq('is_active', true)
|
|
127
|
+
.single()
|
|
128
|
+
|
|
129
|
+
if (!keyRecord) {
|
|
130
|
+
throw new Error('Invalid or inactive SPINE_CLI_API_KEY')
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const principal: Principal = {
|
|
134
|
+
id: keyRecord.principal_id || keyRecord.id,
|
|
135
|
+
type: 'machine',
|
|
136
|
+
accountId: keyRecord.account_id,
|
|
137
|
+
scopes: keyRecord.scopes || [],
|
|
138
|
+
provenance: {
|
|
139
|
+
sourceType: 'api_key',
|
|
140
|
+
createdBy: null,
|
|
141
|
+
apiKeyId: keyRecord.id,
|
|
142
|
+
invokedAt: new Date().toISOString()
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
principal,
|
|
148
|
+
accountId: accountId || keyRecord.account_id,
|
|
149
|
+
db: adminDb,
|
|
150
|
+
requestId
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// JWT auth (human principal)
|
|
155
|
+
const jwt = process.env.SPINE_CLI_JWT
|
|
156
|
+
if (jwt) {
|
|
157
|
+
const userDb = getUserDb(jwt)
|
|
158
|
+
const { data: { user } } = await userDb.auth.getUser()
|
|
159
|
+
|
|
160
|
+
if (!user) {
|
|
161
|
+
throw new Error('Invalid or expired SPINE_CLI_JWT')
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const { data: person } = await adminDb
|
|
165
|
+
.from('people')
|
|
166
|
+
.select('id, full_name, email, roles:people_roles(role:roles(slug))')
|
|
167
|
+
.eq('auth_user_id', user.id)
|
|
168
|
+
.single()
|
|
169
|
+
|
|
170
|
+
const roles = (person?.roles as any[])?.map((r: any) => r.role?.slug).filter(Boolean) || []
|
|
171
|
+
|
|
172
|
+
const principal: Principal = {
|
|
173
|
+
id: person?.id || user.id,
|
|
174
|
+
type: 'human',
|
|
175
|
+
accountId: accountId,
|
|
176
|
+
displayName: person?.full_name || user.email,
|
|
177
|
+
email: person?.email || user.email,
|
|
178
|
+
roles,
|
|
179
|
+
provenance: {
|
|
180
|
+
sourceType: 'jwt',
|
|
181
|
+
createdBy: person?.id || user.id,
|
|
182
|
+
invokedAt: new Date().toISOString()
|
|
183
|
+
},
|
|
184
|
+
authContext: { jwt }
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return {
|
|
188
|
+
principal,
|
|
189
|
+
accountId,
|
|
190
|
+
db: userDb,
|
|
191
|
+
requestId
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Fallback: system principal (admin ops)
|
|
196
|
+
return {
|
|
197
|
+
principal: SYSTEM_PRINCIPAL,
|
|
198
|
+
accountId,
|
|
199
|
+
db: adminDb,
|
|
200
|
+
requestId
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
// ─── CHUNK_END: CLI_CONTEXT_BUILD ────────────────────────────────────────────────
|
|
204
|
+
|
|
205
|
+
// ─── OUTPUT UTILITIES ─────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
// ─── CHUNK_START: CLI_CONTEXT_PRINT_RESULT ──────────────────────────────────────────────
|
|
208
|
+
/**
|
|
209
|
+
* @chunk-id CLI_CONTEXT_PRINT_RESULT_1_0_0
|
|
210
|
+
* @version 1.0.0
|
|
211
|
+
* @hash 06e492defa9012290a5c8b2d4fca27f01df11bdc194ffbdf4f4b4518aa683c2b
|
|
212
|
+
* @macro CLI Output Formatter
|
|
213
|
+
* @micro Pretty-prints query results as JSON or ASCII tables
|
|
214
|
+
* @inputs data: any — Result data to display (array or single value)
|
|
215
|
+
* @inputs opts: { json?: boolean } — Output format options
|
|
216
|
+
* @outputs void — Console output only
|
|
217
|
+
* @depends-on [console, JSON]
|
|
218
|
+
* @depended-by [All CLI command list/get handlers]
|
|
219
|
+
* @side-effects [console.log output]
|
|
220
|
+
* @tags cli, output, formatting, tables, json
|
|
221
|
+
*/
|
|
222
|
+
export function printResult(data: any, opts: { json?: boolean } = {}) {
|
|
223
|
+
if (opts.json) {
|
|
224
|
+
console.log(JSON.stringify(data, null, 2))
|
|
225
|
+
return
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (Array.isArray(data)) {
|
|
229
|
+
if (data.length === 0) {
|
|
230
|
+
console.log('(no results)')
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
const keys = Object.keys(data[0])
|
|
234
|
+
const rows = data.map(row => keys.map(k => String(row[k] ?? '')))
|
|
235
|
+
const widths = keys.map((k, i) => Math.max(k.length, ...rows.map(r => r[i].length)))
|
|
236
|
+
const hr = widths.map(w => '-'.repeat(w)).join(' ')
|
|
237
|
+
console.log(keys.map((k, i) => k.padEnd(widths[i])).join(' '))
|
|
238
|
+
console.log(hr)
|
|
239
|
+
rows.forEach(row => console.log(row.map((v, i) => v.padEnd(widths[i])).join(' ')))
|
|
240
|
+
console.log(`\n(${data.length} row${data.length !== 1 ? 's' : ''})`)
|
|
241
|
+
} else {
|
|
242
|
+
console.log(JSON.stringify(data, null, 2))
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
// ─── CHUNK_END: CLI_CONTEXT_PRINT_RESULT ────────────────────────────────────────────────
|
|
246
|
+
|
|
247
|
+
// ─── CHUNK_START: CLI_CONTEXT_HANDLE_ERROR ──────────────────────────────────────────────
|
|
248
|
+
/**
|
|
249
|
+
* @chunk-id CLI_CONTEXT_HANDLE_ERROR_1_0_0
|
|
250
|
+
* @version 1.0.0
|
|
251
|
+
* @hash 61aca7e4bce61b5b790e8f27ef3f505a6a6fe9a8cd6643e577f610d9dd15b1cd
|
|
252
|
+
* @macro CLI Error Handler
|
|
253
|
+
* @micro Prints formatted error and exits with proper debug information
|
|
254
|
+
* @inputs err: any — Error object or string message
|
|
255
|
+
* @outputs void — Process termination (exit code 1)
|
|
256
|
+
* @depends-on [console, process]
|
|
257
|
+
* @depended-by [All CLI command action handlers]
|
|
258
|
+
* @side-effects [console.error output, process.exit]
|
|
259
|
+
* @tags cli, error-handling, debug, process-exit
|
|
260
|
+
*/
|
|
261
|
+
export function handleError(err: any) {
|
|
262
|
+
console.error(`\nError: ${err.message || err}`)
|
|
263
|
+
if (process.env.SPINE_CLI_DEBUG) {
|
|
264
|
+
console.error(err.stack)
|
|
265
|
+
}
|
|
266
|
+
process.exit(1)
|
|
267
|
+
}
|
|
268
|
+
// ─── CHUNK_END: CLI_CONTEXT_HANDLE_ERROR ────────────────────────────────────────────────
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/env-loader
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* Side-effect module: loads `v2-core/.xenv` into `process.env` before any
|
|
9
|
+
* other CLI module is imported. Must be the first import in `cli/index.ts`
|
|
10
|
+
* so that `db.ts` (which reads env vars at module-load time) sees the values.
|
|
11
|
+
*
|
|
12
|
+
* @sideEffects Mutates process.env — only sets keys not already present
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { readFileSync, existsSync } from 'fs'
|
|
16
|
+
import { resolve, dirname } from 'path'
|
|
17
|
+
import { fileURLToPath } from 'url'
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
20
|
+
const __dirname = dirname(__filename)
|
|
21
|
+
|
|
22
|
+
const envPath = resolve(__dirname, '../.xenv')
|
|
23
|
+
if (existsSync(envPath)) {
|
|
24
|
+
const lines = readFileSync(envPath, 'utf8').split('\n')
|
|
25
|
+
for (const line of lines) {
|
|
26
|
+
const trimmed = line.trim()
|
|
27
|
+
if (!trimmed || trimmed.startsWith('#')) continue
|
|
28
|
+
const eqIdx = trimmed.indexOf('=')
|
|
29
|
+
if (eqIdx === -1) continue
|
|
30
|
+
const key = trimmed.slice(0, eqIdx).trim()
|
|
31
|
+
const value = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '')
|
|
32
|
+
if (!process.env[key]) {
|
|
33
|
+
process.env[key] = value
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|