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,391 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/commands/install-app
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* `spine-framework install-app <slug>` — Install an app into the current Spine instance.
|
|
9
|
+
*
|
|
10
|
+
* Reads seed/*.json files from the app package directory and upserts their
|
|
11
|
+
* contents into the database. Also registers the app in `app_installations`.
|
|
12
|
+
*
|
|
13
|
+
* **Seed file conventions:**
|
|
14
|
+
* - `types.json` → upsert into `types` table
|
|
15
|
+
* - `link-types.json` → upsert into `link_types` table
|
|
16
|
+
* - `triggers.json` → upsert into `triggers` table
|
|
17
|
+
* - `accounts.json` → upsert into `accounts` table
|
|
18
|
+
* - `pipelines.json` → upsert into `pipelines` table
|
|
19
|
+
*
|
|
20
|
+
* All upserts keyed on `(app_id, slug)` — idempotent and safe to re-run.
|
|
21
|
+
*
|
|
22
|
+
* **Usage:**
|
|
23
|
+
* ```bash
|
|
24
|
+
* spine-framework install-app cortex
|
|
25
|
+
* spine-framework install-app customer-portal
|
|
26
|
+
* spine-framework install-app cortex --account <account-id>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { Command } from 'commander'
|
|
31
|
+
import { existsSync, readFileSync, readdirSync } 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
|
+
|
|
40
|
+
interface InstallOptions {
|
|
41
|
+
account?: string
|
|
42
|
+
force: boolean
|
|
43
|
+
dryRun: boolean
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
interface SeedResult {
|
|
47
|
+
table: string
|
|
48
|
+
inserted: number
|
|
49
|
+
skipped: number
|
|
50
|
+
errors: string[]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function resolveAppId(slug: string): Promise<string | null> {
|
|
54
|
+
const { data } = await adminDb
|
|
55
|
+
.from('apps')
|
|
56
|
+
.select('id')
|
|
57
|
+
.eq('slug', slug)
|
|
58
|
+
.single()
|
|
59
|
+
|
|
60
|
+
return data?.id || null
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function ensureAppRecord(slug: string, name: string): Promise<string> {
|
|
64
|
+
let appId = await resolveAppId(slug)
|
|
65
|
+
|
|
66
|
+
if (!appId) {
|
|
67
|
+
// Read manifest for display name
|
|
68
|
+
const { error: insertErr, data } = await adminDb
|
|
69
|
+
.from('apps')
|
|
70
|
+
.insert({
|
|
71
|
+
slug,
|
|
72
|
+
name,
|
|
73
|
+
route_prefix: `/${slug}`,
|
|
74
|
+
renderer: 'custom',
|
|
75
|
+
is_active: true,
|
|
76
|
+
is_system: false,
|
|
77
|
+
})
|
|
78
|
+
.select('id')
|
|
79
|
+
.single()
|
|
80
|
+
|
|
81
|
+
if (insertErr) {
|
|
82
|
+
throw new Error(`Failed to create app record: ${insertErr.message}`)
|
|
83
|
+
}
|
|
84
|
+
appId = data.id
|
|
85
|
+
console.log(` ✓ Created app record for '${slug}'`)
|
|
86
|
+
} else {
|
|
87
|
+
console.log(` ⏭️ App '${slug}' already in database`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return appId
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function upsertTypes(appId: string, records: any[], dryRun: boolean): Promise<SeedResult> {
|
|
94
|
+
const result: SeedResult = { table: 'types', inserted: 0, skipped: 0, errors: [] }
|
|
95
|
+
|
|
96
|
+
for (const record of records) {
|
|
97
|
+
if (dryRun) {
|
|
98
|
+
console.log(` [dry-run] Would upsert type: ${record.slug}`)
|
|
99
|
+
result.inserted++
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const { error } = await adminDb
|
|
104
|
+
.from('types')
|
|
105
|
+
.upsert({
|
|
106
|
+
...record,
|
|
107
|
+
app_id: appId,
|
|
108
|
+
}, {
|
|
109
|
+
onConflict: 'app_id,kind,slug',
|
|
110
|
+
ignoreDuplicates: false
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
if (error) {
|
|
114
|
+
result.errors.push(`${record.slug}: ${error.message}`)
|
|
115
|
+
} else {
|
|
116
|
+
result.inserted++
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return result
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function upsertLinkTypes(appId: string, records: any[], dryRun: boolean): Promise<SeedResult> {
|
|
124
|
+
const result: SeedResult = { table: 'link_types', inserted: 0, skipped: 0, errors: [] }
|
|
125
|
+
|
|
126
|
+
for (const record of records) {
|
|
127
|
+
if (dryRun) {
|
|
128
|
+
console.log(` [dry-run] Would upsert link type: ${record.slug}`)
|
|
129
|
+
result.inserted++
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const { error } = await adminDb
|
|
134
|
+
.from('link_types')
|
|
135
|
+
.upsert({
|
|
136
|
+
...record,
|
|
137
|
+
app_id: appId,
|
|
138
|
+
}, {
|
|
139
|
+
onConflict: 'app_id,slug',
|
|
140
|
+
ignoreDuplicates: false
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
if (error) {
|
|
144
|
+
result.errors.push(`${record.slug}: ${error.message}`)
|
|
145
|
+
} else {
|
|
146
|
+
result.inserted++
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
return result
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function upsertTriggers(appId: string, records: any[], dryRun: boolean): Promise<SeedResult> {
|
|
154
|
+
const result: SeedResult = { table: 'triggers', inserted: 0, skipped: 0, errors: [] }
|
|
155
|
+
|
|
156
|
+
for (const record of records) {
|
|
157
|
+
if (dryRun) {
|
|
158
|
+
console.log(` [dry-run] Would upsert trigger: ${record.name}`)
|
|
159
|
+
result.inserted++
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const { error } = await adminDb
|
|
164
|
+
.from('triggers')
|
|
165
|
+
.upsert({
|
|
166
|
+
...record,
|
|
167
|
+
app_id: appId,
|
|
168
|
+
}, {
|
|
169
|
+
onConflict: 'app_id,name',
|
|
170
|
+
ignoreDuplicates: false
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
if (error) {
|
|
174
|
+
result.errors.push(`${record.name}: ${error.message}`)
|
|
175
|
+
} else {
|
|
176
|
+
result.inserted++
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return result
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function upsertAccounts(appId: string, records: any[], dryRun: boolean): Promise<SeedResult> {
|
|
184
|
+
const result: SeedResult = { table: 'accounts', inserted: 0, skipped: 0, errors: [] }
|
|
185
|
+
|
|
186
|
+
for (const record of records) {
|
|
187
|
+
if (dryRun) {
|
|
188
|
+
console.log(` [dry-run] Would upsert account: ${record.slug}`)
|
|
189
|
+
result.inserted++
|
|
190
|
+
continue
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Resolve type_slug → type_id if present
|
|
194
|
+
const row = { ...record, app_id: appId }
|
|
195
|
+
if (row.type_slug && !row.type_id) {
|
|
196
|
+
const { data: typeData } = await adminDb
|
|
197
|
+
.from('types')
|
|
198
|
+
.select('id')
|
|
199
|
+
.eq('kind', 'account')
|
|
200
|
+
.eq('slug', row.type_slug)
|
|
201
|
+
.limit(1)
|
|
202
|
+
.single()
|
|
203
|
+
if (typeData) {
|
|
204
|
+
row.type_id = typeData.id
|
|
205
|
+
} else {
|
|
206
|
+
result.errors.push(`${record.slug}: type_slug '${row.type_slug}' not found`)
|
|
207
|
+
continue
|
|
208
|
+
}
|
|
209
|
+
delete row.type_slug
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const { error } = await adminDb
|
|
213
|
+
.from('accounts')
|
|
214
|
+
.upsert(row, {
|
|
215
|
+
onConflict: 'slug',
|
|
216
|
+
ignoreDuplicates: false
|
|
217
|
+
})
|
|
218
|
+
|
|
219
|
+
if (error) {
|
|
220
|
+
result.errors.push(`${record.slug}: ${error.message}`)
|
|
221
|
+
} else {
|
|
222
|
+
result.inserted++
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return result
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
async function upsertPipelines(appId: string, records: any[], dryRun: boolean): Promise<SeedResult> {
|
|
230
|
+
const result: SeedResult = { table: 'pipelines', inserted: 0, skipped: 0, errors: [] }
|
|
231
|
+
|
|
232
|
+
for (const record of records) {
|
|
233
|
+
if (dryRun) {
|
|
234
|
+
console.log(` [dry-run] Would upsert pipeline: ${record.slug}`)
|
|
235
|
+
result.inserted++
|
|
236
|
+
continue
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const { error } = await adminDb
|
|
240
|
+
.from('pipelines')
|
|
241
|
+
.upsert({
|
|
242
|
+
...record,
|
|
243
|
+
app_id: appId,
|
|
244
|
+
}, {
|
|
245
|
+
onConflict: 'app_id,name',
|
|
246
|
+
ignoreDuplicates: false
|
|
247
|
+
})
|
|
248
|
+
|
|
249
|
+
if (error) {
|
|
250
|
+
result.errors.push(`${record.slug}: ${error.message}`)
|
|
251
|
+
} else {
|
|
252
|
+
result.inserted++
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return result
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const SEED_HANDLERS: Record<string, (appId: string, records: any[], dryRun: boolean) => Promise<SeedResult>> = {
|
|
260
|
+
'types.json': upsertTypes,
|
|
261
|
+
'link-types.json': upsertLinkTypes,
|
|
262
|
+
'triggers.json': upsertTriggers,
|
|
263
|
+
'accounts.json': upsertAccounts,
|
|
264
|
+
'pipelines.json': upsertPipelines,
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async function recordInstallation(appSlug: string, accountId: string | null, dryRun: boolean): Promise<void> {
|
|
268
|
+
if (dryRun) {
|
|
269
|
+
console.log(` [dry-run] Would record installation for '${appSlug}'`)
|
|
270
|
+
return
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const { error } = await adminDb
|
|
274
|
+
.from('app_installations')
|
|
275
|
+
.upsert({
|
|
276
|
+
app_slug: appSlug,
|
|
277
|
+
account_id: accountId,
|
|
278
|
+
is_enabled: true,
|
|
279
|
+
updated_at: new Date().toISOString(),
|
|
280
|
+
}, {
|
|
281
|
+
onConflict: 'app_slug,account_id',
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
if (error) {
|
|
285
|
+
console.log(` ⚠️ Failed to record installation: ${error.message}`)
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
async function installApp(slug: string, options: InstallOptions): Promise<void> {
|
|
290
|
+
console.log(`\n📦 Installing app '${slug}'...\n`)
|
|
291
|
+
|
|
292
|
+
// 1. Find the app directory
|
|
293
|
+
const appDir = resolve(PROJECT_ROOT, `custom/apps/${slug}`)
|
|
294
|
+
if (!existsSync(appDir)) {
|
|
295
|
+
console.error(` ❌ App directory not found: custom/apps/${slug}`)
|
|
296
|
+
console.error(` Install the npm package first or create the app with 'spine-framework create-app ${slug}'`)
|
|
297
|
+
process.exit(1)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 2. Read manifest for display name
|
|
301
|
+
const manifestPath = resolve(appDir, 'manifest.json')
|
|
302
|
+
let appName = slug
|
|
303
|
+
if (existsSync(manifestPath)) {
|
|
304
|
+
try {
|
|
305
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'))
|
|
306
|
+
appName = manifest.name || slug
|
|
307
|
+
console.log(` 📋 Manifest: ${appName} v${manifest.version || '0.0.0'}`)
|
|
308
|
+
} catch {
|
|
309
|
+
console.log(` ⚠️ Could not parse manifest.json, using slug as name`)
|
|
310
|
+
}
|
|
311
|
+
} else {
|
|
312
|
+
console.log(` ⚠️ No manifest.json found, using slug as name`)
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// 3. Ensure app record in DB
|
|
316
|
+
const appId = await ensureAppRecord(slug, appName)
|
|
317
|
+
|
|
318
|
+
// 4. Process seed files
|
|
319
|
+
const seedDir = resolve(appDir, 'seed')
|
|
320
|
+
if (!existsSync(seedDir)) {
|
|
321
|
+
console.log(` ⏭️ No seed/ directory — skipping data seeding`)
|
|
322
|
+
} else {
|
|
323
|
+
console.log(`\n🌱 Applying seed data...`)
|
|
324
|
+
const seedFiles = readdirSync(seedDir).filter(f => f.endsWith('.json'))
|
|
325
|
+
|
|
326
|
+
let totalInserted = 0
|
|
327
|
+
let totalErrors = 0
|
|
328
|
+
|
|
329
|
+
for (const file of seedFiles) {
|
|
330
|
+
const handler = SEED_HANDLERS[file]
|
|
331
|
+
if (!handler) {
|
|
332
|
+
console.log(` ⏭️ Skipping unknown seed file: ${file}`)
|
|
333
|
+
continue
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const filePath = resolve(seedDir, file)
|
|
337
|
+
let records: any[]
|
|
338
|
+
try {
|
|
339
|
+
records = JSON.parse(readFileSync(filePath, 'utf8'))
|
|
340
|
+
if (!Array.isArray(records)) {
|
|
341
|
+
console.log(` ⚠️ ${file}: expected array, skipping`)
|
|
342
|
+
continue
|
|
343
|
+
}
|
|
344
|
+
} catch (err: any) {
|
|
345
|
+
console.error(` ❌ ${file}: parse error — ${err.message}`)
|
|
346
|
+
continue
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
console.log(` 📄 ${file} (${records.length} records)...`)
|
|
350
|
+
const result = await handler(appId, records, options.dryRun)
|
|
351
|
+
|
|
352
|
+
if (result.errors.length > 0) {
|
|
353
|
+
for (const e of result.errors) {
|
|
354
|
+
console.error(` ❌ ${e}`)
|
|
355
|
+
}
|
|
356
|
+
totalErrors += result.errors.length
|
|
357
|
+
}
|
|
358
|
+
totalInserted += result.inserted
|
|
359
|
+
console.log(` ✓ ${result.inserted} upserted`)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
console.log(`\n Total: ${totalInserted} records upserted, ${totalErrors} errors`)
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// 5. Record installation
|
|
366
|
+
console.log(`\n📝 Recording installation...`)
|
|
367
|
+
await recordInstallation(slug, options.account || null, options.dryRun)
|
|
368
|
+
|
|
369
|
+
console.log(`\n✅ App '${appName}' installed successfully!`)
|
|
370
|
+
console.log(`\n Next steps:`)
|
|
371
|
+
console.log(` 1. npm run assemble && netlify dev`)
|
|
372
|
+
console.log(` 2. Navigate to /${slug} in your browser`)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
export function registerInstallAppCommands(program: Command) {
|
|
376
|
+
program
|
|
377
|
+
.command('install-app <slug>')
|
|
378
|
+
.description('Install an app by applying its seed data to the database')
|
|
379
|
+
.option('--account <id>', 'Account ID to associate the installation with')
|
|
380
|
+
.option('--force', 'Overwrite existing seed data', false)
|
|
381
|
+
.option('--dry-run', 'Show what would happen without making changes', false)
|
|
382
|
+
.action(async (slug, opts) => {
|
|
383
|
+
try {
|
|
384
|
+
await installApp(slug, opts)
|
|
385
|
+
} catch (err: any) {
|
|
386
|
+
console.error('Error:', err.message)
|
|
387
|
+
if (process.env.SPINE_CLI_DEBUG) console.error(err.stack)
|
|
388
|
+
process.exit(1)
|
|
389
|
+
}
|
|
390
|
+
})
|
|
391
|
+
}
|
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
/**
|
|
3
|
+
* @module cli/commands/items
|
|
4
|
+
* @audience installer
|
|
5
|
+
* @layer cli
|
|
6
|
+
* @stability stable
|
|
7
|
+
*
|
|
8
|
+
* `spine items` command group. Direct CRUD access to the `items` table via
|
|
9
|
+
* `adminDb` (service-role, bypasses RLS). Use only in controlled environments;
|
|
10
|
+
* does NOT enforce field-level permissions or RLS for the principal.
|
|
11
|
+
*
|
|
12
|
+
* **Commands:**
|
|
13
|
+
* | Subcommand | Description |
|
|
14
|
+
* |------------------------------------------------|--------------------------------|
|
|
15
|
+
* | `items list [--type <slug>] [--account <id>]` | List items, filtered by type |
|
|
16
|
+
* | `items get <id>` | Fetch a single item by UUID |
|
|
17
|
+
* | `items create --type <slug> --data <json>` | Insert a new item |
|
|
18
|
+
* | `items update <id> --data <json>` | Patch item data fields |
|
|
19
|
+
* | `items delete <id> [--hard]` | Soft-delete or hard-delete |
|
|
20
|
+
*
|
|
21
|
+
* **Authorization note:** All commands use `adminDb` — no RLS enforcement.
|
|
22
|
+
* Account scoping is applied as a filter only; it does not restrict access.
|
|
23
|
+
*
|
|
24
|
+
* **Usage:**
|
|
25
|
+
* ```bash
|
|
26
|
+
* spine items list --type support_ticket --account <id> --limit 50
|
|
27
|
+
* spine items get <uuid>
|
|
28
|
+
* spine items create --type support_ticket --title "Bug report" --data '{"priority":"high"}'
|
|
29
|
+
* spine items update <uuid> --data '{"status":"resolved"}'
|
|
30
|
+
* spine items delete <uuid>
|
|
31
|
+
* spine items delete <uuid> --hard
|
|
32
|
+
* ```
|
|
33
|
+
*
|
|
34
|
+
* @seeAlso cli/context.ts (buildCliContext)
|
|
35
|
+
* @seeAlso functions/admin-data.ts (API equivalent with RLS)
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import type { Command } from 'commander'
|
|
39
|
+
import { buildCliContext, printResult, handleError } from '../context.ts'
|
|
40
|
+
import { adminDb } from '../../functions/_shared/index.ts'
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Registers the `items` subcommand group on the root Commander program.
|
|
44
|
+
*
|
|
45
|
+
* @param program - The root `spine` Commander instance
|
|
46
|
+
* @sideEffects Adds `items list/get/create/update/delete` subcommands to `program`
|
|
47
|
+
* @calledBy cli/index.ts
|
|
48
|
+
*/
|
|
49
|
+
export function registerItemCommands(program: Command) {
|
|
50
|
+
const items = program
|
|
51
|
+
.command('items')
|
|
52
|
+
.description('Item record management')
|
|
53
|
+
|
|
54
|
+
items
|
|
55
|
+
.command('list')
|
|
56
|
+
.description('List items, optionally filtered by type')
|
|
57
|
+
.option('--type <slug>', 'Filter by item type slug')
|
|
58
|
+
.option('--account <id>', 'Account scope')
|
|
59
|
+
.option('--limit <n>', 'Max results', '20')
|
|
60
|
+
.option('--json', 'Output as JSON')
|
|
61
|
+
.action(async (opts) => {
|
|
62
|
+
try {
|
|
63
|
+
const ctx = await buildCliContext({ account: opts.account })
|
|
64
|
+
|
|
65
|
+
let query = adminDb
|
|
66
|
+
.from('items')
|
|
67
|
+
.select('id, type_id, title, status, is_active, created_at')
|
|
68
|
+
.order('created_at', { ascending: false })
|
|
69
|
+
.limit(parseInt(opts.limit))
|
|
70
|
+
|
|
71
|
+
if (ctx.accountId) {
|
|
72
|
+
query = query.eq('account_id', ctx.accountId)
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (opts.type) {
|
|
76
|
+
const { data: typeRecord } = await adminDb
|
|
77
|
+
.from('types')
|
|
78
|
+
.select('id')
|
|
79
|
+
.eq('slug', opts.type)
|
|
80
|
+
.single()
|
|
81
|
+
|
|
82
|
+
if (!typeRecord) throw new Error(`Item type not found: ${opts.type}`)
|
|
83
|
+
query = query.eq('type_id', typeRecord.id)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const { data, error } = await query
|
|
87
|
+
if (error) throw new Error(error.message)
|
|
88
|
+
printResult(data || [], { json: opts.json })
|
|
89
|
+
} catch (err: any) {
|
|
90
|
+
handleError(err)
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
items
|
|
95
|
+
.command('get <id>')
|
|
96
|
+
.description('Get a single item by ID')
|
|
97
|
+
.option('--json', 'Output as JSON')
|
|
98
|
+
.action(async (id, opts) => {
|
|
99
|
+
try {
|
|
100
|
+
await buildCliContext()
|
|
101
|
+
|
|
102
|
+
const { data, error } = await adminDb
|
|
103
|
+
.from('items')
|
|
104
|
+
.select('*')
|
|
105
|
+
.eq('id', id)
|
|
106
|
+
.single()
|
|
107
|
+
|
|
108
|
+
if (error || !data) throw new Error(error?.message || `Item not found: ${id}`)
|
|
109
|
+
printResult(data, { json: opts.json })
|
|
110
|
+
} catch (err: any) {
|
|
111
|
+
handleError(err)
|
|
112
|
+
}
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
items
|
|
116
|
+
.command('create')
|
|
117
|
+
.description('Create a new item')
|
|
118
|
+
.requiredOption('--type <slug>', 'Item type slug')
|
|
119
|
+
.option('--data <json>', 'Item data as JSON', '{}')
|
|
120
|
+
.option('--title <title>', 'Item title')
|
|
121
|
+
.option('--account <id>', 'Account scope')
|
|
122
|
+
.option('--json', 'Output as JSON')
|
|
123
|
+
.action(async (opts) => {
|
|
124
|
+
try {
|
|
125
|
+
const ctx = await buildCliContext({ account: opts.account })
|
|
126
|
+
|
|
127
|
+
if (!ctx.accountId) {
|
|
128
|
+
throw new Error('--account or SPINE_CLI_ACCOUNT_ID required to create items')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const { data: typeRecord, error: typeError } = await adminDb
|
|
132
|
+
.from('types')
|
|
133
|
+
.select('id')
|
|
134
|
+
.eq('slug', opts.type)
|
|
135
|
+
.single()
|
|
136
|
+
|
|
137
|
+
if (typeError || !typeRecord) throw new Error(`Item type not found: ${opts.type}`)
|
|
138
|
+
|
|
139
|
+
let itemData: any = {}
|
|
140
|
+
try {
|
|
141
|
+
itemData = JSON.parse(opts.data)
|
|
142
|
+
} catch {
|
|
143
|
+
throw new Error(`--data must be valid JSON. Got: ${opts.data}`)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const { data, error } = await adminDb
|
|
147
|
+
.from('items')
|
|
148
|
+
.insert({
|
|
149
|
+
type_id: typeRecord.id,
|
|
150
|
+
account_id: ctx.accountId,
|
|
151
|
+
title: opts.title || itemData.title || null,
|
|
152
|
+
data: itemData,
|
|
153
|
+
is_active: true,
|
|
154
|
+
created_by: ctx.principal.id,
|
|
155
|
+
created_at: new Date().toISOString(),
|
|
156
|
+
updated_at: new Date().toISOString()
|
|
157
|
+
})
|
|
158
|
+
.select()
|
|
159
|
+
.single()
|
|
160
|
+
|
|
161
|
+
if (error) throw new Error(error.message)
|
|
162
|
+
if (opts.json) {
|
|
163
|
+
console.log(JSON.stringify(data, null, 2))
|
|
164
|
+
} else {
|
|
165
|
+
console.log(`✓ Created item ${data.id}`)
|
|
166
|
+
printResult(data, { json: false })
|
|
167
|
+
}
|
|
168
|
+
} catch (err: any) {
|
|
169
|
+
handleError(err)
|
|
170
|
+
}
|
|
171
|
+
})
|
|
172
|
+
|
|
173
|
+
items
|
|
174
|
+
.command('update <id>')
|
|
175
|
+
.description('Update item data fields')
|
|
176
|
+
.option('--data <json>', 'Partial item data as JSON', '{}')
|
|
177
|
+
.option('--title <title>', 'Update item title')
|
|
178
|
+
.option('--json', 'Output as JSON')
|
|
179
|
+
.action(async (id, opts) => {
|
|
180
|
+
try {
|
|
181
|
+
const ctx = await buildCliContext()
|
|
182
|
+
|
|
183
|
+
let patchData: any = {}
|
|
184
|
+
try {
|
|
185
|
+
patchData = JSON.parse(opts.data)
|
|
186
|
+
} catch {
|
|
187
|
+
throw new Error(`--data must be valid JSON`)
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const updates: any = {
|
|
191
|
+
updated_at: new Date().toISOString(),
|
|
192
|
+
updated_by: ctx.principal.id
|
|
193
|
+
}
|
|
194
|
+
if (opts.title) updates.title = opts.title
|
|
195
|
+
if (Object.keys(patchData).length > 0) updates.data = patchData
|
|
196
|
+
|
|
197
|
+
const { data, error } = await adminDb
|
|
198
|
+
.from('items')
|
|
199
|
+
.update(updates)
|
|
200
|
+
.eq('id', id)
|
|
201
|
+
.select()
|
|
202
|
+
.single()
|
|
203
|
+
|
|
204
|
+
if (error) throw new Error(error.message)
|
|
205
|
+
if (opts.json) {
|
|
206
|
+
console.log(JSON.stringify(data, null, 2))
|
|
207
|
+
} else {
|
|
208
|
+
console.log(`✓ Updated item ${id}`)
|
|
209
|
+
printResult(data, { json: false })
|
|
210
|
+
}
|
|
211
|
+
} catch (err: any) {
|
|
212
|
+
handleError(err)
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
items
|
|
217
|
+
.command('delete <id>')
|
|
218
|
+
.description('Soft-delete an item (sets is_active = false)')
|
|
219
|
+
.option('--hard', 'Hard delete — permanently removes the record')
|
|
220
|
+
.option('--json', 'Output as JSON')
|
|
221
|
+
.action(async (id, opts) => {
|
|
222
|
+
try {
|
|
223
|
+
const ctx = await buildCliContext()
|
|
224
|
+
|
|
225
|
+
if (opts.hard) {
|
|
226
|
+
const { error } = await adminDb
|
|
227
|
+
.from('items')
|
|
228
|
+
.delete()
|
|
229
|
+
.eq('id', id)
|
|
230
|
+
|
|
231
|
+
if (error) throw new Error(error.message)
|
|
232
|
+
console.log(`✓ Hard-deleted item ${id}`)
|
|
233
|
+
} else {
|
|
234
|
+
const { data, error } = await adminDb
|
|
235
|
+
.from('items')
|
|
236
|
+
.update({
|
|
237
|
+
is_active: false,
|
|
238
|
+
updated_at: new Date().toISOString(),
|
|
239
|
+
updated_by: ctx.principal.id
|
|
240
|
+
})
|
|
241
|
+
.eq('id', id)
|
|
242
|
+
.select('id, is_active')
|
|
243
|
+
.single()
|
|
244
|
+
|
|
245
|
+
if (error) throw new Error(error.message)
|
|
246
|
+
const out = opts.json ? JSON.stringify(data) : `✓ Soft-deleted item ${id}`
|
|
247
|
+
console.log(out)
|
|
248
|
+
}
|
|
249
|
+
} catch (err: any) {
|
|
250
|
+
handleError(err)
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
}
|