spine-framework 0.3.145 → 0.4.1
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/.env.example +76 -0
- package/.framework/.spine-manifest.json +19 -0
- package/.framework/cli/commands/agents.ts +61 -33
- package/.framework/cli/commands/auth.ts +60 -28
- package/.framework/cli/commands/create-app.ts +212 -80
- package/.framework/cli/commands/dev.ts +184 -68
- package/.framework/cli/commands/doctor.ts +324 -152
- package/.framework/cli/commands/generate.ts +119 -68
- package/.framework/cli/commands/init.ts +141 -242
- package/.framework/cli/commands/install-app.ts +1336 -314
- package/.framework/cli/commands/items.ts +31 -29
- package/.framework/cli/commands/migrate.ts +299 -156
- package/.framework/cli/commands/migrations.ts +62 -30
- package/.framework/cli/commands/pipelines.ts +31 -29
- package/.framework/cli/commands/status.ts +105 -45
- package/.framework/cli/commands/system.ts +47 -42
- package/.framework/cli/commands/test.ts +161 -102
- package/.framework/cli/commands/upgrade.ts +260 -0
- package/.framework/cli/commands/validate-app.ts +517 -161
- package/.framework/cli/context.ts +117 -85
- package/.framework/cli/env-loader.ts +74 -43
- package/.framework/cli/index.ts +70 -46
- package/.framework/cli/lib/project-files.ts +404 -0
- package/.framework/docs/guides/testing-guide.md +1 -1
- package/.framework/docs/runtime-types.md +72 -72
- package/.framework/functions/_shared/access.ts +84 -0
- package/.framework/functions/_shared/agent-runner.ts +5067 -1026
- package/.framework/functions/_shared/app-manifest.ts +214 -94
- package/.framework/functions/_shared/ast.ts +214 -0
- package/.framework/functions/_shared/audit.ts +91 -55
- package/.framework/functions/_shared/cil.ts +368 -127
- package/.framework/functions/_shared/db.ts +337 -68
- package/.framework/functions/_shared/funnel.ts +228 -0
- package/.framework/functions/_shared/geo.ts +105 -0
- package/.framework/functions/_shared/hash-api-key.ts +94 -17
- package/.framework/functions/_shared/index.ts +22 -224
- package/.framework/functions/_shared/kb.ts +832 -58
- package/.framework/functions/_shared/llm.ts +118 -18
- package/.framework/functions/_shared/logging.ts +317 -19
- package/.framework/functions/_shared/middleware.ts +701 -131
- package/.framework/functions/_shared/notify.ts +70 -0
- package/.framework/functions/_shared/outcomes.ts +34 -19
- package/.framework/functions/_shared/permissions.ts +501 -315
- package/.framework/functions/_shared/pipeline-runner.ts +704 -155
- package/.framework/functions/_shared/principal.ts +436 -263
- package/.framework/functions/_shared/rate-limit.ts +233 -0
- package/.framework/functions/_shared/resolve-credentials.ts +86 -19
- package/.framework/functions/_shared/resolve-ids.ts +203 -139
- package/.framework/functions/_shared/retry-deliveries.ts +51 -16
- package/.framework/functions/_shared/schema-utils.ts +602 -521
- package/.framework/functions/_shared/security.ts +345 -0
- package/.framework/functions/_shared/testing.ts +197 -172
- package/.framework/functions/_shared/tracing.ts +351 -21
- package/.framework/functions/_shared/trigger-engine.ts +425 -136
- package/.framework/functions/_shared/webhook-registration.ts +71 -70
- package/.framework/functions/_shared/webhook-registry.ts +126 -83
- package/.framework/functions/account-nodes.ts +30 -30
- package/.framework/functions/adapters/generic-webhook-inbound.ts +49 -16
- package/.framework/functions/adapters/generic-webhook-outbound.ts +17 -17
- package/.framework/functions/adapters/openai-outbound.ts +15 -16
- package/.framework/functions/admin-data.ts +596 -173
- package/.framework/functions/admin-funnel.ts +64 -60
- package/.framework/functions/admin-install-funnel.ts +84 -66
- package/.framework/functions/admin-signals.ts +69 -51
- package/.framework/functions/ai-agents.ts +431 -106
- package/.framework/functions/ai-interactions.ts +186 -112
- package/.framework/functions/api-keys.ts +107 -107
- package/.framework/functions/apps.ts +154 -136
- package/.framework/functions/auth.ts +83 -65
- package/.framework/functions/cil-agent-ingest.ts +189 -232
- package/.framework/functions/cil-agent.ts +433 -177
- package/.framework/functions/cil-callmap.ts +642 -430
- package/.framework/functions/cil-config.ts +139 -135
- package/.framework/functions/cil-context.ts +214 -127
- package/.framework/functions/cil-doc.ts +587 -458
- package/.framework/functions/cil-embeddings.ts +453 -364
- package/.framework/functions/cil-graph.ts +427 -142
- package/.framework/functions/cil-ingest.ts +1418 -1154
- package/.framework/functions/cil-pipeline.ts +276 -21
- package/.framework/functions/cil-project.ts +659 -132
- package/.framework/functions/cil-validate.ts +198 -164
- package/.framework/functions/debug-auth.ts +57 -55
- package/.framework/functions/deliveries.ts +80 -21
- package/.framework/functions/embeddings.ts +1023 -318
- package/.framework/functions/eval-harness.ts +146 -108
- package/.framework/functions/integration-outbound.ts +205 -75
- package/.framework/functions/integration-routes.ts +128 -23
- package/.framework/functions/integrations.ts +125 -100
- package/.framework/functions/invites.ts +1095 -460
- package/.framework/functions/item-progress.ts +158 -102
- package/.framework/functions/kb.ts +34 -10
- package/.framework/functions/logs.ts +133 -133
- package/.framework/functions/observability.ts +152 -122
- package/.framework/functions/pipeline-executions.ts +147 -132
- package/.framework/functions/pipelines.ts +134 -119
- package/.framework/functions/prompt-configs.ts +96 -80
- package/.framework/functions/roles.ts +112 -82
- package/.framework/functions/security.ts +450 -0
- package/.framework/functions/system-cron.ts +117 -80
- package/.framework/functions/system.ts +158 -98
- package/.framework/functions/tests.ts +29 -29
- package/.framework/functions/timers.ts +94 -95
- package/.framework/functions/trigger-scheduler.ts +10 -0
- package/.framework/functions/triggers.ts +179 -149
- package/.framework/functions/types.ts +137 -125
- package/.framework/mcp/index.ts +133 -0
- package/.framework/mcp/load-env.ts +60 -0
- package/.framework/mcp/shared/app-manifest.ts +275 -0
- package/.framework/mcp/shared/db.ts +477 -0
- package/.framework/mcp/shared/kb.ts +118 -0
- package/.framework/mcp/tools/architect.ts +431 -0
- package/.framework/mcp/tools/cil/context.ts +345 -0
- package/.framework/mcp/tools/cil/graph.ts +242 -0
- package/.framework/mcp/tools/cil/ingest.ts +1217 -0
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +693 -0
- package/.framework/mcp/tools/cil/pipeline.ts +767 -0
- package/.framework/mcp/tools/cil/project.ts +791 -0
- package/.framework/mcp/tools/cil/retrieval.ts +336 -0
- package/.framework/mcp/tools/cil/search-core.ts +454 -0
- package/.framework/mcp/tools/cil/search.ts +97 -0
- package/.framework/mcp/tools/cil/suggest.ts +186 -0
- package/.framework/mcp/tools/cil/validate.ts +1369 -0
- package/.framework/mcp/tools/kb/delete.ts +139 -0
- package/.framework/mcp/tools/kb/index.ts +52 -0
- package/.framework/mcp/tools/kb/ingest.ts +84 -0
- package/.framework/mcp/tools/kb/list-types.ts +102 -0
- package/.framework/mcp/tools/kb/publish.ts +100 -0
- package/.framework/mcp/tools/kb/reindex.ts +86 -0
- package/.framework/mcp/tools/kb/revoke.ts +85 -0
- package/.framework/mcp/tools/kb/search.ts +101 -0
- package/.framework/mcp/tools/kb/upsert.ts +125 -0
- package/.framework/mcp/tools/observability.ts +510 -0
- package/.framework/migrations/001_core_schema.sql +15 -0
- package/.framework/migrations/002_core_rls_and_functions.sql +15 -0
- package/.framework/migrations/003_core_system_data.sql +16 -1
- package/.framework/migrations/004_core_rpc_and_broker.sql +17 -1
- package/.framework/migrations/005_core_observability.sql +17 -1
- package/.framework/migrations/006_security_hardening_rls.sql +15 -0
- package/.framework/migrations/007_kb_article_list.sql +17 -1
- package/.framework/migrations/008_items_target.sql +15 -0
- package/.framework/migrations/009_roles_slug_unique.sql +15 -0
- package/.framework/migrations/010_auth_user_trigger.sql +15 -0
- package/.framework/migrations/011_spine_system_account.sql +16 -0
- package/.framework/migrations/012_service_role_grants.sql +16 -0
- package/.framework/migrations/013_get_account_apps_role_filter.sql +15 -0
- package/.framework/migrations/014_apps_required_roles.sql +16 -0
- package/.framework/migrations/015_types_kind_slug_unique.sql +15 -0
- package/.framework/migrations/016_cil_types.sql +281 -0
- package/.framework/migrations/017_cil_stable_ids.sql +37 -0
- package/.framework/migrations/018_runtime_tables.sql +129 -0
- package/.framework/migrations/019_agent_prompt_columns.sql +56 -0
- package/.framework/migrations/020_schema_reconciliation.sql +67 -0
- package/.framework/migrations/021_kb_embedding_lifecycle.sql +31 -0
- package/.framework/migrations/032_cil_status_migration.sql +73 -0
- package/.framework/migrations/033_cil_versioning.sql +73 -0
- package/.framework/migrations/034_match_embeddings_knn.sql +115 -0
- package/.framework/migrations/035_rate_limits.sql +55 -0
- package/.framework/migrations/036_demo_record_flags.sql +72 -0
- package/.framework/migrations/037_kb_types_canonical.sql +244 -0
- package/.framework/migrations/038_drop_demo_record_flags.sql +38 -0
- package/.framework/migrations/042_registration_hierarchy.sql +309 -0
- package/.framework/migrations/043_policy_actor_cleanup.sql +39 -0
- package/.framework/migrations/044_match_embeddings_filtered_knn.sql +151 -0
- package/.framework/migrations/045_security_critical_rls.sql +139 -0
- package/.framework/migrations/046_security_missing_rls.sql +79 -0
- package/.framework/migrations/047_people_updated_by.sql +27 -0
- package/.framework/migrations/048_watcher_last_seen_at.sql +24 -0
- package/.framework/migrations/049_security_blocklist.sql +148 -0
- package/.framework/migrations/050_notifications.sql +49 -0
- package/.framework/migrations/051_message_agent_fields.sql +74 -0
- package/.framework/migrations/052_registration_security.sql +421 -0
- package/.framework/migrations/053_builtin_action_seeds.sql +66 -0
- package/.framework/migrations/054_kb_ingest_lease.sql +157 -0
- package/.framework/migrations/055_type_id_drift_reconciliation.sql +33 -0
- package/.framework/migrations/056_slug_column_reconciliation.sql +38 -0
- package/.framework/migrations/057_pipelines_slug_identity.sql +14 -0
- package/.framework/migrations/058_account_data_field_declarations.sql +53 -0
- package/.framework/migrations/059_registration_placement_fixes.sql +269 -0
- package/.framework/migrations/060_account_person_record_permissions.sql +38 -0
- package/.framework/migrations/061_fk_dedup.sql +32 -0
- package/.framework/public/spine-logo.svg +10 -3
- package/.framework/scripts/app-install-cli.ts +190 -93
- package/.framework/scripts/assemble-frontend.sh +127 -44
- package/.framework/scripts/assemble-functions.sh +128 -30
- package/.framework/scripts/boundary-check.sh +6 -2
- package/.framework/scripts/build-manifest.sh +24 -68
- package/.framework/scripts/check-core-integrity.sh +15 -76
- package/.framework/scripts/checksum-manifest.mts +343 -0
- package/.framework/scripts/cil-migrate-markers.ts +403 -0
- package/.framework/scripts/fix-cil-markers.mjs +176 -0
- package/.framework/scripts/verify-integrity.sh +21 -57
- package/.framework/scripts/verify-manifest.mts +305 -0
- package/.framework/seeds/types/funnel_signal_use.json +74 -21
- package/.framework/seeds/types/spine_instance.json +73 -21
- package/.framework/src/App.tsx +118 -72
- package/.framework/src/apps/admin/index.tsx +855 -58
- package/.framework/src/components/AppWrapper.tsx +43 -45
- package/.framework/src/components/CustomAppLoader.tsx +160 -116
- package/.framework/src/components/admin/AdminListPage.tsx +46 -44
- package/.framework/src/components/admin/AdminSidebar.tsx +57 -47
- package/.framework/src/components/admin/AdminStatsCard.tsx +46 -44
- package/.framework/src/components/admin/PageInstructions.tsx +98 -2
- package/.framework/src/components/admin/SortableTableHeader.tsx +44 -45
- package/.framework/src/components/app-shell/GenericAppShell.tsx +64 -65
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +44 -47
- package/.framework/src/components/app-shell/GenericListPage.tsx +45 -49
- package/.framework/src/components/app-sidebar.tsx +42 -45
- package/.framework/src/components/auth/ProtectedRoute.tsx +48 -41
- package/.framework/src/components/auth/TurnstileWidget.tsx +27 -0
- package/.framework/src/components/kb/KBArticleEditor.tsx +231 -22
- package/.framework/src/components/kb/KBArticleList.tsx +251 -10
- package/.framework/src/components/layout/AppShell.tsx +51 -45
- package/.framework/src/components/layout/Header.tsx +42 -43
- package/.framework/src/components/layout/Layout.tsx +46 -46
- package/.framework/src/components/layout/Sidebar.tsx +196 -96
- package/.framework/src/components/runtime/DataDetailHeader.tsx +55 -30
- package/.framework/src/components/runtime/DataDetailPage.tsx +31 -269
- package/.framework/src/components/runtime/DataFilters.tsx +49 -74
- package/.framework/src/components/runtime/DataHeader.tsx +53 -32
- package/.framework/src/components/runtime/DataListPage.tsx +51 -155
- package/.framework/src/components/runtime/DataStats.tsx +66 -44
- package/.framework/src/components/runtime/DataTable.tsx +49 -149
- package/.framework/src/components/runtime/SchemaDetailForm.tsx +45 -75
- package/.framework/src/components/runtime/index.ts +119 -16
- package/.framework/src/components/search-form.tsx +46 -29
- package/.framework/src/components/shared/AgentView.tsx +65 -48
- package/.framework/src/components/shared/FieldRenderer.tsx +144 -50
- package/.framework/src/components/shared/InviteManager.tsx +207 -97
- package/.framework/src/components/shared/MemberList.tsx +97 -0
- package/.framework/src/components/shared/SchemaFields.tsx +107 -86
- package/.framework/src/components/ui/DataTable.tsx +80 -68
- package/.framework/src/components/ui/Form.tsx +157 -146
- package/.framework/src/components/ui/ItemCard.tsx +74 -67
- package/.framework/src/components/ui/ItemListView.tsx +73 -68
- package/.framework/src/components/ui/LoadingSpinner.tsx +43 -48
- package/.framework/src/components/ui/Modal.tsx +78 -68
- package/.framework/src/components/ui/RichTextEditor.tsx +89 -69
- package/.framework/src/components/ui/accordion.tsx +85 -86
- package/.framework/src/components/ui/alert-dialog.tsx +197 -184
- package/.framework/src/components/ui/alert.tsx +105 -94
- package/.framework/src/components/ui/aspect-ratio.tsx +57 -43
- package/.framework/src/components/ui/avatar.tsx +122 -107
- package/.framework/src/components/ui/badge.tsx +60 -55
- package/.framework/src/components/ui/breadcrumb.tsx +127 -128
- package/.framework/src/components/ui/button-group.tsx +86 -72
- package/.framework/src/components/ui/button.tsx +71 -50
- package/.framework/src/components/ui/calendar.tsx +72 -57
- package/.framework/src/components/ui/card.tsx +136 -129
- package/.framework/src/components/ui/carousel.tsx +226 -107
- package/.framework/src/components/ui/chart.tsx +252 -147
- package/.framework/src/components/ui/checkbox.tsx +43 -44
- package/.framework/src/components/ui/collapsible.tsx +86 -71
- package/.framework/src/components/ui/combobox.tsx +282 -249
- package/.framework/src/components/ui/command.tsx +158 -146
- package/.framework/src/components/ui/context-menu.tsx +239 -240
- package/.framework/src/components/ui/dialog.tsx +174 -157
- package/.framework/src/components/ui/direction.tsx +72 -56
- package/.framework/src/components/ui/drawer.tsx +168 -169
- package/.framework/src/components/ui/dropdown-menu.tsx +254 -223
- package/.framework/src/components/ui/empty.tsx +131 -120
- package/.framework/src/components/ui/field.tsx +205 -173
- package/.framework/src/components/ui/hover-card.tsx +71 -72
- package/.framework/src/components/ui/input-group.tsx +148 -133
- package/.framework/src/components/ui/input-otp.tsx +99 -86
- package/.framework/src/components/ui/input.tsx +43 -41
- package/.framework/src/components/ui/item.tsx +207 -185
- package/.framework/src/components/ui/kbd.tsx +57 -56
- package/.framework/src/components/ui/label.tsx +45 -41
- package/.framework/src/components/ui/menubar.tsx +268 -236
- package/.framework/src/components/ui/native-select.tsx +85 -86
- package/.framework/src/components/ui/navigation-menu.tsx +166 -156
- package/.framework/src/components/ui/pagination.tsx +131 -141
- package/.framework/src/components/ui/popover.tsx +125 -128
- package/.framework/src/components/ui/progress.tsx +47 -44
- package/.framework/src/components/ui/radio-group.tsx +60 -55
- package/.framework/src/components/ui/resizable.tsx +85 -72
- package/.framework/src/components/ui/scroll-area.tsx +57 -58
- package/.framework/src/components/ui/select.tsx +182 -169
- package/.framework/src/components/ui/separator.tsx +42 -43
- package/.framework/src/components/ui/sheet.tsx +170 -159
- package/.framework/src/components/ui/sidebar.tsx +395 -398
- package/.framework/src/components/ui/skeleton.tsx +42 -43
- package/.framework/src/components/ui/slider.tsx +61 -44
- package/.framework/src/components/ui/sonner.tsx +44 -44
- package/.framework/src/components/ui/spinner.tsx +47 -42
- package/.framework/src/components/ui/switch.tsx +56 -42
- package/.framework/src/components/ui/table-primitives.tsx +272 -132
- package/.framework/src/components/ui/table.tsx +140 -142
- package/.framework/src/components/ui/tabs.tsx +110 -93
- package/.framework/src/components/ui/textarea.tsx +43 -44
- package/.framework/src/components/ui/toggle-group.tsx +87 -71
- package/.framework/src/components/ui/toggle.tsx +56 -56
- package/.framework/src/components/ui/tooltip.tsx +99 -85
- package/.framework/src/contexts/AppContext.tsx +166 -114
- package/.framework/src/contexts/AuthContext.tsx +232 -137
- package/.framework/src/hooks/use-mobile.ts +43 -44
- package/.framework/src/hooks/useApi.ts +185 -172
- package/.framework/src/hooks/useAppPath.ts +31 -29
- package/.framework/src/hooks/useApps.ts +77 -42
- package/.framework/src/hooks/useEntityList.ts +66 -29
- package/.framework/src/hooks/useEntityRecord.ts +76 -32
- package/.framework/src/hooks/useForm.ts +62 -30
- package/.framework/src/hooks/useListSchema.ts +60 -43
- package/.framework/src/hooks/useSchemaRecord.ts +115 -95
- package/.framework/src/lib/api.ts +258 -134
- package/.framework/src/lib/registration.ts +181 -0
- package/.framework/src/lib/supabase.ts +118 -81
- package/.framework/src/lib/utils.ts +266 -266
- package/.framework/src/main.tsx +43 -44
- package/.framework/src/pages/DashboardPage.tsx +44 -43
- package/.framework/src/pages/NoAppPage.tsx +50 -0
- package/.framework/src/pages/NotFoundPage.tsx +32 -29
- package/.framework/src/pages/SetupPage.tsx +34 -33
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +69 -58
- package/.framework/src/pages/admin/AIAgentsPage.tsx +43 -40
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +63 -55
- package/.framework/src/pages/admin/APIKeysPage.tsx +44 -42
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +117 -94
- package/.framework/src/pages/admin/AppDetailPage.tsx +48 -42
- package/.framework/src/pages/admin/AppsPage.tsx +45 -42
- package/.framework/src/pages/admin/DeliveriesPage.tsx +49 -17
- package/.framework/src/pages/admin/DesignedPage.tsx +31 -30
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +58 -0
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +49 -0
- package/.framework/src/pages/admin/ExtendedShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/IncrementalShadcnTestPage.tsx +46 -29
- package/.framework/src/pages/admin/IntegratedDashboard.tsx +32 -29
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +68 -55
- package/.framework/src/pages/admin/IntegrationsPage.tsx +45 -42
- package/.framework/src/pages/admin/InvitesPage.tsx +60 -44
- package/.framework/src/pages/admin/LogsPage.tsx +87 -137
- package/.framework/src/pages/admin/MinimalShadcnTestPage.tsx +31 -29
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +150 -89
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +77 -104
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +44 -43
- package/.framework/src/pages/admin/PipelinesPage.tsx +59 -145
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +53 -42
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +47 -117
- package/.framework/src/pages/admin/ProperlyDesignedPage.tsx +31 -44
- package/.framework/src/pages/admin/RoleDetailPage.tsx +49 -92
- package/.framework/src/pages/admin/RolesPage.tsx +47 -164
- package/.framework/src/pages/admin/SecurityPage.tsx +310 -0
- package/.framework/src/pages/admin/SelectTestPage.tsx +30 -32
- package/.framework/src/pages/admin/ShadcnTestPage.tsx +37 -44
- package/.framework/src/pages/admin/SimpleDashboard.tsx +32 -44
- package/.framework/src/pages/admin/TestRunDetailPage.tsx +97 -44
- package/.framework/src/pages/admin/TestingDashboard.tsx +126 -71
- package/.framework/src/pages/admin/TimerDetailPage.tsx +67 -55
- package/.framework/src/pages/admin/TimersPage.tsx +44 -42
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +47 -42
- package/.framework/src/pages/admin/TriggersPage.tsx +58 -57
- package/.framework/src/pages/admin/TypeDetailPage.tsx +91 -55
- package/.framework/src/pages/admin/TypesPage.tsx +45 -42
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +258 -128
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +127 -123
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +308 -235
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +159 -110
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +432 -0
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +200 -178
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +286 -239
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +211 -90
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +156 -143
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +379 -0
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +337 -0
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +219 -214
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +64 -0
- package/.framework/src/pages/auth/AuthConfirmPage.tsx +135 -65
- package/.framework/src/pages/auth/ForgotPasswordPage.tsx +39 -31
- package/.framework/src/pages/auth/LoginPage.tsx +38 -32
- package/.framework/src/pages/auth/PendingApprovalPage.tsx +31 -28
- package/.framework/src/pages/auth/RegisterPage.tsx +429 -155
- package/.framework/src/pages/spine-framework/APIPage.tsx +33 -30
- package/.framework/src/pages/spine-framework/CLIPage.tsx +32 -30
- package/.framework/src/types/auth.ts +101 -93
- package/.framework/src/types/types.ts +283 -262
- package/.framework/templates/project.json +87 -0
- package/.framework/templates/spine.config.neutral.json +7 -0
- package/.framework/tests/api/api-surface.test.ts +381 -0
- package/.framework/tests/fixtures/seed.ts +427 -0
- package/.framework/tests/fixtures/teardown.ts +228 -0
- package/.framework/tests/reporter.ts +351 -0
- package/.framework/tests/setup.ts +135 -0
- package/.framework/tests/ui/ui-sweep.ts +278 -0
- package/.framework/tests/unit/admin-data-logger.test.ts +310 -0
- package/.framework/tests/unit/agent-runner.test.ts +2389 -0
- package/.framework/tests/unit/ai-interactions.test.ts +81 -0
- package/.framework/tests/unit/app-manifest.test.ts +359 -0
- package/.framework/tests/unit/assembly-scripts.test.ts +172 -0
- package/.framework/tests/unit/cil-determinism.test.ts +85 -0
- package/.framework/tests/unit/core-isolation.test.ts +153 -0
- package/.framework/tests/unit/funnel-signals.test.ts +135 -0
- package/.framework/tests/unit/install-app.test.ts +1759 -0
- package/.framework/tests/unit/invites-security.test.ts +572 -0
- package/.framework/tests/unit/kb-ingest-lease.test.ts +287 -0
- package/.framework/tests/unit/kb-search.test.ts +119 -0
- package/.framework/tests/unit/migrate.test.ts +509 -0
- package/.framework/tests/unit/notify.test.ts +136 -0
- package/.framework/tests/unit/permissions.test.ts +269 -0
- package/.framework/tests/unit/pipeline-runner.test.ts +755 -0
- package/.framework/tests/unit/principal-cache.test.ts +138 -0
- package/.framework/tests/unit/retrieval-golden.test.ts +520 -0
- package/.framework/tests/unit/runtime-isolation.test.ts +642 -0
- package/.framework/tests/unit/schema-utils.test.ts +225 -0
- package/.framework/tests/unit/trigger-engine.test.ts +513 -0
- package/.framework/tests/unit/validate-app.test.ts +527 -0
- package/README.md +34 -3
- package/bin/spine-framework.cjs +1 -0
- package/config/tailwind.config.cjs +7 -0
- package/config/tsconfig.functions.json +42 -0
- package/config/tsconfig.json +37 -9
- package/config/vite.config.ts +31 -2
- package/netlify.toml +31 -3
- package/package.json +46 -29
- package/scripts/assemble-frontend.sh +127 -44
- package/scripts/assemble-functions.sh +127 -29
- package/scripts/boundary-check.sh +6 -2
- package/scripts/build-manifest.sh +24 -68
- package/scripts/checksum-manifest.mts +345 -0
- package/scripts/dev-clean.sh +86 -0
- package/scripts/dev-netlify.sh +49 -0
- package/scripts/netlify-dev-wrapper.sh +2 -0
- package/scripts/verify-install.ts +358 -0
- package/scripts/verify-integrity.sh +21 -57
- package/scripts/verify-manifest.mts +305 -0
- package/templates/project/gitignore +45 -0
- package/{config → templates/project}/package-lock.json +22335 -9366
- package/vitest.config.ts +46 -0
- package/.framework/public/spine.config.json +0 -91
- package/config/deno.lock +0 -108
- package/config/tsconfig.node.tsbuildinfo +0 -1
- package/config/tsconfig.tsbuildinfo +0 -1
- package/config/vite.config.d.ts +0 -2
- package/custom/.devin/AGENT.md +0 -357
- package/custom/.devin/skills/install-app/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-discovery/SKILL.md +0 -39
- package/custom/.devin/skills/spine-workbench-idea/SKILL.md +0 -36
- package/custom/.devin/skills/spine-workbench-implement/SKILL.md +0 -46
- package/custom/.devin/skills/spine-workbench-initiative/SKILL.md +0 -28
- package/custom/.devin/skills/spine-workbench-plan/SKILL.md +0 -38
- package/custom/.devin/workflows/app-install.yaml +0 -83
- package/custom/README.md +0 -79
- package/docs/README.md +0 -15
- package/scripts/app-install-cli.ts +0 -286
- package/scripts/check-core-integrity.sh +0 -82
- package/scripts/ingest-chunks.cjs +0 -202
- package/scripts/kb-chunk-parser.cjs +0 -312
- package/scripts/kb-chunk-parser.ts +0 -330
- package/scripts/kb-ingest-docs.ts +0 -203
- package/scripts/load-test-app-install.ts +0 -484
- package/scripts/test-login.js +0 -33
- package/spine.config.json +0 -91
package/.framework/cli/index.ts
CHANGED
|
@@ -2,60 +2,72 @@
|
|
|
2
2
|
|
|
3
3
|
|
|
4
4
|
|
|
5
|
-
// CIL[BEGIN
|
|
5
|
+
// CIL[BEGIN stable_id="~/.framework/cli/index.ts:module_header:module_header" type="module_header" identity="module_header" parent="none"]
|
|
6
6
|
// CIL[DOC-MACRO purpose="Import CLI dependencies and environment loader"]
|
|
7
7
|
// CIL[DOC-RESPONSIBILITY responsibility="Load required modules for CLI setup"]
|
|
8
|
-
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files="
|
|
8
|
+
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts"]
|
|
9
9
|
// CIL[DOC-CONTRACT inputs="none" outputs="loaded_modules" errors="none" side_effects="none"]
|
|
10
10
|
// CIL[DOC-MICRO logic_summary="Imports environment loader and Commander.js" notable_decisions="[]"]
|
|
11
11
|
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
12
12
|
// CIL[DOC-STABILITY stability="stable"]
|
|
13
13
|
// CIL[DOC-AUDIENCE audience="developer"]
|
|
14
|
-
// CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/pages/admin/RolesPage.tsx, .framework/src/components/ui/dialog.tsx, \."]
|
|
15
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
16
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
17
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
18
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
19
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
20
14
|
import './env-loader.ts'
|
|
21
15
|
import { Command } from 'commander'
|
|
22
|
-
|
|
23
|
-
|
|
16
|
+
import { readFileSync } from 'fs'
|
|
17
|
+
import { resolve, dirname, join } from 'path'
|
|
18
|
+
import { fileURLToPath } from 'url'
|
|
19
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Import CLI dependencies and environment loader'; No verified external callers; imports: .framework/cli/env-loader.ts"]
|
|
20
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
21
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
22
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
23
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
24
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
25
|
+
// CIL[END stable_id="~/.framework/cli/index.ts:module_header:module_header"]
|
|
26
|
+
// CIL[BEGIN stable_id="~/.framework/cli/index.ts:program:variable" type="variable" identity="program" parent="none"]
|
|
24
27
|
// CIL[DOC-MACRO purpose="Initialize CLI program with basic configuration"]
|
|
25
28
|
// CIL[DOC-RESPONSIBILITY responsibility="Configure main CLI program instance"]
|
|
26
|
-
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files="
|
|
29
|
+
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts,.framework/cli/index.ts"]
|
|
27
30
|
// CIL[DOC-CONTRACT inputs="none" outputs="configured_program" errors="none" side_effects="none"]
|
|
28
31
|
// CIL[DOC-MICRO logic_summary="Creates Commander program with name, description, version, and account option" notable_decisions="[]"]
|
|
29
32
|
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
30
33
|
// CIL[DOC-STABILITY stability="stable"]
|
|
31
|
-
// CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: .framework/functions/_shared/schema-utils.ts, .framework/src/components/ui/dialog.tsx, .framework/functions/_shared/principal.ts, .framework/src/components/ui/table-primitives.tsx, .framework/src/pages/admin/TypeDetailPage.tsx, \."]
|
|
32
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
33
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
34
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
35
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
36
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
37
34
|
const program = new Command()
|
|
38
35
|
|
|
36
|
+
// Version: the spine-framework package.json when running from the package,
|
|
37
|
+
// otherwise the project's frozen manifest (vendored CLI copy).
|
|
38
|
+
function cliVersion(): string {
|
|
39
|
+
const pkgRoot = resolve(dirname(fileURLToPath(import.meta.url)), '../../')
|
|
40
|
+
try {
|
|
41
|
+
const pkg = JSON.parse(readFileSync(join(pkgRoot, 'package.json'), 'utf8'))
|
|
42
|
+
if (pkg.name === 'spine-framework' && typeof pkg.version === 'string') return pkg.version
|
|
43
|
+
} catch { /* fall through */ }
|
|
44
|
+
try {
|
|
45
|
+
const manifest = JSON.parse(readFileSync(join(process.cwd(), '.spine-manifest.json'), 'utf8'))
|
|
46
|
+
if (typeof manifest.framework_version === 'string') return manifest.framework_version
|
|
47
|
+
} catch { /* fall through */ }
|
|
48
|
+
return '0.0.0'
|
|
49
|
+
}
|
|
50
|
+
|
|
39
51
|
program
|
|
40
52
|
.name('spine-framework')
|
|
41
53
|
.description('Spine Framework CLI — install, migrate, and manage your Spine instance')
|
|
42
|
-
.version(
|
|
54
|
+
.version(cliVersion())
|
|
43
55
|
.option('--account <id>', 'Override the account ID for this command')
|
|
44
|
-
// CIL[
|
|
45
|
-
// CIL[
|
|
56
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Initialize CLI program with basic configuration'; No verified external callers; imports: .framework/cli/env-loader.ts"]
|
|
57
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
58
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
59
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
60
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
61
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
62
|
+
// CIL[END stable_id="~/.framework/cli/index.ts:program:variable"]
|
|
63
|
+
// CIL[BEGIN stable_id="~/.framework/cli/index.ts:command_imports:block" type="block" identity="command_imports" parent="none"]
|
|
46
64
|
// CIL[DOC-MACRO purpose="Dynamically import all CLI command modules"]
|
|
47
65
|
// CIL[DOC-RESPONSIBILITY responsibility="Load command registration functions"]
|
|
48
|
-
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files="
|
|
66
|
+
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts,.framework/cli/index.ts"]
|
|
49
67
|
// CIL[DOC-CONTRACT inputs="none" outputs="command_modules" errors="import_failures" side_effects="none"]
|
|
50
68
|
// CIL[DOC-MICRO logic_summary="Uses Promise.all to parallel import all command modules" notable_decisions="[]"]
|
|
51
69
|
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
52
70
|
// CIL[DOC-STABILITY stability="stable"]
|
|
53
|
-
// CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/015_simplify_apps_table.sql, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/ui/table-primitives.tsx, \."]
|
|
54
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
55
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
56
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
57
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
58
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
59
71
|
const [
|
|
60
72
|
{ registerInitCommands },
|
|
61
73
|
{ registerMigrateCommands },
|
|
@@ -72,6 +84,8 @@ const [
|
|
|
72
84
|
{ registerSystemCommands },
|
|
73
85
|
{ registerGenerateCommands },
|
|
74
86
|
{ registerCreateAppCommands },
|
|
87
|
+
{ registerValidateAppCommands },
|
|
88
|
+
{ registerUpgradeCommands },
|
|
75
89
|
] = await Promise.all([
|
|
76
90
|
import('./commands/init.ts'),
|
|
77
91
|
import('./commands/migrate.ts'),
|
|
@@ -88,22 +102,24 @@ const [
|
|
|
88
102
|
import('./commands/system.ts'),
|
|
89
103
|
import('./commands/generate.ts'),
|
|
90
104
|
import('./commands/create-app.ts'),
|
|
105
|
+
import('./commands/validate-app.ts'),
|
|
106
|
+
import('./commands/upgrade.ts'),
|
|
91
107
|
])
|
|
92
|
-
// CIL[
|
|
93
|
-
// CIL[
|
|
108
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Dynamically import all CLI command modules'; No verified external callers; imports: .framework/cli/env-loader.ts"]
|
|
109
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
110
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
111
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
112
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
113
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
114
|
+
// CIL[END stable_id="~/.framework/cli/index.ts:command_imports:block"]
|
|
115
|
+
// CIL[BEGIN stable_id="~/.framework/cli/index.ts:command_registrations:block" type="block" identity="command_registrations" parent="none"]
|
|
94
116
|
// CIL[DOC-MACRO purpose="Register all CLI commands with the program"]
|
|
95
117
|
// CIL[DOC-RESPONSIBILITY responsibility="Configure CLI with all available commands"]
|
|
96
|
-
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files="
|
|
118
|
+
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts,.framework/cli/index.ts"]
|
|
97
119
|
// CIL[DOC-CONTRACT inputs="none" outputs="registered_commands" errors="none" side_effects="none"]
|
|
98
120
|
// CIL[DOC-MICRO logic_summary="Calls each command registration function with the program instance" notable_decisions="[]"]
|
|
99
121
|
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
100
122
|
// CIL[DOC-STABILITY stability="stable"]
|
|
101
|
-
// CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: .framework/migrations/015_simplify_apps_table.sql, .framework/functions/_shared/principal.ts, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/ui/table-primitives.tsx, \."]
|
|
102
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
103
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
104
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
105
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
106
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
107
123
|
registerInitCommands(program)
|
|
108
124
|
registerMigrateCommands(program)
|
|
109
125
|
registerInstallAppCommands(program)
|
|
@@ -119,23 +135,31 @@ registerTestCommands(program)
|
|
|
119
135
|
registerSystemCommands(program)
|
|
120
136
|
registerGenerateCommands(program)
|
|
121
137
|
registerCreateAppCommands(program)
|
|
122
|
-
|
|
123
|
-
|
|
138
|
+
registerValidateAppCommands(program)
|
|
139
|
+
registerUpgradeCommands(program)
|
|
140
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Register all CLI commands with the program'; No verified external callers; imports: .framework/cli/env-loader.ts"]
|
|
141
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
142
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
143
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
144
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
145
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
146
|
+
// CIL[END stable_id="~/.framework/cli/index.ts:command_registrations:block"]
|
|
147
|
+
// CIL[BEGIN stable_id="~/.framework/cli/index.ts:error_handling:block" type="block" identity="error_handling" parent="none"]
|
|
124
148
|
// CIL[DOC-MACRO purpose="Parse CLI arguments and handle errors"]
|
|
125
149
|
// CIL[DOC-RESPONSIBILITY responsibility="Execute CLI and handle uncaught errors"]
|
|
126
|
-
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files="
|
|
150
|
+
// CIL[DOC-CONTEXT module="cli" layer="entry" related_files=".framework/cli/env-loader.ts,.framework/cli/index.ts"]
|
|
127
151
|
// CIL[DOC-CONTRACT inputs="none" outputs="command_execution" errors="cli_errors" side_effects="process_exit"]
|
|
128
152
|
// CIL[DOC-MICRO logic_summary="Parses async command line arguments and exits on error" notable_decisions="[]"]
|
|
129
153
|
// CIL[DOC-CONFIDENCE pass="second" score="0.95" flags="none"]
|
|
130
154
|
// CIL[DOC-STABILITY stability="stable"]
|
|
131
|
-
// CIL[DOC-VECTOR type="context" content="Layer: entry. Direct callers: 0. Direct calls: 0. Related files: custom/apps/portal/pages/ContentPage.tsx, .framework/src/pages/admin/RolesPage.tsx, custom/apps/cortex/pages/operations/OperationsDashboard.tsx, .framework/src/components/ui/dialog.tsx, \."]
|
|
132
|
-
// CIL[DOC-VECTOR type="blast_radius" content="Direct callers: 0. Total affected: 0."]
|
|
133
|
-
// CIL[DOC-VECTOR type="configuration" content="No configuration dependencies."]
|
|
134
|
-
// CIL[DOC-VECTOR type="constraint" content="No test paths identified."]
|
|
135
|
-
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
136
|
-
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
137
155
|
program.parseAsync(process.argv).catch((error) => {
|
|
138
156
|
console.error('CLI Error:', error.message)
|
|
139
157
|
process.exit(1)
|
|
140
158
|
})
|
|
141
|
-
// CIL[
|
|
159
|
+
// CIL[DOC-VECTOR type="context" content="purpose='Parse CLI arguments and handle errors'; No verified external callers; imports: .framework/cli/env-loader.ts"]
|
|
160
|
+
// CIL[DOC-VECTOR type="blast_radius" content="No direct caller data from CIL graph; local impact only"]
|
|
161
|
+
// CIL[DOC-VECTOR type="configuration" content="No environment or build configuration dependencies identified"]
|
|
162
|
+
// CIL[DOC-VECTOR type="constraint" content="Standard runtime; no special invariants documented"]
|
|
163
|
+
// CIL[DOC-VECTOR type="implementation_pattern" content="" status="pending_human_input"]
|
|
164
|
+
// CIL[DOC-VECTOR type="user_journey" content="" status="pending_human_input"]
|
|
165
|
+
// CIL[END stable_id="~/.framework/cli/index.ts:error_handling:block"]
|
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
// CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:module_header:module_header" type="module_header" identity="module_header" parent="~/.framework/cli/lib/project-files.ts"]
|
|
2
|
+
// CIL[DOC-MACRO purpose="Framework-owned project fileset — collect files to vend, render netlify.toml/.gitignore, merge package.json, and write the frozen .spine-manifest.json"]
|
|
3
|
+
// CIL[DOC-RESPONSIBILITY responsibility="Provide the shared primitives for `init` (scaffold a new project) and `upgrade` (sync an installed project to a newer spine-framework) — always app-agnostic, driven by .framework/templates/project.json"]
|
|
4
|
+
// CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".framework/templates/project.json,.framework/cli/commands/init.ts,.framework/cli/commands/upgrade.ts,scripts/checksum-manifest.mts"]
|
|
5
|
+
// CIL[DOC-CONTRACT inputs="pkgRoot (spine-framework package/monorepo root), projectRoot" outputs="file lists, rendered config text, frozen manifest" errors="throws on missing declared framework files" side_effects="writeFrozenManifest writes .spine-manifest.json; all other exports are pure"]
|
|
6
|
+
// CIL[DOC-MICRO logic_summary="collectFrameworkFiles mirrors export-standalone's collectCopyList semantics: excludes apply to walked dirs only, explicitly listed files bypass them. renderNetlifyToml treats [functions.\"custom_*\"] sections as app-owned: strip them from the framework source, re-append the project's existing custom_* sections verbatim" notable_decisions="framework never writes custom/, spine.config.json, .env, or netlify.toml custom_* sections — those belong to apps and the customer"]
|
|
7
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.85" flags="none"]
|
|
8
|
+
// CIL[DOC-STABILITY stability="experimental"]
|
|
9
|
+
// CIL[DOC-NOTE type="design" content="Kept dependency-free of export-standalone.ts on purpose: the packager profile lives in .framework/templates/project.json so the framework never learns app specifics"]
|
|
10
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import { createHash } from 'node:crypto'
|
|
15
|
+
import {
|
|
16
|
+
collectProtectedFiles,
|
|
17
|
+
hashEntry,
|
|
18
|
+
computeTreeDigest,
|
|
19
|
+
} from '../../../scripts/checksum-manifest.mts'
|
|
20
|
+
// CIL[END stable_id="~/.framework/cli/lib/project-files.ts:module_header:module_header"]
|
|
21
|
+
|
|
22
|
+
// CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:types:block" type="block" identity="types" parent="~/.framework/cli/lib/project-files.ts"]
|
|
23
|
+
// CIL[DOC-MACRO purpose="Project profile + collected-file shapes shared by init and upgrade"]
|
|
24
|
+
// CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".framework/templates/project.json"]
|
|
25
|
+
// CIL[DOC-CONTRACT inputs="none" outputs="ProjectProfile, CollectedFile interfaces" errors="none" side_effects="none"]
|
|
26
|
+
// CIL[DOC-MICRO logic_summary="ProjectProfile mirrors templates/project.json keys; CollectedFile is the {rel, abs} copy unit" notable_decisions="one profile shape drives both scaffold and upgrade"]
|
|
27
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.95" flags="none"]
|
|
28
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
29
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
30
|
+
export interface ProjectProfile {
|
|
31
|
+
framework_dirs: string[]
|
|
32
|
+
framework_files: string[]
|
|
33
|
+
config_files: string[]
|
|
34
|
+
root_scripts: string[]
|
|
35
|
+
bin: string[]
|
|
36
|
+
exclude_globs: string[]
|
|
37
|
+
package_scripts: Record<string, string>
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CollectedFile {
|
|
41
|
+
rel: string
|
|
42
|
+
abs: string
|
|
43
|
+
symlink: boolean
|
|
44
|
+
}
|
|
45
|
+
// CIL[END stable_id="~/.framework/cli/lib/project-files.ts:types:block"]
|
|
46
|
+
|
|
47
|
+
// CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:globs_and_hashes:block" type="block" identity="globs_and_hashes" parent="~/.framework/cli/lib/project-files.ts"]
|
|
48
|
+
// CIL[DOC-MACRO purpose="Minimal `**`/`*`/`?` glob→regex matcher (export-standalone semantics) and sha256 file/text helpers"]
|
|
49
|
+
// CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".framework/templates/project.json,scripts/checksum-manifest.mts"]
|
|
50
|
+
// CIL[DOC-CONTRACT inputs="glob: string; abs: string; text: string" outputs="RegExp; sha256 hex" errors="none" side_effects="none"]
|
|
51
|
+
// CIL[DOC-MICRO logic_summary="globToRegex escapes literals then expands `**`→.* `*`→[^/]* `?`→[^/]; sha256 helpers feed both the frozen manifest and upgrade integrity checks" notable_decisions="no external glob dep — the exclude vocabulary is deliberately small"]
|
|
52
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
53
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
54
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
55
|
+
|
|
56
|
+
/** Minimal glob matcher supporting `**`, `*`, and `?` — same semantics as export-standalone. */
|
|
57
|
+
export function globToRegex(glob: string): RegExp {
|
|
58
|
+
const parts = glob.split('**').map(seg =>
|
|
59
|
+
seg
|
|
60
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
61
|
+
.replace(/\*/g, '[^/]*')
|
|
62
|
+
.replace(/\?/g, '[^/]')
|
|
63
|
+
)
|
|
64
|
+
return new RegExp(`^${parts.join('.*')}$`)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function sha256File(abs: string): string {
|
|
68
|
+
return `sha256:${createHash('sha256').update(fs.readFileSync(abs)).digest('hex')}`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function sha256Text(text: string): string {
|
|
72
|
+
return `sha256:${createHash('sha256').update(text, 'utf8').digest('hex')}`
|
|
73
|
+
}
|
|
74
|
+
// CIL[END stable_id="~/.framework/cli/lib/project-files.ts:globs_and_hashes:block"]
|
|
75
|
+
|
|
76
|
+
// CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:collect:block" type="block" identity="collect" parent="~/.framework/cli/lib/project-files.ts"]
|
|
77
|
+
// CIL[DOC-MACRO purpose="Profile load + framework file collection — walked dirs honor excludes, listed files bypass them"]
|
|
78
|
+
// CIL[DOC-CONTRACT inputs="dirAbs: string, rootAbs: string, excludes: RegExp[], out: CollectedFile[], pkgRoot: string, profile: ProjectProfile" outputs="sorted CollectedFile[]" errors="throws listing missing required framework_dirs/framework_files/root_scripts" side_effects="none (read-only)"]
|
|
79
|
+
// CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".framework/templates/project.json"]
|
|
80
|
+
// CIL[DOC-MICRO logic_summary="loadProjectProfile reads templates/project.json; collectFrameworkFiles walks declared dirs (honoring excludes) and appends declared files/locks verbatim, deduping by rel" notable_decisions="walked entries honor excludes; explicitly listed files bypass them — export-standalone parity"]
|
|
81
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
82
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
83
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
84
|
+
function walkDir(
|
|
85
|
+
dirAbs: string,
|
|
86
|
+
rootAbs: string,
|
|
87
|
+
excludes: RegExp[],
|
|
88
|
+
out: CollectedFile[] = [],
|
|
89
|
+
): CollectedFile[] {
|
|
90
|
+
for (const dent of fs.readdirSync(dirAbs, { withFileTypes: true })) {
|
|
91
|
+
const abs = path.join(dirAbs, dent.name)
|
|
92
|
+
const rel = path.relative(rootAbs, abs).split(path.sep).join('/')
|
|
93
|
+
if (excludes.some((re) => re.test(rel))) continue
|
|
94
|
+
if (dent.isDirectory()) walkDir(abs, rootAbs, excludes, out)
|
|
95
|
+
else if (dent.isSymbolicLink()) out.push({ rel, abs, symlink: true })
|
|
96
|
+
else if (dent.isFile()) out.push({ rel, abs, symlink: false })
|
|
97
|
+
}
|
|
98
|
+
return out
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Load the framework's own project profile shipped at .framework/templates/project.json. */
|
|
102
|
+
export function loadProjectProfile(pkgRoot: string): ProjectProfile {
|
|
103
|
+
const file = path.join(pkgRoot, '.framework/templates/project.json')
|
|
104
|
+
return JSON.parse(fs.readFileSync(file, 'utf8')) as ProjectProfile
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Collect the framework-owned file set declared by the project profile.
|
|
109
|
+
* Directories are walked with excludes; individually listed files bypass
|
|
110
|
+
* excludes (so `.env.example` survives env-glob exclusions).
|
|
111
|
+
* Missing `framework_dirs`/`framework_files`/`root_scripts` entries are fatal;
|
|
112
|
+
* `config_files` and `bin` are optional (warn + skip).
|
|
113
|
+
*/
|
|
114
|
+
export function collectFrameworkFiles(pkgRoot: string, profile: ProjectProfile): CollectedFile[] {
|
|
115
|
+
const excludes = profile.exclude_globs.map(globToRegex)
|
|
116
|
+
const seen = new Map<string, CollectedFile>()
|
|
117
|
+
const missing: string[] = []
|
|
118
|
+
|
|
119
|
+
const addFile = (rel: string, required: boolean) => {
|
|
120
|
+
const abs = path.join(pkgRoot, rel)
|
|
121
|
+
if (!fs.existsSync(abs)) {
|
|
122
|
+
if (required) missing.push(rel)
|
|
123
|
+
else console.log(` ○ optional ${rel} absent — skipped`)
|
|
124
|
+
return
|
|
125
|
+
}
|
|
126
|
+
const lst = fs.lstatSync(abs)
|
|
127
|
+
seen.set(rel, { rel, abs, symlink: lst.isSymbolicLink() })
|
|
128
|
+
}
|
|
129
|
+
const addDir = (rel: string) => {
|
|
130
|
+
const abs = path.join(pkgRoot, rel)
|
|
131
|
+
if (!fs.existsSync(abs) || !fs.statSync(abs).isDirectory()) {
|
|
132
|
+
missing.push(rel)
|
|
133
|
+
return
|
|
134
|
+
}
|
|
135
|
+
for (const e of walkDir(abs, pkgRoot, excludes)) seen.set(e.rel, e)
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
for (const d of profile.framework_dirs) addDir(d)
|
|
139
|
+
for (const f of profile.framework_files) addFile(f, true)
|
|
140
|
+
for (const f of profile.config_files) addFile(f, false)
|
|
141
|
+
for (const f of profile.root_scripts) addFile(f, true)
|
|
142
|
+
for (const f of profile.bin) addFile(f, false)
|
|
143
|
+
|
|
144
|
+
if (missing.length > 0) {
|
|
145
|
+
throw new Error(`framework package is missing declared files:\n ${missing.join('\n ')}`)
|
|
146
|
+
}
|
|
147
|
+
return [...seen.values()].sort((a, b) => Buffer.compare(Buffer.from(a.rel), Buffer.from(b.rel)))
|
|
148
|
+
}
|
|
149
|
+
// CIL[END stable_id="~/.framework/cli/lib/project-files.ts:collect:block"]
|
|
150
|
+
|
|
151
|
+
// CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:toml:block" type="block" identity="toml" parent="~/.framework/cli/lib/project-files.ts"]
|
|
152
|
+
// CIL[DOC-MACRO purpose="netlify.toml split/render — framework blocks owned upstream, [functions.\"custom_*\"] sections app-owned and preserved verbatim; .gitignore render"]
|
|
153
|
+
// CIL[DOC-CONTEXT module="cli" layer="lib" related_files="netlify.toml,.gitignore"]
|
|
154
|
+
// CIL[DOC-CONTRACT inputs="toml: string; frameworkToml: string; existingToml?: string; template: string" outputs="{ base, customSections } | rendered TOML | rendered gitignore" errors="none" side_effects="none (pure)"]
|
|
155
|
+
// CIL[DOC-MICRO logic_summary="splitNetlifyToml removes app-owned custom_* scheduled sections, comments attached, from framework source and returns them verbatim; renderNetlifyToml re-appends the project's own sections; renderGitignore ensures .env ignore lines" notable_decisions="custom_* ownership boundary is the core of app-independence — identical rendered output must not create a .bak upstream"]
|
|
156
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
157
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
158
|
+
// CIL[DOC-NOTE type="invariant" content="splitNetlifyToml must stay idempotent — extracted sections attach preceding comments but never leading blank lines, or every upgrade writes a phantom diff"]
|
|
159
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
160
|
+
const CUSTOM_SECTION_RE = /^\[functions\."custom_/
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Split a netlify.toml into base content and app-owned [functions."custom_*"]
|
|
164
|
+
* sections (each including its contiguous preceding comment block).
|
|
165
|
+
*/
|
|
166
|
+
export function splitNetlifyToml(toml: string): { base: string; customSections: string[] } {
|
|
167
|
+
const lines = toml.split('\n')
|
|
168
|
+
const base: string[] = []
|
|
169
|
+
const customSections: string[] = []
|
|
170
|
+
let skipping = false
|
|
171
|
+
let pendingComments: string[] = []
|
|
172
|
+
let current: string[] | null = null
|
|
173
|
+
|
|
174
|
+
const flushSection = () => {
|
|
175
|
+
if (current) customSections.push(current.join('\n').replace(/\s+$/, ''))
|
|
176
|
+
current = null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
for (const line of lines) {
|
|
180
|
+
const trimmed = line.trim()
|
|
181
|
+
if (trimmed === '' ) {
|
|
182
|
+
if (!skipping && current === null) pendingComments.push(line)
|
|
183
|
+
else current?.push(line)
|
|
184
|
+
continue
|
|
185
|
+
}
|
|
186
|
+
if (trimmed.startsWith('#')) {
|
|
187
|
+
if (!skipping && current === null) pendingComments.push(line)
|
|
188
|
+
else current?.push(line)
|
|
189
|
+
continue
|
|
190
|
+
}
|
|
191
|
+
if (trimmed.startsWith('[')) {
|
|
192
|
+
if (current !== null) flushSection()
|
|
193
|
+
skipping = CUSTOM_SECTION_RE.test(trimmed)
|
|
194
|
+
if (skipping) {
|
|
195
|
+
// Attach the immediately preceding comment block, but not blank lines —
|
|
196
|
+
// rendering already separates sections, and blanks here would drift
|
|
197
|
+
// on every round-trip.
|
|
198
|
+
const comments = [...pendingComments]
|
|
199
|
+
while (comments.length > 0 && comments[0].trim() === '') comments.shift()
|
|
200
|
+
current = [...comments, line]
|
|
201
|
+
} else {
|
|
202
|
+
base.push(...pendingComments, line)
|
|
203
|
+
}
|
|
204
|
+
pendingComments = []
|
|
205
|
+
continue
|
|
206
|
+
}
|
|
207
|
+
if (skipping) { current?.push(line); continue }
|
|
208
|
+
base.push(...pendingComments, line)
|
|
209
|
+
pendingComments = []
|
|
210
|
+
}
|
|
211
|
+
flushSection()
|
|
212
|
+
return {
|
|
213
|
+
base: base.join('\n').replace(/\n{3,}/g, '\n\n').replace(/\s+$/, ''),
|
|
214
|
+
customSections,
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Render a project netlify.toml: framework-owned blocks from the package,
|
|
220
|
+
* app-owned [functions."custom_*"] sections preserved from the existing file.
|
|
221
|
+
*/
|
|
222
|
+
export function renderNetlifyToml(frameworkToml: string, existingToml?: string): string {
|
|
223
|
+
const { base } = splitNetlifyToml(frameworkToml)
|
|
224
|
+
const { customSections } = existingToml ? splitNetlifyToml(existingToml) : { customSections: [] }
|
|
225
|
+
return customSections.length > 0
|
|
226
|
+
? `${base}\n\n${customSections.join('\n\n')}\n`
|
|
227
|
+
: `${base}\n`
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/** Render the project .gitignore from the packed template (templates/project/gitignore). */
|
|
231
|
+
export function renderGitignore(template: string): string {
|
|
232
|
+
return template
|
|
233
|
+
.split('\n')
|
|
234
|
+
.filter((l) => l.trim() !== '.env.example')
|
|
235
|
+
.join('\n')
|
|
236
|
+
}
|
|
237
|
+
// CIL[END stable_id="~/.framework/cli/lib/project-files.ts:toml:block"]
|
|
238
|
+
|
|
239
|
+
// CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:package_merge:block" type="block" identity="package_merge" parent="~/.framework/cli/lib/project-files.ts"]
|
|
240
|
+
// CIL[DOC-MACRO purpose="Merge framework-owned scripts/deps/pin into a project package.json — removes only deps the previous framework version owned"]
|
|
241
|
+
// CIL[DOC-CONTEXT module="cli" layer="lib" related_files="package.json,.spine-manifest.json"]
|
|
242
|
+
// CIL[DOC-CONTRACT inputs="opts: { existing, name, description, scripts, newDependencies, newDevDependencies, spineVersion, oldFrameworkDeps }" outputs="{ text, frameworkDeps }" errors="none" side_effects="none"]
|
|
243
|
+
// CIL[DOC-MICRO logic_summary="Removes deps recorded in the old manifest's framework_dependencies, installs the new set, applies profile scripts, pins spine-framework exactly" notable_decisions="dep removal is scoped to previously framework-owned deps — customer-added deps/scripts always survive"]
|
|
244
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
245
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
246
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
247
|
+
export interface MergedPackageJson {
|
|
248
|
+
text: string
|
|
249
|
+
frameworkDeps: string[]
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Merge framework-owned content into a project package.json.
|
|
254
|
+
* - scripts listed in the profile are written (framework-owned keys)
|
|
255
|
+
* - deps listed in newFrameworkDeps are written verbatim
|
|
256
|
+
* - a dep is removed only when the previous version owned it (present in
|
|
257
|
+
* prevFrameworkDeps) and the new version dropped it
|
|
258
|
+
* - customer-added keys everywhere else are preserved
|
|
259
|
+
*/
|
|
260
|
+
export function mergePackageJson(opts: {
|
|
261
|
+
existing?: Record<string, any>
|
|
262
|
+
name: string
|
|
263
|
+
version?: string
|
|
264
|
+
description?: string
|
|
265
|
+
scripts: Record<string, string>
|
|
266
|
+
newDependencies: Record<string, string>
|
|
267
|
+
newDevDependencies: Record<string, string>
|
|
268
|
+
prevFrameworkDeps?: string[]
|
|
269
|
+
spineVersion: string
|
|
270
|
+
}): MergedPackageJson {
|
|
271
|
+
const prevOwned = new Set(opts.prevFrameworkDeps ?? [])
|
|
272
|
+
const dependencies: Record<string, string> = { ...(opts.existing?.dependencies ?? {}) }
|
|
273
|
+
const devDependencies: Record<string, string> = { ...(opts.existing?.devDependencies ?? {}) }
|
|
274
|
+
|
|
275
|
+
for (const dep of prevOwned) {
|
|
276
|
+
if (!opts.newDependencies[dep] && !opts.newDevDependencies[dep]) {
|
|
277
|
+
delete dependencies[dep]
|
|
278
|
+
delete devDependencies[dep]
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
for (const [k, v] of Object.entries(opts.newDependencies)) dependencies[k] = v
|
|
282
|
+
for (const [k, v] of Object.entries(opts.newDevDependencies)) devDependencies[k] = v
|
|
283
|
+
dependencies['spine-framework'] = opts.spineVersion
|
|
284
|
+
|
|
285
|
+
const frameworkDeps = [
|
|
286
|
+
...Object.keys(opts.newDependencies),
|
|
287
|
+
...Object.keys(opts.newDevDependencies),
|
|
288
|
+
'spine-framework',
|
|
289
|
+
].sort()
|
|
290
|
+
|
|
291
|
+
const pkg = {
|
|
292
|
+
name: opts.existing?.name ?? opts.name,
|
|
293
|
+
version: opts.existing?.version ?? opts.version ?? '0.1.0',
|
|
294
|
+
private: true,
|
|
295
|
+
type: 'module',
|
|
296
|
+
...(opts.existing?.description ?? opts.description
|
|
297
|
+
? { description: opts.existing?.description ?? opts.description }
|
|
298
|
+
: {}),
|
|
299
|
+
...(opts.existing ?? {}),
|
|
300
|
+
scripts: { ...(opts.existing?.scripts ?? {}), ...opts.scripts },
|
|
301
|
+
dependencies,
|
|
302
|
+
devDependencies,
|
|
303
|
+
}
|
|
304
|
+
// Re-assert generated fields over any copied `existing` keys above.
|
|
305
|
+
pkg.private = true
|
|
306
|
+
pkg.type = 'module'
|
|
307
|
+
|
|
308
|
+
return { text: JSON.stringify(pkg, null, 2) + '\n', frameworkDeps }
|
|
309
|
+
}
|
|
310
|
+
// CIL[END stable_id="~/.framework/cli/lib/project-files.ts:package_merge:block"]
|
|
311
|
+
|
|
312
|
+
// CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:manifest:block" type="block" identity="manifest" parent="~/.framework/cli/lib/project-files.ts"]
|
|
313
|
+
// CIL[DOC-MACRO purpose="Write the frozen .spine-manifest.json — per-file hashes over the protected trees plus framework_version + framework_dependencies"]
|
|
314
|
+
// CIL[DOC-CONTEXT module="cli" layer="lib" related_files=".spine-manifest.json,scripts/checksum-manifest.mts"]
|
|
315
|
+
// CIL[DOC-CONTRACT inputs="opts: { projectRoot, frameworkVersion, frameworkDeps }" outputs=".spine-manifest.json written; {fileCount, treeDigest}" errors="none" side_effects="writes projectRoot/.spine-manifest.json"]
|
|
316
|
+
// CIL[DOC-MICRO logic_summary="Hashes every protected file and records framework_version + framework_dependencies so upgrade can detect local mods and owned deps" notable_decisions="manifest is the integrity contract — same hash inputs as scripts/checksum-manifest.mts verify"]
|
|
317
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
318
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
319
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
320
|
+
/** Write the frozen .spine-manifest.json for a project at the given framework version. */
|
|
321
|
+
export function writeFrozenManifest(opts: {
|
|
322
|
+
projectRoot: string
|
|
323
|
+
frameworkVersion: string
|
|
324
|
+
frameworkDeps: string[]
|
|
325
|
+
}): { fileCount: number; treeDigest: string } {
|
|
326
|
+
const { projectRoot } = opts
|
|
327
|
+
const entries = collectProtectedFiles(projectRoot, ['bin'], new Set(['.spine-manifest.json']))
|
|
328
|
+
const pairs = entries.map((e) => [e.rel, hashEntry(e)] as const)
|
|
329
|
+
const files: Record<string, string> = {}
|
|
330
|
+
for (const [rel, hash] of pairs) files[rel] = hash
|
|
331
|
+
const manifest = {
|
|
332
|
+
format_version: 1,
|
|
333
|
+
frozen: true,
|
|
334
|
+
framework_version: opts.frameworkVersion,
|
|
335
|
+
framework_dependencies: opts.frameworkDeps,
|
|
336
|
+
source_commit: null,
|
|
337
|
+
tree_digest: computeTreeDigest(pairs),
|
|
338
|
+
node: '>=22.13.0',
|
|
339
|
+
npm: '>=10.0.0',
|
|
340
|
+
files,
|
|
341
|
+
}
|
|
342
|
+
fs.writeFileSync(
|
|
343
|
+
path.join(projectRoot, '.spine-manifest.json'),
|
|
344
|
+
JSON.stringify(manifest, null, 2) + '\n',
|
|
345
|
+
)
|
|
346
|
+
return { fileCount: pairs.length, treeDigest: manifest.tree_digest }
|
|
347
|
+
}
|
|
348
|
+
// CIL[END stable_id="~/.framework/cli/lib/project-files.ts:manifest:block"]
|
|
349
|
+
|
|
350
|
+
// CIL[BEGIN stable_id="~/.framework/cli/lib/project-files.ts:file_io:block" type="block" identity="file_io" parent="~/.framework/cli/lib/project-files.ts"]
|
|
351
|
+
// CIL[DOC-MACRO purpose="Copy collected files (mode-preserving, symlink safety-checked), sanitize dir names to npm package names, compare semvers"]
|
|
352
|
+
// CIL[DOC-CONTEXT module="cli" layer="lib" related_files=""]
|
|
353
|
+
// CIL[DOC-CONTRACT inputs="files: CollectedFile[], projectRoot: string, name: string, a: string, b: string" outputs="copied tree; sanitized name; -1|0|1" errors="throws when a source symlink resolves outside pkgRoot" side_effects="copyCollectedFiles writes + chmods files under projectRoot"]
|
|
354
|
+
// CIL[DOC-MICRO logic_summary="copyCollectedFiles resolves symlinks (rejecting escapes), preserves executable bits, creates parent dirs; sanitizePackageName lowercases and strips invalid chars; cmpSemver compares numeric triples" notable_decisions="symlink escape guard keeps packaged payload inside the project"]
|
|
355
|
+
// CIL[DOC-CONFIDENCE pass="first" score="0.9" flags="none"]
|
|
356
|
+
// CIL[DOC-STABILITY stability="stable"]
|
|
357
|
+
// CIL[DOC-AUDIENCE audience="developer"]
|
|
358
|
+
/** Copy a collected file list into the project, preserving modes and safe symlinks. */
|
|
359
|
+
export function copyCollectedFiles(files: CollectedFile[], projectRoot: string): void {
|
|
360
|
+
for (const f of files) {
|
|
361
|
+
const dest = path.join(projectRoot, f.rel)
|
|
362
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true })
|
|
363
|
+
if (f.symlink) {
|
|
364
|
+
const target = fs.readlinkSync(f.abs)
|
|
365
|
+
const outResolved = path.resolve(path.dirname(dest), target)
|
|
366
|
+
if (!outResolved.startsWith(projectRoot + path.sep) && outResolved !== projectRoot) {
|
|
367
|
+
throw new Error(`unsafe symlink: ${f.rel} → ${target} resolves outside the project tree`)
|
|
368
|
+
}
|
|
369
|
+
try { fs.rmSync(dest, { force: true }) } catch { /* absent */ }
|
|
370
|
+
fs.symlinkSync(target, dest)
|
|
371
|
+
} else {
|
|
372
|
+
fs.copyFileSync(f.abs, dest)
|
|
373
|
+
fs.chmodSync(dest, fs.statSync(f.abs).mode)
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Sanitize a directory basename into a valid npm package name. */
|
|
379
|
+
export function sanitizePackageName(name: string): string {
|
|
380
|
+
const cleaned = name
|
|
381
|
+
.toLowerCase()
|
|
382
|
+
.replace(/[^a-z0-9._~+-]+/g, '-')
|
|
383
|
+
.replace(/^[._-]+/, '')
|
|
384
|
+
.replace(/[._-]+$/, '')
|
|
385
|
+
return cleaned || 'spine-project'
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/** Compare two semver strings (numeric core + prerelease); returns -1/0/1. */
|
|
389
|
+
export function cmpSemver(a: string, b: string): number {
|
|
390
|
+
const parse = (v: string) => {
|
|
391
|
+
const [core, pre] = v.split('-', 2)
|
|
392
|
+
const nums = core.split('.').map((n) => parseInt(n, 10) || 0)
|
|
393
|
+
return { nums, pre: pre ?? null }
|
|
394
|
+
}
|
|
395
|
+
const pa = parse(a); const pb = parse(b)
|
|
396
|
+
for (let i = 0; i < 3; i++) {
|
|
397
|
+
if (pa.nums[i] !== pb.nums[i]) return pa.nums[i] < pb.nums[i] ? -1 : 1
|
|
398
|
+
}
|
|
399
|
+
if (pa.pre === pb.pre) return 0
|
|
400
|
+
if (pa.pre === null) return 1
|
|
401
|
+
if (pb.pre === null) return -1
|
|
402
|
+
return pa.pre < pb.pre ? -1 : 1
|
|
403
|
+
}
|
|
404
|
+
// CIL[END stable_id="~/.framework/cli/lib/project-files.ts:file_io:block"]
|
|
@@ -37,7 +37,7 @@ See `.windsurf/workflows/run-tests.md` for the full step-by-step workflow.
|
|
|
37
37
|
Integration, API, and UI tests need credentials in `v2-core/.xenv` or `v2-core/.xenv.test`:
|
|
38
38
|
|
|
39
39
|
```env
|
|
40
|
-
SUPABASE_URL=https
|
|
40
|
+
SUPABASE_URL=https://<your-project-ref>.supabase.co
|
|
41
41
|
SUPABASE_SERVICE_ROLE_KEY=<service-role-key>
|
|
42
42
|
SPINE_TEST_ACCOUNT_ID=<uuid>
|
|
43
43
|
DB_SCHEMA=public
|