forgeos 0.1.0-alpha.4 → 0.1.0-alpha.41
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/.npmignore +10 -0
- package/AGENTS.md +142 -90
- package/CHANGELOG.md +433 -0
- package/LICENSE +21 -0
- package/README.md +67 -8
- package/adapters/go/README.md +23 -0
- package/adapters/go/go.mod +3 -0
- package/adapters/go/http.go +149 -0
- package/adapters/go/registry.go +234 -0
- package/adapters/go/types.go +136 -0
- package/adapters/java/README.md +68 -0
- package/adapters/java/pom.xml +34 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Auth.java +20 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Diagnostic.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Entry.java +38 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/EntryKind.java +16 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ErrorInfo.java +4 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Forge.java +94 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeCall.java +12 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeContext.java +11 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHandler.java +8 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeHttpHandler.java +179 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ForgeRegistry.java +121 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Json.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Manifest.java +14 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/RequestEnvelope.java +6 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/ResponseEnvelope.java +25 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Risk.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Schemas.java +36 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/Service.java +65 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TransactionMode.java +18 -0
- package/adapters/java/src/main/java/dev/forgeos/adapter/TypedForgeHandler.java +6 -0
- package/adapters/java-spring-boot-starter/README.md +32 -0
- package/adapters/java-spring-boot-starter/pom.xml +36 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeCommand.java +22 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeExternalService.java +15 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeQuery.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeServiceBeanCondition.java +18 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringAutoConfiguration.java +16 -0
- package/adapters/java-spring-boot-starter/src/main/java/dev/forgeos/adapter/spring/ForgeSpringRuntime.java +104 -0
- package/adapters/java-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports +1 -0
- package/bin/forge.mjs +18 -0
- package/docs/cair-protocol.md +103 -0
- package/docs/changelog.md +548 -0
- package/docs/forge-protocol.md +189 -0
- package/examples/go-billing/go.mod +7 -0
- package/examples/go-billing/main.go +120 -0
- package/examples/java-billing/pom.xml +52 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/CreateInvoiceInput.java +4 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Invoice.java +11 -0
- package/examples/java-billing/src/main/java/dev/forgeos/examples/billing/Main.java +127 -0
- package/package.json +31 -3
- package/schemas/forge-manifest.schema.json +57 -0
- package/src/forge/_generated/releaseManifest.json +1 -2
- package/src/forge/_generated/releaseManifest.ts +3 -3
- package/src/forge/agent-adapters/index.ts +1584 -125
- package/src/forge/agent-adapters/types.ts +224 -1
- package/src/forge/agent-memory/bridge.ts +1333 -0
- package/src/forge/agent-memory/context-pack.ts +277 -0
- package/src/forge/agent-memory/hook-runner.ts +312 -0
- package/src/forge/agent-memory/mcp.ts +224 -0
- package/src/forge/agent-memory/normalize.ts +498 -0
- package/src/forge/agent-memory/redaction.ts +103 -0
- package/src/forge/agent-memory/sources/claude-code.ts +51 -0
- package/src/forge/agent-memory/sources/codex-hook-runner.mjs +273 -0
- package/src/forge/agent-memory/sources/codex.ts +119 -0
- package/src/forge/agent-memory/sources/cursor.ts +35 -0
- package/src/forge/agent-memory/types.ts +204 -0
- package/src/forge/bench.ts +248 -0
- package/src/forge/brownfield-import/index.ts +801 -0
- package/src/forge/brownfield-import/types.ts +127 -0
- package/src/forge/cair/action-journal.ts +61 -0
- package/src/forge/cair/action-parser.ts +314 -0
- package/src/forge/cair/action-validator.ts +40 -0
- package/src/forge/cair/actions.ts +1818 -0
- package/src/forge/cair/format.ts +77 -0
- package/src/forge/cair/index.ts +106 -0
- package/src/forge/cair/query.ts +478 -0
- package/src/forge/cair/snapshot.ts +315 -0
- package/src/forge/cair/types.ts +248 -0
- package/src/forge/cli/ai.ts +339 -21
- package/src/forge/cli/auth.ts +348 -10
- package/src/forge/cli/authmd.ts +441 -0
- package/src/forge/cli/baseline.ts +112 -0
- package/src/forge/cli/build.ts +20 -4
- package/src/forge/cli/changed.ts +437 -0
- package/src/forge/cli/codex-app-server.ts +877 -0
- package/src/forge/cli/commands.ts +1620 -20
- package/src/forge/cli/db.ts +209 -2
- package/src/forge/cli/deploy.ts +887 -0
- package/src/forge/cli/deps.ts +82 -13
- package/src/forge/cli/dev.ts +961 -46
- package/src/forge/cli/docs.ts +265 -0
- package/src/forge/cli/doctor.ts +297 -0
- package/src/forge/cli/field-test.ts +329 -0
- package/src/forge/cli/handoff.ts +336 -0
- package/src/forge/cli/index.ts +1 -0
- package/src/forge/cli/last-run.ts +84 -0
- package/src/forge/cli/main.ts +107 -3
- package/src/forge/cli/new.ts +152 -13
- package/src/forge/cli/next-actions.ts +23 -0
- package/src/forge/cli/output.ts +389 -6
- package/src/forge/cli/parse.ts +1050 -34
- package/src/forge/cli/progress.ts +51 -0
- package/src/forge/cli/query.ts +32 -0
- package/src/forge/cli/release.ts +35 -11
- package/src/forge/cli/rls.ts +43 -4
- package/src/forge/cli/run.ts +41 -0
- package/src/forge/cli/security.ts +112 -0
- package/src/forge/cli/self-host.ts +56 -14
- package/src/forge/cli/studio.ts +2218 -0
- package/src/forge/cli/verify.ts +1455 -32
- package/src/forge/cli/windows.ts +23 -0
- package/src/forge/cli/workos.ts +1020 -0
- package/src/forge/compiler/agent-contract/build.ts +670 -64
- package/src/forge/compiler/agent-contract/types.ts +45 -2
- package/src/forge/compiler/ai-registry/parse.ts +71 -2
- package/src/forge/compiler/api-surface/build.ts +47 -0
- package/src/forge/compiler/app-graph/build.ts +112 -9
- package/src/forge/compiler/app-graph/extract.ts +107 -0
- package/src/forge/compiler/app-graph/module-graph.ts +73 -78
- package/src/forge/compiler/app-graph/parser.ts +24 -24
- package/src/forge/compiler/app-graph/profile.ts +26 -0
- package/src/forge/compiler/app-graph/versions.ts +1 -1
- package/src/forge/compiler/classifier/capabilities.ts +3 -2
- package/src/forge/compiler/classifier/classify.ts +32 -8
- package/src/forge/compiler/classifier/secrets.ts +3 -2
- package/src/forge/compiler/classifier/signals.ts +91 -1
- package/src/forge/compiler/client-sdk/build-manifest.ts +59 -0
- package/src/forge/compiler/client-sdk/render-client.ts +295 -13
- package/src/forge/compiler/data-graph/parse.ts +20 -4
- package/src/forge/compiler/data-graph/sql/ddl.ts +144 -31
- package/src/forge/compiler/data-graph/sql/serialize.ts +4 -0
- package/src/forge/compiler/data-graph/sql/types.ts +1 -0
- package/src/forge/compiler/diagnostics/codes.ts +30 -0
- package/src/forge/compiler/diagnostics/create.ts +21 -2
- package/src/forge/compiler/diagnostics/index.ts +2 -0
- package/src/forge/compiler/emitter/barrel.ts +3 -0
- package/src/forge/compiler/emitter/render.ts +9 -0
- package/src/forge/compiler/external-manifest/registry.ts +205 -0
- package/src/forge/compiler/external-manifest/types.ts +91 -0
- package/src/forge/compiler/external-manifest/validate.ts +373 -0
- package/src/forge/compiler/frontend-graph/build.ts +142 -14
- package/src/forge/compiler/integration/add.ts +618 -24
- package/src/forge/compiler/integration/plan.ts +43 -12
- package/src/forge/compiler/integration/render.ts +29 -0
- package/src/forge/compiler/integration/snapshot.ts +2 -0
- package/src/forge/compiler/integration/templates/convex.ts +70 -0
- package/src/forge/compiler/integration/templates/index.ts +1 -0
- package/src/forge/compiler/integration/templates/render.ts +38 -0
- package/src/forge/compiler/integration/templates/types.ts +3 -0
- package/src/forge/compiler/integration/templates/workos.ts +1219 -0
- package/src/forge/compiler/make-registry/build.ts +8 -9
- package/src/forge/compiler/orchestrator/fast-check.ts +4 -0
- package/src/forge/compiler/orchestrator/generate-lock.ts +14 -3
- package/src/forge/compiler/orchestrator/manifest.ts +1 -1
- package/src/forge/compiler/orchestrator/plan-profile.ts +23 -0
- package/src/forge/compiler/orchestrator/plan.ts +247 -10
- package/src/forge/compiler/orchestrator/profile.ts +65 -0
- package/src/forge/compiler/orchestrator/run.ts +97 -31
- package/src/forge/compiler/orchestrator/serialize.ts +33 -8
- package/src/forge/compiler/orchestrator/types.ts +1 -1
- package/src/forge/compiler/package-graph/compiler.ts +3 -1
- package/src/forge/compiler/package-graph/constants.ts +1 -1
- package/src/forge/compiler/package-manager/adapter.ts +4 -1
- package/src/forge/compiler/package-manager/commands.ts +4 -0
- package/src/forge/compiler/package-manager/executor.ts +30 -1
- package/src/forge/compiler/package-manager/version.ts +36 -12
- package/src/forge/compiler/package-upgrades/planner.ts +155 -18
- package/src/forge/compiler/package-upgrades/types.ts +2 -0
- package/src/forge/compiler/policy-registry/build.ts +47 -2
- package/src/forge/compiler/policy-registry/parse.ts +32 -2
- package/src/forge/compiler/recipes/definitions.ts +63 -0
- package/src/forge/compiler/recipes/index.ts +2 -0
- package/src/forge/compiler/recipes/registry.ts +6 -0
- package/src/forge/compiler/test-graph/build.ts +11 -3
- package/src/forge/compiler/types/app-graph.ts +8 -2
- package/src/forge/compiler/types/cli.ts +75 -1
- package/src/forge/compiler/types/dev-manifest.ts +4 -0
- package/src/forge/compiler/types/emit.ts +2 -0
- package/src/forge/compiler/types/frontend-graph.ts +2 -2
- package/src/forge/compiler/types/integration.ts +1 -0
- package/src/forge/compiler/types/package-graph.ts +6 -0
- package/src/forge/compiler/types/policy-registry.ts +3 -1
- package/src/forge/delta/classifier.ts +52 -0
- package/src/forge/delta/explain.ts +238 -0
- package/src/forge/delta/git-observer.ts +43 -0
- package/src/forge/delta/ids.ts +44 -0
- package/src/forge/delta/index.ts +25 -0
- package/src/forge/delta/recorder.ts +540 -0
- package/src/forge/delta/redaction.ts +50 -0
- package/src/forge/delta/schema.ts +240 -0
- package/src/forge/delta/session.ts +142 -0
- package/src/forge/delta/status.ts +1202 -0
- package/src/forge/delta/store.ts +3284 -0
- package/src/forge/delta/timeline.ts +178 -0
- package/src/forge/dev/server.ts +808 -17
- package/src/forge/dev/types.ts +15 -1
- package/src/forge/dev/watch.ts +17 -7
- package/src/forge/dev-console/cycle.ts +280 -33
- package/src/forge/dev-console/types.ts +47 -1
- package/src/forge/impact/index.ts +204 -9
- package/src/forge/impact/types.ts +33 -1
- package/src/forge/intent/index.ts +35 -16
- package/src/forge/make/fields.ts +26 -0
- package/src/forge/make/index.ts +40 -6
- package/src/forge/make/templates.ts +155 -2
- package/src/forge/make/types.ts +2 -1
- package/src/forge/policy.ts +1 -1
- package/src/forge/react/index.ts +160 -2
- package/src/forge/refactor/index.ts +1 -0
- package/src/forge/repair/rules/index.ts +2 -2
- package/src/forge/review/index.ts +158 -12
- package/src/forge/review/types.ts +15 -0
- package/src/forge/runtime/auth/config.ts +17 -0
- package/src/forge/runtime/auth/evaluate.ts +15 -2
- package/src/forge/runtime/auth/resolve.ts +61 -6
- package/src/forge/runtime/auth/types.ts +1 -0
- package/src/forge/runtime/db/factory.ts +1 -3
- package/src/forge/runtime/db/generated-client.ts +13 -2
- package/src/forge/runtime/db/memory-adapter.ts +139 -32
- package/src/forge/runtime/db/pglite-adapter.ts +263 -2
- package/src/forge/runtime/db/postgres-adapter.ts +6 -3
- package/src/forge/runtime/executor.ts +129 -16
- package/src/forge/runtime/external/bridge.ts +649 -0
- package/src/forge/runtime/runner/run-entry.ts +16 -7
- package/src/forge/runtime/telemetry/buffer.ts +4 -1
- package/src/forge/runtime/telemetry/scrubber.ts +35 -5
- package/src/forge/runtime/webhooks/security.ts +12 -7
- package/src/forge/server.ts +17 -3
- package/src/forge/ui/index.ts +209 -0
- package/src/forge/ui/types.ts +1 -0
- package/src/forge/version.ts +1 -1
- package/src/forge/vue/index.ts +422 -0
- package/src/forge/workspace/baseline.ts +112 -0
- package/src/forge/workspace/change-summary.ts +250 -0
- package/src/forge/workspace/forge-cli.ts +42 -0
- package/src/forge/workspace/git-summary.ts +391 -0
- package/templates/agent-workroom/AGENTS.md +29 -0
- package/templates/agent-workroom/README.md +34 -0
- package/templates/agent-workroom/forge.config.ts +3 -0
- package/templates/agent-workroom/package.json +33 -0
- package/templates/agent-workroom/src/actions/indexAgentSignal.ts +10 -0
- package/templates/agent-workroom/src/commands/openWorkroom.ts +61 -0
- package/templates/agent-workroom/src/commands/recordAgentSignal.ts +119 -0
- package/templates/agent-workroom/src/commands/recordCheckRun.ts +52 -0
- package/templates/agent-workroom/src/forge/schema.ts +54 -0
- package/templates/agent-workroom/src/policies.ts +6 -0
- package/templates/agent-workroom/src/queries/listWorkrooms.ts +11 -0
- package/templates/agent-workroom/src/queries/liveWorkroom.ts +63 -0
- package/templates/agent-workroom/tsconfig.json +16 -0
- package/templates/agent-workroom/web/index.html +12 -0
- package/templates/agent-workroom/web/package.json +21 -0
- package/templates/agent-workroom/web/src/App.tsx +345 -0
- package/templates/agent-workroom/web/src/lib/forge.ts +13 -0
- package/templates/agent-workroom/web/src/main.tsx +13 -0
- package/templates/agent-workroom/web/src/styles.css +545 -0
- package/templates/agent-workroom/web/tsconfig.json +27 -0
- package/templates/b2b-support-web/package.json +1 -0
- package/templates/b2b-support-web/web/package.json +1 -1
- package/templates/minimal-web/package.json +1 -1
- package/templates/minimal-web/web/package.json +2 -2
- package/templates/nuxt-web/.vscode/settings.json +14 -0
- package/templates/nuxt-web/README.md +30 -0
- package/templates/nuxt-web/forge.config.ts +3 -0
- package/templates/nuxt-web/package.json +33 -0
- package/templates/nuxt-web/src/actions/logNoteCreated.ts +11 -0
- package/templates/nuxt-web/src/commands/createNote.ts +26 -0
- package/templates/nuxt-web/src/forge/schema.ts +12 -0
- package/templates/nuxt-web/src/policies.ts +6 -0
- package/templates/nuxt-web/src/queries/listNotes.ts +8 -0
- package/templates/nuxt-web/src/queries/liveNotes.ts +8 -0
- package/templates/nuxt-web/tsconfig.json +17 -0
- package/templates/nuxt-web/web/app.vue +67 -0
- package/templates/nuxt-web/web/components/LiveNotes.vue +89 -0
- package/templates/nuxt-web/web/components/NoteComposer.vue +100 -0
- package/templates/nuxt-web/web/composables/forge.ts +13 -0
- package/templates/nuxt-web/web/composables/useNotes.ts +24 -0
- package/templates/nuxt-web/web/nuxt.config.ts +11 -0
- package/templates/nuxt-web/web/package.json +18 -0
- package/templates/nuxt-web/web/plugins/forge.client.ts +10 -0
- package/templates/nuxt-web/web/plugins/forge.server.ts +10 -0
- package/templates/nuxt-web/web/server/api/forge-health.get.ts +7 -0
- package/templates/nuxt-web/web/tsconfig.json +6 -0
- package/src/forge/_generated/actionSubscriptions.json +0 -2
- package/src/forge/_generated/actionSubscriptions.ts +0 -10
- package/src/forge/_generated/agentAdapterManifest.json +0 -2
- package/src/forge/_generated/agentAdapterManifest.ts +0 -73
- package/src/forge/_generated/agentContract.json +0 -2
- package/src/forge/_generated/agentContract.ts +0 -7829
- package/src/forge/_generated/agentQuickstart.md +0 -34
- package/src/forge/_generated/agentTools.json +0 -2
- package/src/forge/_generated/agentTools.md +0 -16
- package/src/forge/_generated/agentTools.ts +0 -12
- package/src/forge/_generated/aiContext.ts +0 -125
- package/src/forge/_generated/aiModels.json +0 -2
- package/src/forge/_generated/aiModels.ts +0 -51
- package/src/forge/_generated/aiProviders.json +0 -2
- package/src/forge/_generated/aiProviders.ts +0 -23
- package/src/forge/_generated/aiRegistry.json +0 -2
- package/src/forge/_generated/aiRegistry.ts +0 -31
- package/src/forge/_generated/api.json +0 -2
- package/src/forge/_generated/api.ts +0 -8
- package/src/forge/_generated/appGraph.json +0 -2
- package/src/forge/_generated/appGraph.ts +0 -14919
- package/src/forge/_generated/appMap.md +0 -55
- package/src/forge/_generated/artifactManifest.json +0 -2
- package/src/forge/_generated/artifactManifest.ts +0 -7
- package/src/forge/_generated/authClaims.json +0 -2
- package/src/forge/_generated/authClaims.ts +0 -13
- package/src/forge/_generated/authConfig.json +0 -2
- package/src/forge/_generated/authConfig.ts +0 -17
- package/src/forge/_generated/authContext.ts +0 -23
- package/src/forge/_generated/authRegistry.json +0 -2
- package/src/forge/_generated/authRegistry.ts +0 -25
- package/src/forge/_generated/buildInfo.json +0 -2
- package/src/forge/_generated/buildInfo.ts +0 -9
- package/src/forge/_generated/capabilityMap.json +0 -2
- package/src/forge/_generated/capabilityMap.md +0 -15
- package/src/forge/_generated/capabilityMap.ts +0 -17
- package/src/forge/_generated/client.ts +0 -282
- package/src/forge/_generated/clientApi.ts +0 -9
- package/src/forge/_generated/clientManifest.json +0 -2
- package/src/forge/_generated/clientManifest.ts +0 -39
- package/src/forge/_generated/clientTypes.ts +0 -78
- package/src/forge/_generated/configRegistry.json +0 -2
- package/src/forge/_generated/configRegistry.ts +0 -4
- package/src/forge/_generated/dataGraph.json +0 -2
- package/src/forge/_generated/dataGraph.ts +0 -8
- package/src/forge/_generated/db.json +0 -2
- package/src/forge/_generated/db.ts +0 -2
- package/src/forge/_generated/dbSecurityManifest.json +0 -2
- package/src/forge/_generated/dbSecurityManifest.ts +0 -15
- package/src/forge/_generated/dbSessionContext.json +0 -2
- package/src/forge/_generated/dbSessionContext.ts +0 -39
- package/src/forge/_generated/deployManifest.json +0 -2
- package/src/forge/_generated/deployManifest.ts +0 -14
- package/src/forge/_generated/devManifest.json +0 -2
- package/src/forge/_generated/devManifest.ts +0 -62
- package/src/forge/_generated/envSchema.json +0 -2
- package/src/forge/_generated/envSchema.ts +0 -59
- package/src/forge/_generated/frontendGraph.json +0 -2
- package/src/forge/_generated/frontendGraph.ts +0 -27
- package/src/forge/_generated/importGuards.json +0 -2
- package/src/forge/_generated/importGuards.ts +0 -686
- package/src/forge/_generated/index.ts +0 -68
- package/src/forge/_generated/liveProductionManifest.json +0 -2
- package/src/forge/_generated/liveProductionManifest.ts +0 -23
- package/src/forge/_generated/liveProtocol.json +0 -2
- package/src/forge/_generated/liveProtocol.ts +0 -21
- package/src/forge/_generated/liveQueryRegistry.json +0 -2
- package/src/forge/_generated/liveQueryRegistry.ts +0 -9
- package/src/forge/_generated/liveTransportConfig.json +0 -2
- package/src/forge/_generated/liveTransportConfig.ts +0 -19
- package/src/forge/_generated/makeRegistry.json +0 -2
- package/src/forge/_generated/makeRegistry.ts +0 -177
- package/src/forge/_generated/makeTemplates.json +0 -2
- package/src/forge/_generated/makeTemplates.ts +0 -66
- package/src/forge/_generated/mockMap.json +0 -2
- package/src/forge/_generated/mockMap.ts +0 -7
- package/src/forge/_generated/operationPlaybooks.md +0 -167
- package/src/forge/_generated/packageGraph.json +0 -2
- package/src/forge/_generated/packageGraph.ts +0 -249334
- package/src/forge/_generated/packageUpgradeRegistry.json +0 -2
- package/src/forge/_generated/packageUpgradeRegistry.ts +0 -15
- package/src/forge/_generated/permissionMatrix.json +0 -2
- package/src/forge/_generated/permissionMatrix.ts +0 -7
- package/src/forge/_generated/policyRegistry.json +0 -2
- package/src/forge/_generated/policyRegistry.ts +0 -11
- package/src/forge/_generated/queryRegistry.json +0 -2
- package/src/forge/_generated/queryRegistry.ts +0 -9
- package/src/forge/_generated/react.d.ts +0 -22
- package/src/forge/_generated/react.ts +0 -29
- package/src/forge/_generated/reactManifest.json +0 -2
- package/src/forge/_generated/reactManifest.ts +0 -19
- package/src/forge/_generated/rlsPolicies.json +0 -2
- package/src/forge/_generated/rlsPolicies.sql +0 -34
- package/src/forge/_generated/rlsPolicies.ts +0 -6
- package/src/forge/_generated/runtimeGraph.json +0 -2
- package/src/forge/_generated/runtimeGraph.ts +0 -8
- package/src/forge/_generated/runtimeMatrix.json +0 -2
- package/src/forge/_generated/runtimeMatrix.ts +0 -334130
- package/src/forge/_generated/runtimeRegistry.ts +0 -2
- package/src/forge/_generated/runtimeRules.md +0 -91
- package/src/forge/_generated/secretRegistry.json +0 -2
- package/src/forge/_generated/secretRegistry.ts +0 -50
- package/src/forge/_generated/secretsContext.ts +0 -11
- package/src/forge/_generated/serverApi.ts +0 -10
- package/src/forge/_generated/sourceMapManifest.json +0 -2
- package/src/forge/_generated/sourceMapManifest.ts +0 -7
- package/src/forge/_generated/sqlPlan.json +0 -2
- package/src/forge/_generated/sqlPlan.ts +0 -88
- package/src/forge/_generated/subscriptionManifest.json +0 -2
- package/src/forge/_generated/subscriptionManifest.ts +0 -7
- package/src/forge/_generated/symbolicationManifest.json +0 -2
- package/src/forge/_generated/symbolicationManifest.ts +0 -17
- package/src/forge/_generated/telemetryRegistry.json +0 -2
- package/src/forge/_generated/telemetryRegistry.ts +0 -9
- package/src/forge/_generated/telemetrySinks.json +0 -2
- package/src/forge/_generated/telemetrySinks.ts +0 -11
- package/src/forge/_generated/tenantScope.json +0 -2
- package/src/forge/_generated/tenantScope.ts +0 -8
- package/src/forge/_generated/testGraph.json +0 -2
- package/src/forge/_generated/testGraph.ts +0 -3430
- package/src/forge/_generated/testPlanRegistry.json +0 -2
- package/src/forge/_generated/testPlanRegistry.ts +0 -33
- package/src/forge/_generated/uiRoutes.json +0 -2
- package/src/forge/_generated/uiRoutes.ts +0 -16
- package/src/forge/_generated/uiScenarios.json +0 -2
- package/src/forge/_generated/uiScenarios.ts +0 -30
- package/src/forge/_generated/uiTestManifest.json +0 -2
- package/src/forge/_generated/uiTestManifest.ts +0 -27
- package/src/forge/_generated/workflowRegistry.json +0 -2
- package/src/forge/_generated/workflowRegistry.ts +0 -9
- package/src/forge/_generated/workflowSubscriptions.json +0 -2
- package/src/forge/_generated/workflowSubscriptions.ts +0 -10
package/src/forge/dev/server.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import {
|
|
3
|
+
createHmac,
|
|
4
|
+
timingSafeEqual,
|
|
5
|
+
} from "node:crypto";
|
|
6
|
+
import { createRequire } from "node:module";
|
|
2
7
|
import {
|
|
3
8
|
createServer,
|
|
4
9
|
type IncomingMessage,
|
|
@@ -10,6 +15,7 @@ import { pathToFileURL } from "node:url";
|
|
|
10
15
|
import { createDiagnostic } from "../compiler/diagnostics/create.ts";
|
|
11
16
|
import {
|
|
12
17
|
FORGE_AUTH_DEV_HEADERS_IN_PRODUCTION,
|
|
18
|
+
FORGE_DEV_DB_RESET,
|
|
13
19
|
FORGE_DEV_INVOKE_FAILED,
|
|
14
20
|
FORGE_DEV_SERVER_ERROR,
|
|
15
21
|
FORGE_POLICY_DENIED,
|
|
@@ -22,13 +28,16 @@ import type { TableMapEntry } from "../compiler/data-graph/sql/serialize.ts";
|
|
|
22
28
|
import type { SqlPlan } from "../compiler/data-graph/sql/types.ts";
|
|
23
29
|
import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
|
|
24
30
|
import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
|
|
25
|
-
import type { DevManifest } from "../compiler/types/dev-manifest.ts";
|
|
31
|
+
import type { DevManifest, DevRoute } from "../compiler/types/dev-manifest.ts";
|
|
32
|
+
import type { Diagnostic } from "../compiler/types/diagnostic.ts";
|
|
26
33
|
import type { RuntimeGraph } from "../compiler/types/runtime-graph.ts";
|
|
27
34
|
import { createDbAdapter } from "../runtime/db/factory.ts";
|
|
28
|
-
import { applyMigrations } from "../runtime/db/migrate.ts";
|
|
35
|
+
import { applyMigrations, getMigrationStatus, resetDatabase } from "../runtime/db/migrate.ts";
|
|
29
36
|
import {
|
|
37
|
+
disposeRuntimeModuleNamespace,
|
|
30
38
|
listEntries,
|
|
31
39
|
prepareRuntimeEnvironment,
|
|
40
|
+
refreshRuntimeModuleNamespace,
|
|
32
41
|
runEntry,
|
|
33
42
|
} from "../runtime/executor.ts";
|
|
34
43
|
import { listQueries, runQuery } from "../runtime/query/run-query.ts";
|
|
@@ -49,7 +58,7 @@ import { loadWorkflowRegistry } from "../runtime/workflows/registry.ts";
|
|
|
49
58
|
import { retryWorkflowRun } from "../runtime/workflows/retry-run.ts";
|
|
50
59
|
import { hashStable } from "../compiler/primitives/hash.ts";
|
|
51
60
|
import { canonicalJson } from "../compiler/primitives/serialize.ts";
|
|
52
|
-
import type { DevServerHandle, DevServerOptions, DevServerState } from "./types.ts";
|
|
61
|
+
import type { DevServerHandle, DevServerOptions, DevServerReloadResult, DevServerState } from "./types.ts";
|
|
53
62
|
import { getTelemetrySummary, inspectTrace } from "../runtime/telemetry/flush.ts";
|
|
54
63
|
import { processTelemetryBatch } from "../runtime/telemetry/process.ts";
|
|
55
64
|
import { getRuntimeEnvStore } from "../runtime/context/create-context.ts";
|
|
@@ -69,12 +78,47 @@ import type { AgentToolRegistry } from "../compiler/agent-contract/types.ts";
|
|
|
69
78
|
import { loadLiveQueryRegistry } from "../runtime/live/registry.ts";
|
|
70
79
|
import { createLiveSubscriptionManager } from "../runtime/live/subscription-manager.ts";
|
|
71
80
|
import { createSseResponse } from "../runtime/live/sse.ts";
|
|
81
|
+
import {
|
|
82
|
+
MemoryWebhookReplayStore,
|
|
83
|
+
verifyWebhookSignature,
|
|
84
|
+
} from "../runtime/webhooks/security.ts";
|
|
72
85
|
import {
|
|
73
86
|
ensureLiveInvalidationSchema,
|
|
74
87
|
listLiveInvalidations,
|
|
75
88
|
} from "../runtime/live/invalidation-log.ts";
|
|
76
89
|
import { currentReleaseInfo } from "../runtime/release/runtime.ts";
|
|
77
90
|
import { DEFAULT_LIVE_LIMITS } from "../runtime/live/types.ts";
|
|
91
|
+
import {
|
|
92
|
+
loadExternalServiceGraph,
|
|
93
|
+
runExternalEntry,
|
|
94
|
+
} from "../runtime/external/bridge.ts";
|
|
95
|
+
|
|
96
|
+
async function applyDevMigrations(
|
|
97
|
+
adapter: NonNullable<Awaited<ReturnType<typeof createDbAdapter>>["adapter"]>,
|
|
98
|
+
sqlPlan: SqlPlan,
|
|
99
|
+
dbMode: DevServerOptions["db"],
|
|
100
|
+
): Promise<Diagnostic[]> {
|
|
101
|
+
const shouldAutoReset =
|
|
102
|
+
(dbMode === "pglite" || dbMode === "memory") &&
|
|
103
|
+
process.env.FORGE_DEV_AUTO_RESET_DB !== "0";
|
|
104
|
+
|
|
105
|
+
if (shouldAutoReset) {
|
|
106
|
+
const status = await getMigrationStatus(adapter).catch(() => null);
|
|
107
|
+
const appliedChecksum = status?.applied.at(-1)?.checksum ?? null;
|
|
108
|
+
if (appliedChecksum && appliedChecksum !== sqlPlan.checksum) {
|
|
109
|
+
return [
|
|
110
|
+
createDiagnostic({
|
|
111
|
+
severity: "warning",
|
|
112
|
+
code: FORGE_DEV_DB_RESET,
|
|
113
|
+
message: "local dev database schema changed; reset app tables automatically",
|
|
114
|
+
}),
|
|
115
|
+
...(await resetDatabase(adapter, sqlPlan)),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return applyMigrations(adapter, sqlPlan);
|
|
121
|
+
}
|
|
78
122
|
|
|
79
123
|
interface FetchServer {
|
|
80
124
|
hostname?: string;
|
|
@@ -96,7 +140,7 @@ async function readIncomingBody(request: IncomingMessage): Promise<Buffer | unde
|
|
|
96
140
|
|
|
97
141
|
async function nodeRequestToFetch(
|
|
98
142
|
request: IncomingMessage,
|
|
99
|
-
input: { host: string; port: number },
|
|
143
|
+
input: { host: string; port: number; signal?: AbortSignal },
|
|
100
144
|
): Promise<Request> {
|
|
101
145
|
const headers = new Headers();
|
|
102
146
|
for (const [name, value] of Object.entries(request.headers)) {
|
|
@@ -113,6 +157,7 @@ async function nodeRequestToFetch(
|
|
|
113
157
|
method: request.method,
|
|
114
158
|
headers,
|
|
115
159
|
body: body ? new Blob([new Uint8Array(body)]) : undefined,
|
|
160
|
+
signal: input.signal,
|
|
116
161
|
...(body ? { duplex: "half" as const } : {}),
|
|
117
162
|
};
|
|
118
163
|
return new Request(url, init);
|
|
@@ -155,10 +200,13 @@ async function createNodeFetchServer(
|
|
|
155
200
|
): Promise<FetchServer> {
|
|
156
201
|
let actualPort = input.port;
|
|
157
202
|
const nodeServer: NodeHttpServer = createServer(async (request, response) => {
|
|
203
|
+
const controller = new AbortController();
|
|
204
|
+
response.once("close", () => controller.abort());
|
|
158
205
|
try {
|
|
159
206
|
const fetchRequest = await nodeRequestToFetch(request, {
|
|
160
207
|
host: input.hostname,
|
|
161
208
|
port: actualPort,
|
|
209
|
+
signal: controller.signal,
|
|
162
210
|
});
|
|
163
211
|
const fetchResponse = await fetchHandler(fetchRequest);
|
|
164
212
|
await writeFetchResponse(response, fetchResponse);
|
|
@@ -263,17 +311,274 @@ function htmlResponse(body: string, status = 200): Response {
|
|
|
263
311
|
});
|
|
264
312
|
}
|
|
265
313
|
|
|
314
|
+
function redirectResponse(location: string, extraHeaders: Record<string, string> = {}): Response {
|
|
315
|
+
return new Response(null, {
|
|
316
|
+
status: 302,
|
|
317
|
+
headers: {
|
|
318
|
+
Location: location,
|
|
319
|
+
"Access-Control-Allow-Origin": "*",
|
|
320
|
+
...extraHeaders,
|
|
321
|
+
},
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
function markdownResponse(body: string, status = 200): Response {
|
|
326
|
+
return new Response(body, {
|
|
327
|
+
status,
|
|
328
|
+
headers: {
|
|
329
|
+
"Content-Type": "text/markdown; charset=utf-8",
|
|
330
|
+
"Access-Control-Allow-Origin": "*",
|
|
331
|
+
},
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
266
335
|
function corsPreflight(): Response {
|
|
336
|
+
const allowedHeaders = [
|
|
337
|
+
"Authorization",
|
|
338
|
+
"Content-Type",
|
|
339
|
+
"WorkOS-Signature",
|
|
340
|
+
"x-forge-user-id",
|
|
341
|
+
"x-forge-tenant-id",
|
|
342
|
+
"x-forge-organization-id",
|
|
343
|
+
"x-forge-organization-membership-id",
|
|
344
|
+
"x-forge-role",
|
|
345
|
+
"x-forge-roles",
|
|
346
|
+
"x-forge-permissions",
|
|
347
|
+
"x-forge-claims",
|
|
348
|
+
"x-forge-auth-kind",
|
|
349
|
+
"x-forge-trace-id",
|
|
350
|
+
];
|
|
267
351
|
return new Response(null, {
|
|
268
352
|
status: 204,
|
|
269
353
|
headers: {
|
|
270
354
|
"Access-Control-Allow-Origin": "*",
|
|
271
355
|
"Access-Control-Allow-Methods": "GET, POST, OPTIONS",
|
|
272
|
-
"Access-Control-Allow-Headers": "
|
|
356
|
+
"Access-Control-Allow-Headers": allowedHeaders.join(", "),
|
|
273
357
|
},
|
|
274
358
|
});
|
|
275
359
|
}
|
|
276
360
|
|
|
361
|
+
function hasWorkOSWebhookEndpoint(workspaceRoot: string): boolean {
|
|
362
|
+
return existsSync(join(workspaceRoot, `${GENERATED_DIR}/integrations/workos/http-handler.ts`));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
function hasWorkOSAuthRoutes(workspaceRoot: string): boolean {
|
|
366
|
+
return existsSync(join(workspaceRoot, `${GENERATED_DIR}/integrations/workos/auth-routes.ts`));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function hasPublicAuthMd(workspaceRoot: string): boolean {
|
|
370
|
+
return existsSync(join(workspaceRoot, "public/auth.md"));
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function hasPublicAuthMetadata(workspaceRoot: string): boolean {
|
|
374
|
+
return existsSync(join(workspaceRoot, "public/.well-known/oauth-protected-resource"));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
function withGeneratedIntegrationRoutes(workspaceRoot: string, routes: DevRoute[]): DevRoute[] {
|
|
378
|
+
const next = [...routes];
|
|
379
|
+
if (
|
|
380
|
+
hasWorkOSAuthRoutes(workspaceRoot)
|
|
381
|
+
) {
|
|
382
|
+
for (const route of [
|
|
383
|
+
{ method: "GET" as const, path: "/login" },
|
|
384
|
+
{ method: "GET" as const, path: "/callback" },
|
|
385
|
+
{ method: "POST" as const, path: "/logout" },
|
|
386
|
+
{ method: "GET" as const, path: "/session" },
|
|
387
|
+
]) {
|
|
388
|
+
if (!next.some((candidate) => candidate.method === route.method && candidate.path === route.path)) {
|
|
389
|
+
next.push({ ...route, purpose: "auth" });
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
if (
|
|
394
|
+
hasPublicAuthMd(workspaceRoot) &&
|
|
395
|
+
!next.some((route) => route.method === "GET" && route.path === "/auth.md")
|
|
396
|
+
) {
|
|
397
|
+
next.push({
|
|
398
|
+
method: "GET",
|
|
399
|
+
path: "/auth.md",
|
|
400
|
+
purpose: "auth-md",
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
if (
|
|
404
|
+
hasPublicAuthMetadata(workspaceRoot) &&
|
|
405
|
+
!next.some((route) => route.method === "GET" && route.path === "/.well-known/oauth-protected-resource")
|
|
406
|
+
) {
|
|
407
|
+
next.push({
|
|
408
|
+
method: "GET",
|
|
409
|
+
path: "/.well-known/oauth-protected-resource",
|
|
410
|
+
purpose: "auth-metadata",
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
if (
|
|
414
|
+
hasWorkOSWebhookEndpoint(workspaceRoot) &&
|
|
415
|
+
!next.some((route) => route.method === "POST" && route.path === "/webhooks/workos")
|
|
416
|
+
) {
|
|
417
|
+
next.push({
|
|
418
|
+
method: "POST",
|
|
419
|
+
path: "/webhooks/workos",
|
|
420
|
+
purpose: "webhook",
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
return next.sort((a, b) =>
|
|
424
|
+
a.path === b.path ? a.method.localeCompare(b.method) : a.path.localeCompare(b.path),
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
interface WorkOSDevSession {
|
|
429
|
+
user: Record<string, unknown>;
|
|
430
|
+
accessToken: string;
|
|
431
|
+
refreshToken?: string;
|
|
432
|
+
organizationId?: string;
|
|
433
|
+
organizationMembershipId?: string;
|
|
434
|
+
role?: string;
|
|
435
|
+
roles?: string[];
|
|
436
|
+
permissions?: string[];
|
|
437
|
+
issuedAt: number;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
function normalizeLocalRedirect(value: string | null | undefined, fallback = "/"): string {
|
|
441
|
+
if (!value || !value.startsWith("/") || value.startsWith("//")) {
|
|
442
|
+
return fallback;
|
|
443
|
+
}
|
|
444
|
+
return value;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
function workOSDevCookieName(envStore: ReturnType<typeof getRuntimeEnvStore>): string {
|
|
448
|
+
return envStore.resolve("WORKOS_COOKIE_NAME") || "forgeos_workos_session";
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function base64urlJson(value: unknown): string {
|
|
452
|
+
return Buffer.from(JSON.stringify(value), "utf8").toString("base64url");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function decodeBase64urlJson(value: string): unknown {
|
|
456
|
+
return JSON.parse(Buffer.from(value, "base64url").toString("utf8")) as unknown;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function hmacBase64url(secret: string, payload: string): string {
|
|
460
|
+
return createHmac("sha256", secret).update(payload).digest("base64url");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function safeEqualString(left: string, right: string): boolean {
|
|
464
|
+
const a = Buffer.from(left);
|
|
465
|
+
const b = Buffer.from(right);
|
|
466
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function encodeWorkOSDevSession(session: WorkOSDevSession, cookiePassword: string): string {
|
|
470
|
+
const payload = base64urlJson(session);
|
|
471
|
+
return `${payload}.${hmacBase64url(cookiePassword, payload)}`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
function decodeWorkOSDevSession(value: string | undefined, cookiePassword: string): WorkOSDevSession | null {
|
|
475
|
+
if (!value) {
|
|
476
|
+
return null;
|
|
477
|
+
}
|
|
478
|
+
const [payload, signature] = value.split(".");
|
|
479
|
+
if (!payload || !signature || !safeEqualString(signature, hmacBase64url(cookiePassword, payload))) {
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
return decodeBase64urlJson(payload) as WorkOSDevSession;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function readRequestCookie(request: Request, name: string): string | undefined {
|
|
486
|
+
return (request.headers.get("cookie") ?? "")
|
|
487
|
+
.split(";")
|
|
488
|
+
.map((part) => part.trim())
|
|
489
|
+
.find((part) => part.startsWith(`${name}=`))
|
|
490
|
+
?.slice(name.length + 1);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function workOSDevSessionClaims(session: WorkOSDevSession): Record<string, unknown> {
|
|
494
|
+
const email = typeof session.user.email === "string" ? session.user.email : undefined;
|
|
495
|
+
const firstName = typeof session.user.firstName === "string" ? session.user.firstName : undefined;
|
|
496
|
+
const lastName = typeof session.user.lastName === "string" ? session.user.lastName : undefined;
|
|
497
|
+
return {
|
|
498
|
+
sub: String(session.user.id ?? ""),
|
|
499
|
+
...(email ? { email } : {}),
|
|
500
|
+
...(firstName || lastName ? { name: [firstName, lastName].filter(Boolean).join(" ") } : {}),
|
|
501
|
+
...(session.organizationId ? { organization_id: session.organizationId } : {}),
|
|
502
|
+
...(session.organizationMembershipId ? { organization_membership_id: session.organizationMembershipId } : {}),
|
|
503
|
+
...(session.role ? { role: session.role } : {}),
|
|
504
|
+
...(session.roles ? { roles: session.roles } : {}),
|
|
505
|
+
...(session.permissions ? { permissions: session.permissions } : {}),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function createWorkOSDevSessionCookie(
|
|
510
|
+
envStore: ReturnType<typeof getRuntimeEnvStore>,
|
|
511
|
+
session: WorkOSDevSession,
|
|
512
|
+
): string {
|
|
513
|
+
const cookiePassword = envStore.resolve("WORKOS_COOKIE_PASSWORD") ?? "";
|
|
514
|
+
const cookieName = workOSDevCookieName(envStore);
|
|
515
|
+
const value = encodeWorkOSDevSession(session, cookiePassword);
|
|
516
|
+
return `${cookieName}=${value}; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=2592000`;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function clearWorkOSDevSessionCookie(envStore: ReturnType<typeof getRuntimeEnvStore>): string {
|
|
520
|
+
return `${workOSDevCookieName(envStore)}=; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=0`;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
function workOSConfigDiagnostics(envStore: ReturnType<typeof getRuntimeEnvStore>, requireApiKey: boolean): Diagnostic[] {
|
|
524
|
+
const required = [
|
|
525
|
+
"WORKOS_CLIENT_ID",
|
|
526
|
+
"WORKOS_REDIRECT_URI",
|
|
527
|
+
"WORKOS_COOKIE_PASSWORD",
|
|
528
|
+
...(requireApiKey ? ["WORKOS_API_KEY"] : []),
|
|
529
|
+
];
|
|
530
|
+
return required
|
|
531
|
+
.filter((name) => !envStore.resolve(name))
|
|
532
|
+
.map((name) =>
|
|
533
|
+
createDiagnostic({
|
|
534
|
+
severity: "error",
|
|
535
|
+
code: FORGE_DEV_SERVER_ERROR,
|
|
536
|
+
message: `${name} is required for WorkOS AuthKit dev routes`,
|
|
537
|
+
fixHint: "Run forge add auth workos, copy .env.example to .env.local, and fill WorkOS values.",
|
|
538
|
+
}),
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
function workOSAuthorizationUrl(envStore: ReturnType<typeof getRuntimeEnvStore>, state: string): string {
|
|
543
|
+
const url = new URL("https://api.workos.com/user_management/authorize");
|
|
544
|
+
url.searchParams.set("provider", "authkit");
|
|
545
|
+
url.searchParams.set("client_id", envStore.resolve("WORKOS_CLIENT_ID") ?? "");
|
|
546
|
+
url.searchParams.set("redirect_uri", envStore.resolve("WORKOS_REDIRECT_URI") ?? "");
|
|
547
|
+
if (state) {
|
|
548
|
+
url.searchParams.set("state", state);
|
|
549
|
+
}
|
|
550
|
+
return url.toString();
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
async function authenticateWorkOSCode(workspaceRoot: string, envStore: ReturnType<typeof getRuntimeEnvStore>, code: string) {
|
|
554
|
+
const req = createRequire(join(workspaceRoot, "package.json"));
|
|
555
|
+
const localEntry = join(workspaceRoot, "node_modules/@workos-inc/node/index.js");
|
|
556
|
+
const resolved = existsSync(localEntry) ? localEntry : req.resolve("@workos-inc/node");
|
|
557
|
+
const workosModule = await import(pathToFileURL(resolved).href) as {
|
|
558
|
+
createWorkOS?: (options: { apiKey: string; clientId: string }) => unknown;
|
|
559
|
+
WorkOS?: new (options: { apiKey: string; clientId: string }) => unknown;
|
|
560
|
+
};
|
|
561
|
+
const client = workosModule.createWorkOS
|
|
562
|
+
? workosModule.createWorkOS({
|
|
563
|
+
apiKey: envStore.resolve("WORKOS_API_KEY") ?? "",
|
|
564
|
+
clientId: envStore.resolve("WORKOS_CLIENT_ID") ?? "",
|
|
565
|
+
})
|
|
566
|
+
: workosModule.WorkOS
|
|
567
|
+
? new workosModule.WorkOS({
|
|
568
|
+
apiKey: envStore.resolve("WORKOS_API_KEY") ?? "",
|
|
569
|
+
clientId: envStore.resolve("WORKOS_CLIENT_ID") ?? "",
|
|
570
|
+
})
|
|
571
|
+
: null;
|
|
572
|
+
const userManagement = (client as { userManagement?: { authenticateWithCode?: (input: { code: string; clientId: string }) => Promise<Record<string, unknown>> } } | null)?.userManagement;
|
|
573
|
+
if (!userManagement?.authenticateWithCode) {
|
|
574
|
+
throw new Error("@workos-inc/node does not expose userManagement.authenticateWithCode");
|
|
575
|
+
}
|
|
576
|
+
return userManagement.authenticateWithCode({
|
|
577
|
+
code,
|
|
578
|
+
clientId: envStore.resolve("WORKOS_CLIENT_ID") ?? "",
|
|
579
|
+
});
|
|
580
|
+
}
|
|
581
|
+
|
|
277
582
|
function acceptsHtml(request: Request): boolean {
|
|
278
583
|
return (request.headers.get("accept") ?? "").includes("text/html");
|
|
279
584
|
}
|
|
@@ -406,6 +711,22 @@ function parseInvokeName(pathname: string, prefix: string): string | null {
|
|
|
406
711
|
return name.length > 0 ? decodeURIComponent(name) : null;
|
|
407
712
|
}
|
|
408
713
|
|
|
714
|
+
function parseExternalInvoke(pathname: string): {
|
|
715
|
+
serviceName: string;
|
|
716
|
+
kind: "command" | "query";
|
|
717
|
+
entryName: string;
|
|
718
|
+
} | null {
|
|
719
|
+
const match = pathname.match(/^\/external\/([^/]+)\/(commands|queries)\/([^/]+)$/);
|
|
720
|
+
if (!match) {
|
|
721
|
+
return null;
|
|
722
|
+
}
|
|
723
|
+
return {
|
|
724
|
+
serviceName: decodeURIComponent(match[1]!),
|
|
725
|
+
kind: match[2] === "commands" ? "command" : "query",
|
|
726
|
+
entryName: decodeURIComponent(match[3]!),
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
|
|
409
730
|
function loadAgentToolRegistry(workspaceRoot: string): AgentToolRegistry | null {
|
|
410
731
|
return readGeneratedJson<AgentToolRegistry>(
|
|
411
732
|
workspaceRoot,
|
|
@@ -413,6 +734,18 @@ function loadAgentToolRegistry(workspaceRoot: string): AgentToolRegistry | null
|
|
|
413
734
|
);
|
|
414
735
|
}
|
|
415
736
|
|
|
737
|
+
async function listDatabaseTables(adapter: NonNullable<DevServerState["adapter"]>): Promise<string[]> {
|
|
738
|
+
const result = await adapter.query(
|
|
739
|
+
`SELECT c.relname AS table_name
|
|
740
|
+
FROM pg_catalog.pg_class c
|
|
741
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
742
|
+
WHERE n.nspname = 'public'
|
|
743
|
+
AND c.relkind IN ('r', 'p')
|
|
744
|
+
ORDER BY c.relname`,
|
|
745
|
+
);
|
|
746
|
+
return result.rows.map((row) => String(row.table_name));
|
|
747
|
+
}
|
|
748
|
+
|
|
416
749
|
type RuntimeAgentDefinition = {
|
|
417
750
|
provider?: ForgeAiProvider;
|
|
418
751
|
model: string;
|
|
@@ -562,6 +895,7 @@ async function buildAiSdkTools(input: {
|
|
|
562
895
|
telemetry: ReturnType<typeof createTelemetryContext> | ReturnType<typeof createNoopTelemetryContext>;
|
|
563
896
|
env: Record<string, string | undefined>;
|
|
564
897
|
auth: Awaited<ReturnType<typeof authenticateHeaders>>;
|
|
898
|
+
deltaRecorder?: DevServerOptions["deltaRecorder"];
|
|
565
899
|
}): Promise<Record<string, unknown>> {
|
|
566
900
|
const { tool } = await import("ai");
|
|
567
901
|
return Object.fromEntries(
|
|
@@ -612,6 +946,13 @@ async function buildAiSdkTools(input: {
|
|
|
612
946
|
latencyMs: Date.now() - startedAt,
|
|
613
947
|
status: "completed",
|
|
614
948
|
});
|
|
949
|
+
await input.deltaRecorder?.recordAgentTool({
|
|
950
|
+
toolName: name,
|
|
951
|
+
risk: definition.risk ?? "external",
|
|
952
|
+
status: "completed",
|
|
953
|
+
traceId: input.traceId,
|
|
954
|
+
durationMs: Date.now() - startedAt,
|
|
955
|
+
});
|
|
615
956
|
return output;
|
|
616
957
|
} catch (error) {
|
|
617
958
|
await input.telemetry.capture("forge.ai.tool.failed", {
|
|
@@ -624,6 +965,13 @@ async function buildAiSdkTools(input: {
|
|
|
624
965
|
status: "failed",
|
|
625
966
|
error: error instanceof Error ? error.message : String(error),
|
|
626
967
|
});
|
|
968
|
+
await input.deltaRecorder?.recordAgentTool({
|
|
969
|
+
toolName: name,
|
|
970
|
+
risk: definition.risk ?? "external",
|
|
971
|
+
status: "failed",
|
|
972
|
+
traceId: input.traceId,
|
|
973
|
+
durationMs: Date.now() - startedAt,
|
|
974
|
+
});
|
|
627
975
|
throw error;
|
|
628
976
|
}
|
|
629
977
|
},
|
|
@@ -651,6 +999,7 @@ export async function startDevServer(
|
|
|
651
999
|
options: DevServerOptions,
|
|
652
1000
|
): Promise<DevServerHandle> {
|
|
653
1001
|
const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
|
|
1002
|
+
const deltaRecorder = options.deltaRecorder;
|
|
654
1003
|
const authConfig = loadAuthConfigFromEnv(workspaceRoot, {
|
|
655
1004
|
defaultMode: "dev-headers",
|
|
656
1005
|
});
|
|
@@ -711,7 +1060,7 @@ export async function startDevServer(
|
|
|
711
1060
|
);
|
|
712
1061
|
|
|
713
1062
|
if (sqlPlan) {
|
|
714
|
-
const migrationDiagnostics = await
|
|
1063
|
+
const migrationDiagnostics = await applyDevMigrations(adapter, sqlPlan, dbMode);
|
|
715
1064
|
const errors = migrationDiagnostics.filter(
|
|
716
1065
|
(diagnostic) => diagnostic.severity === "error",
|
|
717
1066
|
);
|
|
@@ -731,6 +1080,7 @@ export async function startDevServer(
|
|
|
731
1080
|
mockAi: options.mockAi,
|
|
732
1081
|
db: serverState.adapter,
|
|
733
1082
|
});
|
|
1083
|
+
refreshRuntimeModuleNamespace("dev-start");
|
|
734
1084
|
|
|
735
1085
|
function loadArtifacts(): {
|
|
736
1086
|
devManifest: DevManifest;
|
|
@@ -764,22 +1114,96 @@ export async function startDevServer(
|
|
|
764
1114
|
}
|
|
765
1115
|
|
|
766
1116
|
const initialArtifacts = loadArtifacts();
|
|
1117
|
+
let currentRoutes = withGeneratedIntegrationRoutes(workspaceRoot, devManifest.routes);
|
|
1118
|
+
let currentRuntimeEntryCount = runtimeGraph.entries.length;
|
|
1119
|
+
|
|
1120
|
+
async function reloadGeneratedArtifacts(reason = "manual"): Promise<DevServerReloadResult> {
|
|
1121
|
+
try {
|
|
1122
|
+
refreshRuntimeModuleNamespace(reason);
|
|
1123
|
+
const fresh = loadArtifacts();
|
|
1124
|
+
currentRoutes = withGeneratedIntegrationRoutes(workspaceRoot, fresh.devManifest.routes);
|
|
1125
|
+
currentRuntimeEntryCount = fresh.runtimeGraph.entries.length;
|
|
1126
|
+
const diagnostics: Diagnostic[] = [];
|
|
1127
|
+
let migrated = false;
|
|
1128
|
+
|
|
1129
|
+
if (serverState.adapter) {
|
|
1130
|
+
const sqlPlan = readGeneratedJson<SqlPlan>(
|
|
1131
|
+
workspaceRoot,
|
|
1132
|
+
`${GENERATED_DIR}/sqlPlan.json`,
|
|
1133
|
+
);
|
|
1134
|
+
if (sqlPlan) {
|
|
1135
|
+
migrated = true;
|
|
1136
|
+
diagnostics.push(...(await applyDevMigrations(serverState.adapter, sqlPlan, options.db)));
|
|
1137
|
+
}
|
|
1138
|
+
if (diagnostics.every((diagnostic) => diagnostic.severity !== "error")) {
|
|
1139
|
+
await ensureLiveInvalidationSchema(serverState.adapter);
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
if (
|
|
1144
|
+
options.worker &&
|
|
1145
|
+
serverState.adapter &&
|
|
1146
|
+
diagnostics.every((diagnostic) => diagnostic.severity !== "error")
|
|
1147
|
+
) {
|
|
1148
|
+
serverState.outboxWorker?.stop();
|
|
1149
|
+
serverState.outboxWorker = startOutboxWorker(
|
|
1150
|
+
serverState.adapter,
|
|
1151
|
+
workspaceRoot,
|
|
1152
|
+
fresh.tableMap,
|
|
1153
|
+
fresh.runtimeGraph.entries,
|
|
1154
|
+
{ mock: options.mock, intervalMs: 2_000, telemetrySinks, workspaceRoot },
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
return {
|
|
1159
|
+
ok: diagnostics.every((diagnostic) => diagnostic.severity !== "error"),
|
|
1160
|
+
reason,
|
|
1161
|
+
migrated,
|
|
1162
|
+
routes: currentRoutes.length,
|
|
1163
|
+
runtimeEntries: currentRuntimeEntryCount,
|
|
1164
|
+
worker: serverState.outboxWorker?.isRunning() ? "running" : "stopped",
|
|
1165
|
+
diagnostics,
|
|
1166
|
+
};
|
|
1167
|
+
} catch (error) {
|
|
1168
|
+
return {
|
|
1169
|
+
ok: false,
|
|
1170
|
+
reason,
|
|
1171
|
+
migrated: false,
|
|
1172
|
+
routes: currentRoutes.length,
|
|
1173
|
+
runtimeEntries: currentRuntimeEntryCount,
|
|
1174
|
+
worker: serverState.outboxWorker?.isRunning() ? "running" : "stopped",
|
|
1175
|
+
diagnostics: [
|
|
1176
|
+
createDiagnostic({
|
|
1177
|
+
severity: "error",
|
|
1178
|
+
code: FORGE_DEV_SERVER_ERROR,
|
|
1179
|
+
message: error instanceof Error ? error.message : "failed to reload generated artifacts",
|
|
1180
|
+
}),
|
|
1181
|
+
],
|
|
1182
|
+
};
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
|
|
767
1186
|
const liveManager = serverState.adapter
|
|
768
1187
|
? createLiveSubscriptionManager({
|
|
769
1188
|
workspaceRoot,
|
|
770
1189
|
adapter: serverState.adapter,
|
|
771
1190
|
loadTableMap: () => loadArtifacts().tableMap,
|
|
772
1191
|
enablePolling: true,
|
|
1192
|
+
limits: {
|
|
1193
|
+
...DEFAULT_LIVE_LIMITS,
|
|
1194
|
+
maxSubscriptionsPerClient: Number(process.env.FORGE_DEV_LIVE_MAX_SUBSCRIPTIONS ?? "250"),
|
|
1195
|
+
},
|
|
773
1196
|
pollIntervalMs: Number(process.env.FORGE_LIVE_POLL_INTERVAL_MS ?? "1000"),
|
|
774
1197
|
})
|
|
775
1198
|
: null;
|
|
1199
|
+
const workosReplayStore = new MemoryWebhookReplayStore();
|
|
776
1200
|
|
|
777
1201
|
if (options.worker && serverState.adapter) {
|
|
778
1202
|
serverState.outboxWorker = startOutboxWorker(
|
|
779
1203
|
serverState.adapter,
|
|
780
1204
|
workspaceRoot,
|
|
781
1205
|
initialArtifacts.tableMap,
|
|
782
|
-
runtimeGraph.entries,
|
|
1206
|
+
initialArtifacts.runtimeGraph.entries,
|
|
783
1207
|
{ mock: options.mock, intervalMs: 2_000, telemetrySinks, workspaceRoot },
|
|
784
1208
|
);
|
|
785
1209
|
}
|
|
@@ -803,6 +1227,7 @@ export async function startDevServer(
|
|
|
803
1227
|
const listed = listEntries(workspaceRoot);
|
|
804
1228
|
const queries = listQueries(workspaceRoot);
|
|
805
1229
|
const liveQueries = loadLiveQueryRegistry(workspaceRoot);
|
|
1230
|
+
const external = loadExternalServiceGraph(workspaceRoot);
|
|
806
1231
|
const entries = [
|
|
807
1232
|
...queries.queries.map((query) => ({
|
|
808
1233
|
name: query.name,
|
|
@@ -825,10 +1250,18 @@ export async function startDevServer(
|
|
|
825
1250
|
: `/actions/${entry.name}`,
|
|
826
1251
|
method: "POST",
|
|
827
1252
|
})),
|
|
1253
|
+
...(external.graph?.services.flatMap((service) =>
|
|
1254
|
+
service.entries.map((entry) => ({
|
|
1255
|
+
name: `${service.name}.${entry.name}`,
|
|
1256
|
+
kind: `external:${entry.kind}`,
|
|
1257
|
+
path: `/external/${encodeURIComponent(service.name)}/${entry.kind === "command" ? "commands" : "queries"}/${encodeURIComponent(entry.name)}`,
|
|
1258
|
+
method: entry.method ?? "POST",
|
|
1259
|
+
}))
|
|
1260
|
+
) ?? []),
|
|
828
1261
|
].sort((a, b) =>
|
|
829
1262
|
a.path === b.path ? a.kind.localeCompare(b.kind) : a.path.localeCompare(b.path),
|
|
830
1263
|
);
|
|
831
|
-
const routes =
|
|
1264
|
+
const routes = currentRoutes.map((route) => ({
|
|
832
1265
|
method: route.method,
|
|
833
1266
|
path: route.path,
|
|
834
1267
|
purpose: route.purpose,
|
|
@@ -846,6 +1279,7 @@ export async function startDevServer(
|
|
|
846
1279
|
...listed.diagnostics,
|
|
847
1280
|
...queries.diagnostics,
|
|
848
1281
|
...(liveQueries.registry?.diagnostics ?? []),
|
|
1282
|
+
...external.diagnostics,
|
|
849
1283
|
],
|
|
850
1284
|
};
|
|
851
1285
|
|
|
@@ -884,6 +1318,16 @@ export async function startDevServer(
|
|
|
884
1318
|
const aiRegistry = loadAiRegistry(workspaceRoot);
|
|
885
1319
|
const aiCheck = checkAiProviders(envStore, aiRegistry, secretRegistry);
|
|
886
1320
|
const mockAi = isMockAiEnabled({ mockAi: options.mockAi });
|
|
1321
|
+
const productionAuthMode = authConfig.mode === "oidc" || authConfig.mode === "jwt";
|
|
1322
|
+
const missingAuthConfig = [
|
|
1323
|
+
...(productionAuthMode && !authConfig.issuer ? ["FORGE_AUTH_ISSUER"] : []),
|
|
1324
|
+
...(productionAuthMode && !authConfig.jwksUri ? ["FORGE_AUTH_JWKS_URI"] : []),
|
|
1325
|
+
];
|
|
1326
|
+
const workosClientId = envStore.resolve("WORKOS_CLIENT_ID") || envStore.resolve("VITE_WORKOS_CLIENT_ID");
|
|
1327
|
+
const suggestedWorkOSJwksUri = workosClientId
|
|
1328
|
+
? `https://api.workos.com/sso/jwks/${workosClientId}`
|
|
1329
|
+
: "https://api.workos.com/sso/jwks/<WORKOS_CLIENT_ID>";
|
|
1330
|
+
const authProductionReady = !productionAuthMode || missingAuthConfig.length === 0;
|
|
887
1331
|
|
|
888
1332
|
return jsonResponse({
|
|
889
1333
|
ok: true,
|
|
@@ -912,6 +1356,12 @@ export async function startDevServer(
|
|
|
912
1356
|
audienceConfigured: Boolean(authConfig.audience),
|
|
913
1357
|
jwksConfigured: Boolean(authConfig.jwksUri),
|
|
914
1358
|
requiresTenant: authConfig.requiresTenant,
|
|
1359
|
+
ready: authProductionReady,
|
|
1360
|
+
productionReady: authProductionReady,
|
|
1361
|
+
missing: missingAuthConfig,
|
|
1362
|
+
...(missingAuthConfig.includes("FORGE_AUTH_JWKS_URI")
|
|
1363
|
+
? { suggestedWorkOSJwksUri }
|
|
1364
|
+
: {}),
|
|
915
1365
|
},
|
|
916
1366
|
env: {
|
|
917
1367
|
loadedFiles: envStore.loadedFiles,
|
|
@@ -931,6 +1381,268 @@ export async function startDevServer(
|
|
|
931
1381
|
});
|
|
932
1382
|
}
|
|
933
1383
|
|
|
1384
|
+
if (["/login", "/callback", "/logout", "/session"].includes(pathname)) {
|
|
1385
|
+
if (!hasWorkOSAuthRoutes(workspaceRoot)) {
|
|
1386
|
+
return jsonResponse(
|
|
1387
|
+
{
|
|
1388
|
+
ok: false,
|
|
1389
|
+
diagnostics: [
|
|
1390
|
+
createDiagnostic({
|
|
1391
|
+
severity: "error",
|
|
1392
|
+
code: FORGE_RUNTIME_NOT_FOUND,
|
|
1393
|
+
message: "WorkOS AuthKit routes are not installed",
|
|
1394
|
+
fixHint: "Run forge add auth workos, then forge generate.",
|
|
1395
|
+
}),
|
|
1396
|
+
],
|
|
1397
|
+
},
|
|
1398
|
+
404,
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
const envStore = getRuntimeEnvStore(workspaceRoot);
|
|
1403
|
+
|
|
1404
|
+
if (pathname === "/login") {
|
|
1405
|
+
if (request.method !== "GET") {
|
|
1406
|
+
return jsonResponse({ ok: false, error: "method_not_allowed" }, 405, { Allow: "GET, OPTIONS" });
|
|
1407
|
+
}
|
|
1408
|
+
const diagnostics = workOSConfigDiagnostics(envStore, false);
|
|
1409
|
+
if (diagnostics.length > 0) {
|
|
1410
|
+
return jsonResponse({ ok: false, diagnostics }, 503);
|
|
1411
|
+
}
|
|
1412
|
+
const returnTo = normalizeLocalRedirect(new URL(request.url).searchParams.get("returnTo"));
|
|
1413
|
+
return redirectResponse(workOSAuthorizationUrl(envStore, returnTo));
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
if (pathname === "/callback") {
|
|
1417
|
+
if (request.method !== "GET") {
|
|
1418
|
+
return jsonResponse({ ok: false, error: "method_not_allowed" }, 405, { Allow: "GET, OPTIONS" });
|
|
1419
|
+
}
|
|
1420
|
+
const diagnostics = workOSConfigDiagnostics(envStore, true);
|
|
1421
|
+
if (diagnostics.length > 0) {
|
|
1422
|
+
return jsonResponse({ ok: false, diagnostics }, 503);
|
|
1423
|
+
}
|
|
1424
|
+
const url = new URL(request.url);
|
|
1425
|
+
const code = url.searchParams.get("code");
|
|
1426
|
+
if (!code) {
|
|
1427
|
+
return jsonResponse({ ok: false, error: "missing_code" }, 400);
|
|
1428
|
+
}
|
|
1429
|
+
try {
|
|
1430
|
+
const result = await authenticateWorkOSCode(workspaceRoot, envStore, code);
|
|
1431
|
+
const user = (result.user ?? {}) as Record<string, unknown>;
|
|
1432
|
+
const organization = (result.organization ?? {}) as Record<string, unknown>;
|
|
1433
|
+
const membership = (result.organizationMembership ?? {}) as Record<string, unknown>;
|
|
1434
|
+
const accessToken = typeof result.accessToken === "string" ? result.accessToken : "";
|
|
1435
|
+
const refreshToken = typeof result.refreshToken === "string" ? result.refreshToken : undefined;
|
|
1436
|
+
const session: WorkOSDevSession = {
|
|
1437
|
+
user,
|
|
1438
|
+
accessToken,
|
|
1439
|
+
...(refreshToken ? { refreshToken } : {}),
|
|
1440
|
+
...(typeof result.organizationId === "string"
|
|
1441
|
+
? { organizationId: result.organizationId }
|
|
1442
|
+
: typeof organization.id === "string"
|
|
1443
|
+
? { organizationId: organization.id }
|
|
1444
|
+
: {}),
|
|
1445
|
+
...(typeof result.organizationMembershipId === "string"
|
|
1446
|
+
? { organizationMembershipId: result.organizationMembershipId }
|
|
1447
|
+
: typeof membership.id === "string"
|
|
1448
|
+
? { organizationMembershipId: membership.id }
|
|
1449
|
+
: {}),
|
|
1450
|
+
...(typeof result.role === "string" ? { role: result.role } : {}),
|
|
1451
|
+
...(Array.isArray(result.roles)
|
|
1452
|
+
? { roles: result.roles.filter((role): role is string => typeof role === "string") }
|
|
1453
|
+
: {}),
|
|
1454
|
+
...(Array.isArray(result.permissions)
|
|
1455
|
+
? {
|
|
1456
|
+
permissions: result.permissions.filter(
|
|
1457
|
+
(permission): permission is string => typeof permission === "string",
|
|
1458
|
+
),
|
|
1459
|
+
}
|
|
1460
|
+
: {}),
|
|
1461
|
+
issuedAt: Math.floor(Date.now() / 1000),
|
|
1462
|
+
};
|
|
1463
|
+
return redirectResponse(
|
|
1464
|
+
normalizeLocalRedirect(
|
|
1465
|
+
url.searchParams.get("state"),
|
|
1466
|
+
normalizeLocalRedirect(envStore.resolve("WORKOS_POST_LOGIN_REDIRECT_URI"), "/"),
|
|
1467
|
+
),
|
|
1468
|
+
{ "Set-Cookie": createWorkOSDevSessionCookie(envStore, session) },
|
|
1469
|
+
);
|
|
1470
|
+
} catch (error) {
|
|
1471
|
+
return jsonResponse(
|
|
1472
|
+
{
|
|
1473
|
+
ok: false,
|
|
1474
|
+
diagnostics: [
|
|
1475
|
+
createDiagnostic({
|
|
1476
|
+
severity: "error",
|
|
1477
|
+
code: FORGE_DEV_SERVER_ERROR,
|
|
1478
|
+
message: error instanceof Error ? error.message : "WorkOS callback failed",
|
|
1479
|
+
fixHint: "Ensure @workos-inc/node is installed and WorkOS env values are valid.",
|
|
1480
|
+
}),
|
|
1481
|
+
],
|
|
1482
|
+
},
|
|
1483
|
+
500,
|
|
1484
|
+
);
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
if (pathname === "/logout") {
|
|
1489
|
+
if (request.method !== "POST") {
|
|
1490
|
+
return jsonResponse({ ok: false, error: "method_not_allowed" }, 405, { Allow: "POST, OPTIONS" });
|
|
1491
|
+
}
|
|
1492
|
+
return redirectResponse(
|
|
1493
|
+
normalizeLocalRedirect(envStore.resolve("WORKOS_POST_LOGOUT_REDIRECT_URI"), "/"),
|
|
1494
|
+
{ "Set-Cookie": clearWorkOSDevSessionCookie(envStore) },
|
|
1495
|
+
);
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
if (pathname === "/session") {
|
|
1499
|
+
if (request.method !== "GET") {
|
|
1500
|
+
return jsonResponse({ ok: false, error: "method_not_allowed" }, 405, { Allow: "GET, OPTIONS" });
|
|
1501
|
+
}
|
|
1502
|
+
const cookiePassword = envStore.resolve("WORKOS_COOKIE_PASSWORD");
|
|
1503
|
+
if (!cookiePassword) {
|
|
1504
|
+
return jsonResponse({ ok: false, diagnostics: workOSConfigDiagnostics(envStore, false) }, 503);
|
|
1505
|
+
}
|
|
1506
|
+
const session = decodeWorkOSDevSession(
|
|
1507
|
+
readRequestCookie(request, workOSDevCookieName(envStore)),
|
|
1508
|
+
cookiePassword,
|
|
1509
|
+
);
|
|
1510
|
+
return session
|
|
1511
|
+
? jsonResponse({ ok: true, session: { user: session.user, claims: workOSDevSessionClaims(session) } })
|
|
1512
|
+
: jsonResponse({ ok: false, session: null }, 401);
|
|
1513
|
+
}
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
if ((request.method === "GET" || request.method === "HEAD") && pathname === "/auth.md") {
|
|
1517
|
+
const authMdPath = join(workspaceRoot, "public/auth.md");
|
|
1518
|
+
if (!existsSync(authMdPath)) {
|
|
1519
|
+
return jsonResponse(
|
|
1520
|
+
{
|
|
1521
|
+
ok: false,
|
|
1522
|
+
diagnostics: [
|
|
1523
|
+
createDiagnostic({
|
|
1524
|
+
severity: "error",
|
|
1525
|
+
code: FORGE_RUNTIME_NOT_FOUND,
|
|
1526
|
+
message: "public/auth.md was not found",
|
|
1527
|
+
fixHint: "Run forge authmd generate.",
|
|
1528
|
+
}),
|
|
1529
|
+
],
|
|
1530
|
+
},
|
|
1531
|
+
404,
|
|
1532
|
+
);
|
|
1533
|
+
}
|
|
1534
|
+
return request.method === "HEAD" ? new Response(null, { status: 200, headers: { "content-type": "text/markdown; charset=utf-8" } }) : markdownResponse(readFileSync(authMdPath, "utf8"));
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
if ((request.method === "GET" || request.method === "HEAD") && pathname === "/.well-known/oauth-protected-resource") {
|
|
1538
|
+
const metadataPath = join(workspaceRoot, "public/.well-known/oauth-protected-resource");
|
|
1539
|
+
if (!existsSync(metadataPath)) {
|
|
1540
|
+
return jsonResponse(
|
|
1541
|
+
{
|
|
1542
|
+
ok: false,
|
|
1543
|
+
diagnostics: [
|
|
1544
|
+
createDiagnostic({
|
|
1545
|
+
severity: "error",
|
|
1546
|
+
code: FORGE_RUNTIME_NOT_FOUND,
|
|
1547
|
+
message: "public/.well-known/oauth-protected-resource was not found",
|
|
1548
|
+
fixHint: "Run forge authmd generate.",
|
|
1549
|
+
}),
|
|
1550
|
+
],
|
|
1551
|
+
},
|
|
1552
|
+
404,
|
|
1553
|
+
);
|
|
1554
|
+
}
|
|
1555
|
+
return request.method === "HEAD" ? new Response(null, { status: 200, headers: { "content-type": "application/json; charset=utf-8" } }) : jsonResponse(JSON.parse(readFileSync(metadataPath, "utf8")));
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
if (pathname === "/webhooks/workos") {
|
|
1559
|
+
if (!hasWorkOSWebhookEndpoint(workspaceRoot)) {
|
|
1560
|
+
return jsonResponse(
|
|
1561
|
+
{
|
|
1562
|
+
ok: false,
|
|
1563
|
+
diagnostics: [
|
|
1564
|
+
createDiagnostic({
|
|
1565
|
+
severity: "error",
|
|
1566
|
+
code: FORGE_RUNTIME_NOT_FOUND,
|
|
1567
|
+
message: "WorkOS webhook endpoint is not installed",
|
|
1568
|
+
fixHint: "Run forge add auth workos, then forge generate.",
|
|
1569
|
+
}),
|
|
1570
|
+
],
|
|
1571
|
+
},
|
|
1572
|
+
404,
|
|
1573
|
+
);
|
|
1574
|
+
}
|
|
1575
|
+
if (request.method !== "POST") {
|
|
1576
|
+
return jsonResponse(
|
|
1577
|
+
{
|
|
1578
|
+
ok: false,
|
|
1579
|
+
diagnostics: [
|
|
1580
|
+
createDiagnostic({
|
|
1581
|
+
severity: "error",
|
|
1582
|
+
code: FORGE_DEV_INVOKE_FAILED,
|
|
1583
|
+
message: "WorkOS webhook endpoint requires POST /webhooks/workos",
|
|
1584
|
+
}),
|
|
1585
|
+
],
|
|
1586
|
+
example: {
|
|
1587
|
+
method: "POST",
|
|
1588
|
+
path: "/webhooks/workos",
|
|
1589
|
+
headers: { "WorkOS-Signature": "t=<ms>,v1=<hex>" },
|
|
1590
|
+
},
|
|
1591
|
+
},
|
|
1592
|
+
405,
|
|
1593
|
+
{ Allow: "POST, OPTIONS" },
|
|
1594
|
+
);
|
|
1595
|
+
}
|
|
1596
|
+
|
|
1597
|
+
const payload = await request.text();
|
|
1598
|
+
let eventId: string | undefined;
|
|
1599
|
+
let eventType: string | undefined;
|
|
1600
|
+
try {
|
|
1601
|
+
const parsed = JSON.parse(payload) as { id?: unknown; event?: unknown; type?: unknown };
|
|
1602
|
+
eventId = typeof parsed.id === "string" ? parsed.id : undefined;
|
|
1603
|
+
eventType =
|
|
1604
|
+
typeof parsed.event === "string"
|
|
1605
|
+
? parsed.event
|
|
1606
|
+
: typeof parsed.type === "string"
|
|
1607
|
+
? parsed.type
|
|
1608
|
+
: undefined;
|
|
1609
|
+
} catch {
|
|
1610
|
+
// Signature verification still returns the authoritative failure for malformed bodies.
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
const envStore = getRuntimeEnvStore(workspaceRoot);
|
|
1614
|
+
const secret = envStore.resolve("WORKOS_WEBHOOK_SECRET") ?? "";
|
|
1615
|
+
const verification = await verifyWebhookSignature({
|
|
1616
|
+
provider: "workos",
|
|
1617
|
+
secret,
|
|
1618
|
+
payload,
|
|
1619
|
+
signatureHeader: request.headers.get("WorkOS-Signature"),
|
|
1620
|
+
eventId,
|
|
1621
|
+
replayStore: workosReplayStore,
|
|
1622
|
+
});
|
|
1623
|
+
if (!verification.ok) {
|
|
1624
|
+
return jsonResponse(
|
|
1625
|
+
{
|
|
1626
|
+
ok: false,
|
|
1627
|
+
diagnostics: [
|
|
1628
|
+
createDiagnostic({
|
|
1629
|
+
severity: "error",
|
|
1630
|
+
code: verification.code ?? FORGE_DEV_SERVER_ERROR,
|
|
1631
|
+
message: verification.reason ?? "WorkOS webhook signature verification failed",
|
|
1632
|
+
}),
|
|
1633
|
+
],
|
|
1634
|
+
},
|
|
1635
|
+
401,
|
|
1636
|
+
);
|
|
1637
|
+
}
|
|
1638
|
+
return jsonResponse({
|
|
1639
|
+
ok: true,
|
|
1640
|
+
provider: "workos",
|
|
1641
|
+
eventId: eventId ?? null,
|
|
1642
|
+
event: eventType ?? "unknown",
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
|
|
934
1646
|
if (request.method === "GET" && pathname === "/live") {
|
|
935
1647
|
const liveQueries = loadLiveQueryRegistry(workspaceRoot).liveQueries;
|
|
936
1648
|
return jsonResponse({
|
|
@@ -1028,6 +1740,13 @@ export async function startDevServer(
|
|
|
1028
1740
|
"NaN",
|
|
1029
1741
|
);
|
|
1030
1742
|
let subscriptionId: string | null = null;
|
|
1743
|
+
const cleanupSubscription = () => {
|
|
1744
|
+
if (subscriptionId) {
|
|
1745
|
+
liveManager.unsubscribe(subscriptionId);
|
|
1746
|
+
subscriptionId = null;
|
|
1747
|
+
}
|
|
1748
|
+
};
|
|
1749
|
+
request.signal.addEventListener("abort", cleanupSubscription, { once: true });
|
|
1031
1750
|
|
|
1032
1751
|
return createSseResponse(
|
|
1033
1752
|
async (send, close) => {
|
|
@@ -1039,6 +1758,11 @@ export async function startDevServer(
|
|
|
1039
1758
|
send,
|
|
1040
1759
|
});
|
|
1041
1760
|
subscriptionId = subscription.id;
|
|
1761
|
+
if (request.signal.aborted) {
|
|
1762
|
+
cleanupSubscription();
|
|
1763
|
+
close();
|
|
1764
|
+
return;
|
|
1765
|
+
}
|
|
1042
1766
|
const known = loadLiveQueryRegistry(workspaceRoot).liveQueries.some(
|
|
1043
1767
|
(liveQuery) => liveQuery.name === name,
|
|
1044
1768
|
);
|
|
@@ -1047,9 +1771,7 @@ export async function startDevServer(
|
|
|
1047
1771
|
}
|
|
1048
1772
|
},
|
|
1049
1773
|
() => {
|
|
1050
|
-
|
|
1051
|
-
liveManager.unsubscribe(subscriptionId);
|
|
1052
|
-
}
|
|
1774
|
+
cleanupSubscription();
|
|
1053
1775
|
},
|
|
1054
1776
|
{
|
|
1055
1777
|
heartbeatIntervalMs: Number(process.env.FORGE_LIVE_HEARTBEAT_MS ?? "15000"),
|
|
@@ -1314,6 +2036,7 @@ export async function startDevServer(
|
|
|
1314
2036
|
telemetry,
|
|
1315
2037
|
env: envStore.snapshot(),
|
|
1316
2038
|
auth,
|
|
2039
|
+
deltaRecorder,
|
|
1317
2040
|
});
|
|
1318
2041
|
const { ToolLoopAgent, createAgentUIStreamResponse, hasToolCall, stepCountIs } = await import("ai");
|
|
1319
2042
|
const stopWhen =
|
|
@@ -1485,12 +2208,11 @@ export async function startDevServer(
|
|
|
1485
2208
|
|
|
1486
2209
|
if (request.method === "GET" && pathname === "/db/tables") {
|
|
1487
2210
|
if (serverState.adapter) {
|
|
1488
|
-
const result = await serverState.adapter.query(
|
|
1489
|
-
`SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name`,
|
|
1490
|
-
);
|
|
1491
2211
|
return jsonResponse({
|
|
1492
2212
|
ok: true,
|
|
1493
|
-
tables:
|
|
2213
|
+
tables: serverState.adapter.kind === "memory"
|
|
2214
|
+
? Object.keys(tableMap).sort()
|
|
2215
|
+
: await listDatabaseTables(serverState.adapter),
|
|
1494
2216
|
});
|
|
1495
2217
|
}
|
|
1496
2218
|
|
|
@@ -1501,6 +2223,15 @@ export async function startDevServer(
|
|
|
1501
2223
|
}
|
|
1502
2224
|
|
|
1503
2225
|
if (request.method === "GET") {
|
|
2226
|
+
const externalInvoke = parseExternalInvoke(pathname);
|
|
2227
|
+
if (externalInvoke) {
|
|
2228
|
+
return methodHelpResponse({
|
|
2229
|
+
kind: externalInvoke.kind,
|
|
2230
|
+
name: `${externalInvoke.serviceName}.${externalInvoke.entryName}`,
|
|
2231
|
+
path: `/external/${externalInvoke.serviceName}/${externalInvoke.kind === "command" ? "commands" : "queries"}/${externalInvoke.entryName}`,
|
|
2232
|
+
});
|
|
2233
|
+
}
|
|
2234
|
+
|
|
1504
2235
|
const queryName = parseInvokeName(pathname, "/queries/");
|
|
1505
2236
|
if (queryName) {
|
|
1506
2237
|
return methodHelpResponse({
|
|
@@ -1658,6 +2389,43 @@ export async function startDevServer(
|
|
|
1658
2389
|
return jsonResponse({ ok: true, ...result });
|
|
1659
2390
|
}
|
|
1660
2391
|
|
|
2392
|
+
const externalInvoke = parseExternalInvoke(pathname);
|
|
2393
|
+
if (externalInvoke) {
|
|
2394
|
+
const args = await parseRequestArgs(request);
|
|
2395
|
+
const auth = await authenticateHeaders(request.headers, authConfig);
|
|
2396
|
+
const result = await runExternalEntry(
|
|
2397
|
+
workspaceRoot,
|
|
2398
|
+
{
|
|
2399
|
+
...externalInvoke,
|
|
2400
|
+
args,
|
|
2401
|
+
auth,
|
|
2402
|
+
requestHeaders: request.headers,
|
|
2403
|
+
},
|
|
2404
|
+
{ adapter: serverState.adapter },
|
|
2405
|
+
);
|
|
2406
|
+
const policyDenied = result.diagnostics.some(
|
|
2407
|
+
(diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
|
|
2408
|
+
);
|
|
2409
|
+
await deltaRecorder?.recordRuntimeCall({
|
|
2410
|
+
entryName: `${externalInvoke.serviceName}.${externalInvoke.entryName}`,
|
|
2411
|
+
entryKind: externalInvoke.kind,
|
|
2412
|
+
service: externalInvoke.serviceName,
|
|
2413
|
+
result: policyDenied ? "denied" : result.ok ? "success" : "failed",
|
|
2414
|
+
diagnostics: result.diagnostics,
|
|
2415
|
+
traceId: result.traceId,
|
|
2416
|
+
});
|
|
2417
|
+
return jsonResponse(
|
|
2418
|
+
{
|
|
2419
|
+
ok: result.ok,
|
|
2420
|
+
result: result.result,
|
|
2421
|
+
diagnostics: result.diagnostics,
|
|
2422
|
+
traceId: result.traceId,
|
|
2423
|
+
},
|
|
2424
|
+
result.ok ? 200 : policyDenied ? 403 : 400,
|
|
2425
|
+
result.traceId ? { "x-forge-trace-id": result.traceId } : {},
|
|
2426
|
+
);
|
|
2427
|
+
}
|
|
2428
|
+
|
|
1661
2429
|
let entryName: string | null = null;
|
|
1662
2430
|
let expectedKind: "command" | "action" | null = null;
|
|
1663
2431
|
let queryName: string | null = null;
|
|
@@ -1707,6 +2475,13 @@ export async function startDevServer(
|
|
|
1707
2475
|
const policyDenied = result.diagnostics.some(
|
|
1708
2476
|
(diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
|
|
1709
2477
|
);
|
|
2478
|
+
await deltaRecorder?.recordRuntimeCall({
|
|
2479
|
+
entryName: queryName,
|
|
2480
|
+
entryKind: "query",
|
|
2481
|
+
result: policyDenied ? "denied" : result.ok ? "success" : "failed",
|
|
2482
|
+
diagnostics: result.diagnostics,
|
|
2483
|
+
traceId: result.traceId,
|
|
2484
|
+
});
|
|
1710
2485
|
|
|
1711
2486
|
return jsonResponse(
|
|
1712
2487
|
{
|
|
@@ -1777,6 +2552,13 @@ export async function startDevServer(
|
|
|
1777
2552
|
const policyDenied = result.diagnostics.some(
|
|
1778
2553
|
(diagnostic) => diagnostic.code === FORGE_POLICY_DENIED,
|
|
1779
2554
|
);
|
|
2555
|
+
await deltaRecorder?.recordRuntimeCall({
|
|
2556
|
+
entryName,
|
|
2557
|
+
entryKind: entry.kind,
|
|
2558
|
+
result: policyDenied ? "denied" : result.ok ? "success" : "failed",
|
|
2559
|
+
diagnostics: result.diagnostics,
|
|
2560
|
+
traceId: result.traceId,
|
|
2561
|
+
});
|
|
1780
2562
|
|
|
1781
2563
|
if (policyDenied) {
|
|
1782
2564
|
const denied = result.diagnostics.find(
|
|
@@ -1862,23 +2644,32 @@ export async function startDevServer(
|
|
|
1862
2644
|
const protocol = "http";
|
|
1863
2645
|
const url = `${protocol}://${host}:${port}`;
|
|
1864
2646
|
|
|
1865
|
-
|
|
2647
|
+
const handle: DevServerHandle = {
|
|
1866
2648
|
host,
|
|
1867
2649
|
port,
|
|
1868
2650
|
url,
|
|
1869
|
-
routes:
|
|
2651
|
+
routes: currentRoutes,
|
|
1870
2652
|
state: serverState,
|
|
1871
2653
|
outboxWorker: serverState.outboxWorker,
|
|
2654
|
+
reload: async (reason?: string) => {
|
|
2655
|
+
const result = await reloadGeneratedArtifacts(reason);
|
|
2656
|
+
handle.routes = currentRoutes;
|
|
2657
|
+
handle.outboxWorker = serverState.outboxWorker;
|
|
2658
|
+
return result;
|
|
2659
|
+
},
|
|
1872
2660
|
stop: () => {
|
|
1873
2661
|
serverState.outboxWorker?.stop();
|
|
1874
2662
|
liveManager?.stop();
|
|
1875
2663
|
server.stop(true);
|
|
1876
2664
|
restoreRuntimeEnvironment();
|
|
2665
|
+
disposeRuntimeModuleNamespace();
|
|
1877
2666
|
const adapter = serverState.adapter;
|
|
1878
2667
|
serverState.adapter = null;
|
|
1879
2668
|
void adapter?.close().catch(() => undefined);
|
|
1880
2669
|
},
|
|
1881
2670
|
};
|
|
2671
|
+
|
|
2672
|
+
return handle;
|
|
1882
2673
|
}
|
|
1883
2674
|
|
|
1884
2675
|
export function resolveDevPort(explicit?: number): number {
|