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/cli/dev.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { closeSync, readFileSync, readlinkSync, openSync } from "node:fs";
|
|
3
|
+
import { createServer as createNetServer } from "node:net";
|
|
2
4
|
import { run } from "../compiler/orchestrator/run.ts";
|
|
3
|
-
import { join } from "node:path";
|
|
5
|
+
import { basename, join } from "node:path";
|
|
4
6
|
import { nodeFileSystem } from "../compiler/fs/index.ts";
|
|
5
7
|
import { stripDeterministicHeader } from "../compiler/primitives/header.ts";
|
|
8
|
+
import { hashStable } from "../compiler/primitives/hash.ts";
|
|
6
9
|
import { GENERATED_DIR } from "../compiler/emitter/constants.ts";
|
|
7
10
|
import type { FrontendGraph } from "../compiler/types/frontend-graph.ts";
|
|
8
11
|
import { detectPackageManager } from "../compiler/package-manager/detect.ts";
|
|
@@ -12,6 +15,12 @@ import {
|
|
|
12
15
|
formatDevConsoleJson,
|
|
13
16
|
runDevConsoleCycle,
|
|
14
17
|
} from "../dev-console/cycle.ts";
|
|
18
|
+
import type {
|
|
19
|
+
DevConsoleAgentContext,
|
|
20
|
+
DevConsoleCycle,
|
|
21
|
+
DevConsoleGeneratedSummary,
|
|
22
|
+
DevConsoleSummary,
|
|
23
|
+
} from "../dev-console/types.ts";
|
|
15
24
|
import {
|
|
16
25
|
resolveDevHost,
|
|
17
26
|
resolveDevPort,
|
|
@@ -19,6 +28,12 @@ import {
|
|
|
19
28
|
} from "../dev/server.ts";
|
|
20
29
|
import { startDevWatch } from "../dev/watch.ts";
|
|
21
30
|
import type { DevServerHandle } from "../dev/types.ts";
|
|
31
|
+
import { createAmbientDeltaRecorder } from "../delta/index.ts";
|
|
32
|
+
import { resetCompileSessions } from "../compiler/orchestrator/session.ts";
|
|
33
|
+
import { FORGE_PGLITE_STORE_ABORTED } from "../compiler/diagnostics/codes.ts";
|
|
34
|
+
import { isPgliteAbortMessage } from "../runtime/db/pglite-adapter.ts";
|
|
35
|
+
import { forgeCliCommandForWorkspace, forgeCliCommandsForWorkspace } from "../workspace/forge-cli.ts";
|
|
36
|
+
import { writeLastRunRecord } from "./last-run.ts";
|
|
22
37
|
|
|
23
38
|
export interface DevCommandOptions {
|
|
24
39
|
workspaceRoot: string;
|
|
@@ -29,7 +44,7 @@ export interface DevCommandOptions {
|
|
|
29
44
|
once?: boolean;
|
|
30
45
|
watch: boolean;
|
|
31
46
|
json: boolean;
|
|
32
|
-
db: "pglite" | "postgres" | "none";
|
|
47
|
+
db: "memory" | "pglite" | "postgres" | "none";
|
|
33
48
|
databaseUrl?: string;
|
|
34
49
|
worker: boolean;
|
|
35
50
|
withWeb?: boolean;
|
|
@@ -41,6 +56,9 @@ export interface DevCommandOptions {
|
|
|
41
56
|
envFile?: string;
|
|
42
57
|
mode?: "dev" | "serve";
|
|
43
58
|
allowDevAuth?: boolean;
|
|
59
|
+
skipStartupConsole?: boolean;
|
|
60
|
+
detach?: boolean;
|
|
61
|
+
lifecycle?: "status" | "stop";
|
|
44
62
|
}
|
|
45
63
|
|
|
46
64
|
export interface DevCommandResult {
|
|
@@ -49,9 +67,30 @@ export interface DevCommandResult {
|
|
|
49
67
|
exitCode: 0 | 1;
|
|
50
68
|
}
|
|
51
69
|
|
|
70
|
+
export interface DevGenerateResult {
|
|
71
|
+
ok: boolean;
|
|
72
|
+
changed: string[];
|
|
73
|
+
unchanged: string[];
|
|
74
|
+
diagnostics: Array<{ severity: string; code: string; message: string }>;
|
|
75
|
+
exitCode: 0 | 1;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface DevStartupGeneratedEvidence {
|
|
79
|
+
ok: boolean;
|
|
80
|
+
state: "fresh" | "regenerated" | "stale-risk";
|
|
81
|
+
changedFiles: number;
|
|
82
|
+
sampleChanged: string[];
|
|
83
|
+
hiddenChanged: number;
|
|
84
|
+
message: string;
|
|
85
|
+
command: string;
|
|
86
|
+
checkCommand: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
52
89
|
interface WebDevServerHandle {
|
|
53
90
|
url: string;
|
|
54
91
|
port: number;
|
|
92
|
+
requestedPort?: number;
|
|
93
|
+
autoPortSelected: boolean;
|
|
55
94
|
command: string[];
|
|
56
95
|
stop: () => void;
|
|
57
96
|
}
|
|
@@ -60,6 +99,7 @@ interface DevStartupSummary {
|
|
|
60
99
|
schemaVersion: "0.1.0";
|
|
61
100
|
ok: true;
|
|
62
101
|
mode: "dev";
|
|
102
|
+
warnings: Array<{ code: string; message: string; nextAction?: string }>;
|
|
63
103
|
api: {
|
|
64
104
|
url: string;
|
|
65
105
|
host: string;
|
|
@@ -70,14 +110,25 @@ interface DevStartupSummary {
|
|
|
70
110
|
web: null | {
|
|
71
111
|
url: string;
|
|
72
112
|
port: number;
|
|
113
|
+
requestedPort?: number;
|
|
114
|
+
autoPortSelected: boolean;
|
|
73
115
|
command: string[];
|
|
74
116
|
framework: string;
|
|
75
117
|
routes: string[];
|
|
76
118
|
bridgeFiles: string[];
|
|
77
119
|
apiUrlEnv?: string;
|
|
78
120
|
};
|
|
121
|
+
preview: {
|
|
122
|
+
studioUrl?: string;
|
|
123
|
+
targetAppUrl: string;
|
|
124
|
+
targetAppPort: number;
|
|
125
|
+
isStudioSelfPreview: boolean;
|
|
126
|
+
note: string;
|
|
127
|
+
};
|
|
79
128
|
watch: {
|
|
80
129
|
enabled: boolean;
|
|
130
|
+
autoGenerate: boolean;
|
|
131
|
+
reloadsRuntime: boolean;
|
|
81
132
|
};
|
|
82
133
|
runtime: {
|
|
83
134
|
routes: Array<{ method: string; path: string; purpose: string }>;
|
|
@@ -90,6 +141,20 @@ interface DevStartupSummary {
|
|
|
90
141
|
bridgeFiles: string[];
|
|
91
142
|
diagnostics: number;
|
|
92
143
|
};
|
|
144
|
+
generated: {
|
|
145
|
+
state: "fresh" | "regenerated" | "stale-risk";
|
|
146
|
+
generatorVersion?: string;
|
|
147
|
+
inputHash?: string;
|
|
148
|
+
buildInfoHash?: string;
|
|
149
|
+
runtimeLoadedHash?: string;
|
|
150
|
+
runtimeStaleRisk: boolean;
|
|
151
|
+
changedFiles: number;
|
|
152
|
+
sampleChanged: string[];
|
|
153
|
+
hiddenChanged: number;
|
|
154
|
+
command: string;
|
|
155
|
+
checkCommand: string;
|
|
156
|
+
message: string;
|
|
157
|
+
};
|
|
93
158
|
next: {
|
|
94
159
|
browserUrl: string;
|
|
95
160
|
apiIndex: string;
|
|
@@ -99,6 +164,504 @@ interface DevStartupSummary {
|
|
|
99
164
|
pid: number;
|
|
100
165
|
}
|
|
101
166
|
|
|
167
|
+
interface DevStartFailure {
|
|
168
|
+
message: string;
|
|
169
|
+
code?: string;
|
|
170
|
+
failureKind: "port_busy" | "pglite_store_aborted" | "dev_start_failed";
|
|
171
|
+
nextActions: string[];
|
|
172
|
+
busy?: {
|
|
173
|
+
port: number;
|
|
174
|
+
host: string;
|
|
175
|
+
suggestedCommands: string[];
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const DEV_STATE_DIR = ".forge/dev";
|
|
180
|
+
const DEV_PID_FILE = `${DEV_STATE_DIR}/dev.pid`;
|
|
181
|
+
const DEV_LOG_FILE = `${DEV_STATE_DIR}/dev.log`;
|
|
182
|
+
|
|
183
|
+
function devStatePaths(workspaceRoot: string): { dir: string; pidFile: string; logFile: string } {
|
|
184
|
+
return {
|
|
185
|
+
dir: join(workspaceRoot, DEV_STATE_DIR),
|
|
186
|
+
pidFile: join(workspaceRoot, DEV_PID_FILE),
|
|
187
|
+
logFile: join(workspaceRoot, DEV_LOG_FILE),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function readDetachedDevPid(workspaceRoot: string): number | null {
|
|
192
|
+
const text = nodeFileSystem.readText(devStatePaths(workspaceRoot).pidFile);
|
|
193
|
+
if (text === null) {
|
|
194
|
+
return null;
|
|
195
|
+
}
|
|
196
|
+
const pid = Number(text.trim());
|
|
197
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isProcessRunning(pid: number): boolean {
|
|
201
|
+
try {
|
|
202
|
+
process.kill(pid, 0);
|
|
203
|
+
return true;
|
|
204
|
+
} catch (error) {
|
|
205
|
+
return error instanceof Error && "code" in error && error.code === "EPERM";
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function removeDevPidFile(workspaceRoot: string): void {
|
|
210
|
+
const paths = devStatePaths(workspaceRoot);
|
|
211
|
+
if (nodeFileSystem.exists(paths.pidFile)) {
|
|
212
|
+
nodeFileSystem.remove(paths.pidFile);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function processDetails(pid: number): { pid: number; cwd?: string; command?: string } {
|
|
217
|
+
const details: { pid: number; cwd?: string; command?: string } = { pid };
|
|
218
|
+
try {
|
|
219
|
+
details.cwd = readlinkSync(`/proc/${pid}/cwd`);
|
|
220
|
+
} catch {
|
|
221
|
+
// /proc is Linux-only; process metadata is best-effort.
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const raw = readFileSync(`/proc/${pid}/cmdline`, "utf8");
|
|
225
|
+
const command = raw.split("\0").filter(Boolean).join(" ");
|
|
226
|
+
if (command) details.command = command;
|
|
227
|
+
} catch {
|
|
228
|
+
// /proc is Linux-only; process metadata is best-effort.
|
|
229
|
+
}
|
|
230
|
+
return details;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function printDevLifecycleResult(input: {
|
|
234
|
+
options: DevCommandOptions;
|
|
235
|
+
ok: boolean;
|
|
236
|
+
action: "detach" | "status" | "stop";
|
|
237
|
+
running: boolean;
|
|
238
|
+
pid?: number;
|
|
239
|
+
process?: { pid: number; cwd?: string; command?: string };
|
|
240
|
+
logFile: string;
|
|
241
|
+
message: string;
|
|
242
|
+
exitCode: 0 | 1;
|
|
243
|
+
}): DevCommandResult {
|
|
244
|
+
const payload = {
|
|
245
|
+
ok: input.ok,
|
|
246
|
+
action: input.action,
|
|
247
|
+
running: input.running,
|
|
248
|
+
...(input.pid !== undefined ? { pid: input.pid } : {}),
|
|
249
|
+
...(input.process ? { process: input.process } : {}),
|
|
250
|
+
logFile: input.logFile,
|
|
251
|
+
message: input.message,
|
|
252
|
+
nextActions: input.running
|
|
253
|
+
? ["forge dev status --json", "forge dev stop --json", `tail -f ${input.logFile}`]
|
|
254
|
+
: ["forge dev --detach --json"],
|
|
255
|
+
exitCode: input.exitCode,
|
|
256
|
+
};
|
|
257
|
+
if (input.options.json) {
|
|
258
|
+
process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
|
|
259
|
+
} else {
|
|
260
|
+
process.stdout.write(`${input.message}\n`);
|
|
261
|
+
if (input.pid !== undefined) process.stdout.write(`pid: ${input.pid}\n`);
|
|
262
|
+
if (input.process?.cwd) process.stdout.write(`cwd: ${input.process.cwd}\n`);
|
|
263
|
+
if (input.process?.command) process.stdout.write(`command: ${input.process.command}\n`);
|
|
264
|
+
process.stdout.write(`log: ${input.logFile}\n`);
|
|
265
|
+
}
|
|
266
|
+
return { exitCode: input.exitCode };
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function buildDetachedDevArgs(options: DevCommandOptions): string[] {
|
|
270
|
+
const args = ["dev", "--skip-startup-console"];
|
|
271
|
+
if (options.host) args.push("--host", options.host);
|
|
272
|
+
if (options.port !== undefined) args.push("--port", String(options.port));
|
|
273
|
+
if (options.mock) args.push("--mock");
|
|
274
|
+
if (options.mockAi) args.push("--mock-ai");
|
|
275
|
+
if (!options.watch) args.push("--no-watch");
|
|
276
|
+
if (options.db) args.push("--db", options.db);
|
|
277
|
+
if (options.databaseUrl) args.push("--database-url", options.databaseUrl);
|
|
278
|
+
if (!options.worker) args.push("--no-worker");
|
|
279
|
+
if (options.withWeb === false) args.push("--no-web");
|
|
280
|
+
if (options.apiOnly) args.push("--api-only");
|
|
281
|
+
if (options.webOnly) args.push("--web-only");
|
|
282
|
+
if (options.open) args.push("--open");
|
|
283
|
+
if (options.webPort !== undefined) args.push("--web-port", String(options.webPort));
|
|
284
|
+
if (options.telemetry.length > 0) args.push("--telemetry", options.telemetry.join(","));
|
|
285
|
+
if (options.envFile) args.push("--env-file", options.envFile);
|
|
286
|
+
return args;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function runDevStatus(options: DevCommandOptions): DevCommandResult {
|
|
290
|
+
const paths = devStatePaths(options.workspaceRoot);
|
|
291
|
+
const pid = readDetachedDevPid(options.workspaceRoot);
|
|
292
|
+
const running = pid !== null && isProcessRunning(pid);
|
|
293
|
+
if (pid !== null && !running) {
|
|
294
|
+
removeDevPidFile(options.workspaceRoot);
|
|
295
|
+
}
|
|
296
|
+
return printDevLifecycleResult({
|
|
297
|
+
options,
|
|
298
|
+
ok: true,
|
|
299
|
+
action: "status",
|
|
300
|
+
running,
|
|
301
|
+
...(pid !== null ? { pid } : {}),
|
|
302
|
+
...(pid !== null && running ? { process: processDetails(pid) } : {}),
|
|
303
|
+
logFile: paths.logFile,
|
|
304
|
+
message: running ? "forge dev detached server is running" : "forge dev detached server is not running",
|
|
305
|
+
exitCode: 0,
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
function runDevStop(options: DevCommandOptions): DevCommandResult {
|
|
310
|
+
const paths = devStatePaths(options.workspaceRoot);
|
|
311
|
+
const pid = readDetachedDevPid(options.workspaceRoot);
|
|
312
|
+
if (pid === null || !isProcessRunning(pid)) {
|
|
313
|
+
removeDevPidFile(options.workspaceRoot);
|
|
314
|
+
return printDevLifecycleResult({
|
|
315
|
+
options,
|
|
316
|
+
ok: true,
|
|
317
|
+
action: "stop",
|
|
318
|
+
running: false,
|
|
319
|
+
...(pid !== null ? { pid } : {}),
|
|
320
|
+
...(pid !== null ? { process: processDetails(pid) } : {}),
|
|
321
|
+
logFile: paths.logFile,
|
|
322
|
+
message: "no detached forge dev server was running",
|
|
323
|
+
exitCode: 0,
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
process.kill(pid, "SIGTERM");
|
|
327
|
+
removeDevPidFile(options.workspaceRoot);
|
|
328
|
+
return printDevLifecycleResult({
|
|
329
|
+
options,
|
|
330
|
+
ok: true,
|
|
331
|
+
action: "stop",
|
|
332
|
+
running: false,
|
|
333
|
+
pid,
|
|
334
|
+
process: processDetails(pid),
|
|
335
|
+
logFile: paths.logFile,
|
|
336
|
+
message: "stopped detached forge dev server",
|
|
337
|
+
exitCode: 0,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
function runDevDetach(options: DevCommandOptions): DevCommandResult {
|
|
342
|
+
if (options.once) {
|
|
343
|
+
return printDevLifecycleResult({
|
|
344
|
+
options,
|
|
345
|
+
ok: false,
|
|
346
|
+
action: "detach",
|
|
347
|
+
running: false,
|
|
348
|
+
logFile: devStatePaths(options.workspaceRoot).logFile,
|
|
349
|
+
message: "forge dev --detach cannot be combined with --once",
|
|
350
|
+
exitCode: 1,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
const existingPid = readDetachedDevPid(options.workspaceRoot);
|
|
354
|
+
const paths = devStatePaths(options.workspaceRoot);
|
|
355
|
+
nodeFileSystem.mkdirp(paths.dir);
|
|
356
|
+
if (existingPid !== null && isProcessRunning(existingPid)) {
|
|
357
|
+
return printDevLifecycleResult({
|
|
358
|
+
options,
|
|
359
|
+
ok: true,
|
|
360
|
+
action: "detach",
|
|
361
|
+
running: true,
|
|
362
|
+
pid: existingPid,
|
|
363
|
+
process: processDetails(existingPid),
|
|
364
|
+
logFile: paths.logFile,
|
|
365
|
+
message: "forge dev detached server is already running",
|
|
366
|
+
exitCode: 0,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const cliPath = process.argv[1];
|
|
371
|
+
if (!cliPath) {
|
|
372
|
+
return printDevLifecycleResult({
|
|
373
|
+
options,
|
|
374
|
+
ok: false,
|
|
375
|
+
action: "detach",
|
|
376
|
+
running: false,
|
|
377
|
+
logFile: paths.logFile,
|
|
378
|
+
message: "could not resolve current forge CLI path for detached dev server",
|
|
379
|
+
exitCode: 1,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
const fd = openSync(paths.logFile, "a");
|
|
384
|
+
try {
|
|
385
|
+
const child = spawn(process.execPath, [cliPath, ...buildDetachedDevArgs(options)], {
|
|
386
|
+
cwd: options.workspaceRoot,
|
|
387
|
+
detached: true,
|
|
388
|
+
stdio: ["ignore", fd, fd],
|
|
389
|
+
windowsHide: true,
|
|
390
|
+
});
|
|
391
|
+
if (child.pid === undefined) {
|
|
392
|
+
return printDevLifecycleResult({
|
|
393
|
+
options,
|
|
394
|
+
ok: false,
|
|
395
|
+
action: "detach",
|
|
396
|
+
running: false,
|
|
397
|
+
logFile: paths.logFile,
|
|
398
|
+
message: "detached forge dev process started without a pid",
|
|
399
|
+
exitCode: 1,
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
child.unref();
|
|
403
|
+
nodeFileSystem.writeText(paths.pidFile, `${child.pid}\n`);
|
|
404
|
+
return printDevLifecycleResult({
|
|
405
|
+
options,
|
|
406
|
+
ok: true,
|
|
407
|
+
action: "detach",
|
|
408
|
+
running: true,
|
|
409
|
+
pid: child.pid,
|
|
410
|
+
process: processDetails(child.pid),
|
|
411
|
+
logFile: paths.logFile,
|
|
412
|
+
message: "started detached forge dev server",
|
|
413
|
+
exitCode: 0,
|
|
414
|
+
});
|
|
415
|
+
} finally {
|
|
416
|
+
closeSync(fd);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function nextPreviewPort(webUrl?: string): number {
|
|
421
|
+
if (!webUrl) {
|
|
422
|
+
return 5174;
|
|
423
|
+
}
|
|
424
|
+
try {
|
|
425
|
+
const parsed = new URL(webUrl);
|
|
426
|
+
const port = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
|
|
427
|
+
return Number.isFinite(port) && port > 0 ? port + 1 : 5174;
|
|
428
|
+
} catch {
|
|
429
|
+
return 5174;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function isForgeStudioWorkspace(workspaceRoot: string): boolean {
|
|
434
|
+
return basename(workspaceRoot).toLowerCase() === "forge-studio";
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
function previewSummaryFor(input: {
|
|
438
|
+
workspaceRoot: string;
|
|
439
|
+
host: string;
|
|
440
|
+
webUrl?: string;
|
|
441
|
+
}): DevStartupSummary["preview"] {
|
|
442
|
+
if (input.webUrl && !isForgeStudioWorkspace(input.workspaceRoot)) {
|
|
443
|
+
const parsed = new URL(input.webUrl);
|
|
444
|
+
const targetAppPort = parsed.port ? Number(parsed.port) : parsed.protocol === "https:" ? 443 : 80;
|
|
445
|
+
return {
|
|
446
|
+
targetAppUrl: input.webUrl,
|
|
447
|
+
targetAppPort,
|
|
448
|
+
isStudioSelfPreview: false,
|
|
449
|
+
note: `Web app preview is running at ${input.webUrl}.`,
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const targetAppPort = nextPreviewPort(input.webUrl);
|
|
454
|
+
const targetAppUrl = `http://${input.host}:${targetAppPort}`;
|
|
455
|
+
const isStudioSelfPreview = Boolean(input.webUrl && input.webUrl === targetAppUrl);
|
|
456
|
+
return {
|
|
457
|
+
...(input.webUrl ? { studioUrl: input.webUrl } : {}),
|
|
458
|
+
targetAppUrl,
|
|
459
|
+
targetAppPort,
|
|
460
|
+
isStudioSelfPreview,
|
|
461
|
+
note: input.webUrl
|
|
462
|
+
? `Use ${targetAppUrl} for the app being built when ${input.webUrl} is Forge Studio itself.`
|
|
463
|
+
: `No web app was detected; ${targetAppUrl} is the default target app preview URL for Studio attach flows.`,
|
|
464
|
+
};
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
async function isPortAvailable(host: string, port: number): Promise<boolean> {
|
|
468
|
+
if (port === 0) {
|
|
469
|
+
return true;
|
|
470
|
+
}
|
|
471
|
+
const server = createNetServer();
|
|
472
|
+
return new Promise<boolean>((resolve) => {
|
|
473
|
+
const cleanup = () => {
|
|
474
|
+
server.removeAllListeners();
|
|
475
|
+
};
|
|
476
|
+
server.once("error", () => {
|
|
477
|
+
cleanup();
|
|
478
|
+
resolve(false);
|
|
479
|
+
});
|
|
480
|
+
server.listen(port, host, () => {
|
|
481
|
+
server.close(() => {
|
|
482
|
+
cleanup();
|
|
483
|
+
resolve(true);
|
|
484
|
+
});
|
|
485
|
+
});
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function classifyDevStartFailure(input: {
|
|
490
|
+
rawMessage: string;
|
|
491
|
+
host: string;
|
|
492
|
+
port: number;
|
|
493
|
+
webPort?: number;
|
|
494
|
+
db: DevCommandOptions["db"];
|
|
495
|
+
}): DevStartFailure {
|
|
496
|
+
const lowerMessage = input.rawMessage.toLowerCase();
|
|
497
|
+
const busy =
|
|
498
|
+
lowerMessage.includes("eaddrinuse") ||
|
|
499
|
+
lowerMessage.includes("address already in use") ||
|
|
500
|
+
/port\s+\d+\s+.*in use/i.test(input.rawMessage);
|
|
501
|
+
if (busy) {
|
|
502
|
+
const suggestedCommands = [
|
|
503
|
+
"forge dev status --json",
|
|
504
|
+
"forge dev stop --json",
|
|
505
|
+
`forge dev --port 0${input.webPort ? ` --web-port ${input.webPort}` : ""} --json`,
|
|
506
|
+
"forge doctor windows --json",
|
|
507
|
+
];
|
|
508
|
+
return {
|
|
509
|
+
message: `${input.rawMessage}. Port ${input.port} appears busy; stop the existing process or rerun with --port 0 / --port <free-port>.`,
|
|
510
|
+
failureKind: "port_busy",
|
|
511
|
+
nextActions: suggestedCommands,
|
|
512
|
+
busy: {
|
|
513
|
+
port: input.port,
|
|
514
|
+
host: input.host,
|
|
515
|
+
suggestedCommands,
|
|
516
|
+
},
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (input.db === "pglite" && isPgliteAbortMessage(input.rawMessage)) {
|
|
521
|
+
return {
|
|
522
|
+
message: `${input.rawMessage}. Local PGlite store may be corrupted or stale at .forge/pglite.`,
|
|
523
|
+
code: FORGE_PGLITE_STORE_ABORTED,
|
|
524
|
+
failureKind: "pglite_store_aborted",
|
|
525
|
+
nextActions: [
|
|
526
|
+
"forge doctor pglite --json",
|
|
527
|
+
"forge db repair --local --adapter pglite --json",
|
|
528
|
+
"forge dev --db memory --json",
|
|
529
|
+
],
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
return {
|
|
534
|
+
message: input.rawMessage,
|
|
535
|
+
failureKind: "dev_start_failed",
|
|
536
|
+
nextActions: ["forge dev --once --json", "forge check --json"],
|
|
537
|
+
};
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
function writeDevStartFailure(input: {
|
|
541
|
+
workspaceRoot: string;
|
|
542
|
+
startedAt: Date;
|
|
543
|
+
finishedAt: Date;
|
|
544
|
+
options: DevCommandOptions;
|
|
545
|
+
host: string;
|
|
546
|
+
port: number;
|
|
547
|
+
failure: DevStartFailure;
|
|
548
|
+
}): DevCommandResult {
|
|
549
|
+
const nextActions = forgeCliCommandsForWorkspace(input.workspaceRoot, input.failure.nextActions);
|
|
550
|
+
const busy = input.failure.busy
|
|
551
|
+
? {
|
|
552
|
+
...input.failure.busy,
|
|
553
|
+
suggestedCommands: forgeCliCommandsForWorkspace(input.workspaceRoot, input.failure.busy.suggestedCommands),
|
|
554
|
+
}
|
|
555
|
+
: undefined;
|
|
556
|
+
writeLastRunRecord(input.workspaceRoot, {
|
|
557
|
+
schemaVersion: "0.1.0",
|
|
558
|
+
command: forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev"),
|
|
559
|
+
ok: false,
|
|
560
|
+
startedAt: input.startedAt.toISOString(),
|
|
561
|
+
finishedAt: input.finishedAt.toISOString(),
|
|
562
|
+
durationMs: input.finishedAt.getTime() - input.startedAt.getTime(),
|
|
563
|
+
...(input.failure.code ? { code: input.failure.code } : {}),
|
|
564
|
+
failureKind: input.failure.failureKind,
|
|
565
|
+
message: input.failure.message,
|
|
566
|
+
nextActions,
|
|
567
|
+
details: {
|
|
568
|
+
db: input.options.db,
|
|
569
|
+
host: input.host,
|
|
570
|
+
port: input.port,
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
if (input.options.json) {
|
|
574
|
+
process.stdout.write(
|
|
575
|
+
`${JSON.stringify({
|
|
576
|
+
ok: false,
|
|
577
|
+
error: input.failure.message,
|
|
578
|
+
code: input.failure.code,
|
|
579
|
+
failureKind: input.failure.failureKind,
|
|
580
|
+
busy,
|
|
581
|
+
nextActions,
|
|
582
|
+
exitCode: 1,
|
|
583
|
+
})}\n`,
|
|
584
|
+
);
|
|
585
|
+
} else {
|
|
586
|
+
console.error(`error${input.failure.code ? ` ${input.failure.code}` : ""}: ${input.failure.message}`);
|
|
587
|
+
if (nextActions.length > 0) {
|
|
588
|
+
console.error("next:");
|
|
589
|
+
for (const action of nextActions) {
|
|
590
|
+
console.error(` ${action}`);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return { exitCode: 1 };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
export async function resolveAvailableWebPort(input: {
|
|
598
|
+
host: string;
|
|
599
|
+
preferredPort: number;
|
|
600
|
+
maxAttempts?: number;
|
|
601
|
+
}): Promise<{ port: number; requestedPort?: number; autoPortSelected: boolean }> {
|
|
602
|
+
const attempts = input.maxAttempts ?? 20;
|
|
603
|
+
const start = Math.max(0, Math.floor(input.preferredPort));
|
|
604
|
+
if (start === 0) {
|
|
605
|
+
return { port: 0, autoPortSelected: false };
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
for (let offset = 0; offset < attempts; offset += 1) {
|
|
609
|
+
const candidate = start + offset;
|
|
610
|
+
if (await isPortAvailable(input.host, candidate)) {
|
|
611
|
+
return {
|
|
612
|
+
port: candidate,
|
|
613
|
+
...(candidate !== start ? { requestedPort: start } : {}),
|
|
614
|
+
autoPortSelected: candidate !== start,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
port: 0,
|
|
621
|
+
requestedPort: start,
|
|
622
|
+
autoPortSelected: true,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
export function devStartupConsoleUrlOptions(input: {
|
|
627
|
+
host: string;
|
|
628
|
+
port: number;
|
|
629
|
+
webUrl?: string;
|
|
630
|
+
}): { apiUrl?: string; webUrl?: string } {
|
|
631
|
+
return {
|
|
632
|
+
...(input.port === 0 ? {} : { apiUrl: `http://${input.host}:${input.port}` }),
|
|
633
|
+
...(input.webUrl ? { webUrl: input.webUrl } : {}),
|
|
634
|
+
};
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
export interface DevWatchGenerateFailureEvent {
|
|
638
|
+
schemaVersion: "0.1.0";
|
|
639
|
+
event: "dev.generate_failed";
|
|
640
|
+
ok: false;
|
|
641
|
+
changedFiles: number;
|
|
642
|
+
changedPaths: string[];
|
|
643
|
+
generated: DevConsoleGeneratedSummary;
|
|
644
|
+
diagnostics: DevGenerateResult["diagnostics"];
|
|
645
|
+
nextActions: string[];
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
export interface DevWatchReloadEvent {
|
|
649
|
+
schemaVersion: "0.1.0";
|
|
650
|
+
event: "dev.reload";
|
|
651
|
+
ok: boolean;
|
|
652
|
+
changedFiles: number;
|
|
653
|
+
changedPaths: string[];
|
|
654
|
+
migrated: boolean;
|
|
655
|
+
routes: number;
|
|
656
|
+
runtimeEntries: number;
|
|
657
|
+
worker: unknown;
|
|
658
|
+
diagnostics: DevGenerateResult["diagnostics"];
|
|
659
|
+
generated: DevConsoleGeneratedSummary;
|
|
660
|
+
preview: DevConsoleSummary["preview"];
|
|
661
|
+
agentContext: DevConsoleAgentContext;
|
|
662
|
+
nextActions: string[];
|
|
663
|
+
}
|
|
664
|
+
|
|
102
665
|
function readGeneratedJson<T>(workspaceRoot: string, relative: string): T | null {
|
|
103
666
|
const absolute = join(workspaceRoot, relative);
|
|
104
667
|
if (!nodeFileSystem.exists(absolute)) {
|
|
@@ -175,6 +738,8 @@ function startWebDevServer(input: {
|
|
|
175
738
|
workspaceRoot: string;
|
|
176
739
|
host: string;
|
|
177
740
|
port: number;
|
|
741
|
+
requestedPort?: number;
|
|
742
|
+
autoPortSelected?: boolean;
|
|
178
743
|
apiUrl: string;
|
|
179
744
|
json: boolean;
|
|
180
745
|
}): WebDevServerHandle | null {
|
|
@@ -209,6 +774,8 @@ function startWebDevServer(input: {
|
|
|
209
774
|
return {
|
|
210
775
|
url: `http://${input.host}:${input.port}`,
|
|
211
776
|
port: input.port,
|
|
777
|
+
...(input.requestedPort !== undefined ? { requestedPort: input.requestedPort } : {}),
|
|
778
|
+
autoPortSelected: input.autoPortSelected ?? false,
|
|
212
779
|
command,
|
|
213
780
|
stop: () => {
|
|
214
781
|
try {
|
|
@@ -225,19 +792,43 @@ function buildStartupSummary(input: {
|
|
|
225
792
|
handle: DevServerHandle;
|
|
226
793
|
web?: WebDevServerHandle | null;
|
|
227
794
|
watch: boolean;
|
|
795
|
+
generated?: DevStartupGeneratedEvidence;
|
|
228
796
|
}): DevStartupSummary {
|
|
229
797
|
const frontend = readGeneratedJson<FrontendGraph>(
|
|
230
798
|
input.workspaceRoot,
|
|
231
799
|
`${GENERATED_DIR}/frontendGraph.json`,
|
|
232
800
|
);
|
|
801
|
+
const buildInfo = readGeneratedJson<{ generatorVersion?: string; inputHash?: string }>(
|
|
802
|
+
input.workspaceRoot,
|
|
803
|
+
`${GENERATED_DIR}/buildInfo.json`,
|
|
804
|
+
);
|
|
805
|
+
const buildInfoRaw = nodeFileSystem.exists(join(input.workspaceRoot, `${GENERATED_DIR}/buildInfo.json`))
|
|
806
|
+
? stripDeterministicHeader(nodeFileSystem.readText(join(input.workspaceRoot, `${GENERATED_DIR}/buildInfo.json`)) ?? "")
|
|
807
|
+
: "";
|
|
233
808
|
const bindings = frontend
|
|
234
809
|
? [...new Set(frontend.clientBindings.map((binding) => `${binding.kind}:${binding.name}`))].sort()
|
|
235
810
|
: [];
|
|
236
811
|
const browserUrl = input.web?.url ?? input.handle.url;
|
|
812
|
+
const warnings = input.handle.state.db.kind === "memory"
|
|
813
|
+
? [
|
|
814
|
+
{
|
|
815
|
+
code: "FORGE_DEV_MEMORY_DB_FIDELITY",
|
|
816
|
+
message:
|
|
817
|
+
"Memory DB is fast and non-persistent; PGlite/Postgres remain the authoritative checks for full SQL constraints and adapter fidelity.",
|
|
818
|
+
nextAction: `rerun with ${forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev --db pglite --once --json")} before treating the result as database-realistic`,
|
|
819
|
+
},
|
|
820
|
+
]
|
|
821
|
+
: [];
|
|
822
|
+
const preview = previewSummaryFor({
|
|
823
|
+
workspaceRoot: input.workspaceRoot,
|
|
824
|
+
host: input.handle.host,
|
|
825
|
+
...(input.web?.url ? { webUrl: input.web.url } : {}),
|
|
826
|
+
});
|
|
237
827
|
return {
|
|
238
828
|
schemaVersion: "0.1.0",
|
|
239
829
|
ok: true,
|
|
240
830
|
mode: "dev",
|
|
831
|
+
warnings,
|
|
241
832
|
api: {
|
|
242
833
|
url: input.handle.url,
|
|
243
834
|
host: input.handle.host,
|
|
@@ -249,6 +840,8 @@ function buildStartupSummary(input: {
|
|
|
249
840
|
? {
|
|
250
841
|
url: input.web.url,
|
|
251
842
|
port: input.web.port,
|
|
843
|
+
...(input.web.requestedPort !== undefined ? { requestedPort: input.web.requestedPort } : {}),
|
|
844
|
+
autoPortSelected: input.web.autoPortSelected,
|
|
252
845
|
command: input.web.command,
|
|
253
846
|
framework: frontend?.framework ?? "unknown",
|
|
254
847
|
routes: frontend?.routes.map((route) => route.path) ?? [],
|
|
@@ -256,8 +849,11 @@ function buildStartupSummary(input: {
|
|
|
256
849
|
...(frontend?.dev?.apiUrlEnv ? { apiUrlEnv: frontend.dev.apiUrlEnv } : {}),
|
|
257
850
|
}
|
|
258
851
|
: null,
|
|
852
|
+
preview,
|
|
259
853
|
watch: {
|
|
260
854
|
enabled: input.watch,
|
|
855
|
+
autoGenerate: input.watch,
|
|
856
|
+
reloadsRuntime: input.watch,
|
|
261
857
|
},
|
|
262
858
|
runtime: {
|
|
263
859
|
routes: input.handle.routes.map((route) => ({
|
|
@@ -274,11 +870,25 @@ function buildStartupSummary(input: {
|
|
|
274
870
|
bridgeFiles: frontend?.bridgeFiles ?? [],
|
|
275
871
|
diagnostics: frontend?.diagnostics.length ?? 0,
|
|
276
872
|
},
|
|
873
|
+
generated: {
|
|
874
|
+
state: input.generated?.state ?? (buildInfoRaw ? "fresh" : "stale-risk"),
|
|
875
|
+
...(buildInfo?.generatorVersion ? { generatorVersion: buildInfo.generatorVersion } : {}),
|
|
876
|
+
...(buildInfo?.inputHash ? { inputHash: buildInfo.inputHash } : {}),
|
|
877
|
+
...(buildInfoRaw ? { buildInfoHash: hashStable(buildInfoRaw) } : {}),
|
|
878
|
+
...(buildInfoRaw ? { runtimeLoadedHash: hashStable(buildInfoRaw) } : {}),
|
|
879
|
+
runtimeStaleRisk: !buildInfoRaw || input.generated?.state === "stale-risk",
|
|
880
|
+
changedFiles: input.generated?.changedFiles ?? 0,
|
|
881
|
+
sampleChanged: input.generated?.sampleChanged ?? [],
|
|
882
|
+
hiddenChanged: input.generated?.hiddenChanged ?? 0,
|
|
883
|
+
command: input.generated?.command ?? forgeCliCommandForWorkspace(input.workspaceRoot, "forge generate"),
|
|
884
|
+
checkCommand: input.generated?.checkCommand ?? forgeCliCommandForWorkspace(input.workspaceRoot, "forge generate --check --json"),
|
|
885
|
+
message: input.generated?.message ?? (buildInfoRaw ? "generated artifacts are loaded" : "generated build info is missing"),
|
|
886
|
+
},
|
|
277
887
|
next: {
|
|
278
888
|
browserUrl,
|
|
279
889
|
apiIndex: input.handle.url,
|
|
280
|
-
inspect: "forge inspect
|
|
281
|
-
verify: "forge dev --once --json",
|
|
890
|
+
inspect: forgeCliCommandForWorkspace(input.workspaceRoot, "forge inspect summary --json"),
|
|
891
|
+
verify: forgeCliCommandForWorkspace(input.workspaceRoot, "forge dev --once --json"),
|
|
282
892
|
},
|
|
283
893
|
pid: process.pid,
|
|
284
894
|
};
|
|
@@ -295,11 +905,24 @@ function printStartupHuman(summary: DevStartupSummary): void {
|
|
|
295
905
|
lines.push(` DB: ${summary.api.db.kind} ${summary.api.db.connected ? "connected" : "not connected"}`);
|
|
296
906
|
lines.push(` Worker: ${summary.api.worker}`);
|
|
297
907
|
lines.push(` Watch: ${summary.watch.enabled ? "on" : "off"}`);
|
|
908
|
+
lines.push(` Auto-generate: ${summary.watch.autoGenerate ? "on" : "off"}`);
|
|
909
|
+
lines.push(` Generated: ${summary.generated.state}${summary.generated.changedFiles > 0 ? ` (${summary.generated.changedFiles} changed)` : ""}${summary.generated.runtimeStaleRisk ? " (stale risk)" : ""}`);
|
|
910
|
+
lines.push(` Generated note: ${summary.generated.message}`);
|
|
911
|
+
lines.push(` Generated check: ${summary.generated.checkCommand}`);
|
|
912
|
+
for (const warning of summary.warnings) {
|
|
913
|
+
lines.push(` Warning ${warning.code}: ${warning.message}`);
|
|
914
|
+
if (warning.nextAction) {
|
|
915
|
+
lines.push(` Next: ${warning.nextAction}`);
|
|
916
|
+
}
|
|
917
|
+
}
|
|
298
918
|
lines.push("");
|
|
299
919
|
|
|
300
920
|
lines.push("Web app");
|
|
301
921
|
if (summary.web) {
|
|
302
922
|
lines.push(` URL: ${summary.web.url}`);
|
|
923
|
+
if (summary.web.autoPortSelected && summary.web.requestedPort) {
|
|
924
|
+
lines.push(` Requested port: ${summary.web.requestedPort} (busy; selected ${summary.web.port})`);
|
|
925
|
+
}
|
|
303
926
|
lines.push(` Framework: ${summary.web.framework}`);
|
|
304
927
|
lines.push(` API env: ${summary.web.apiUrlEnv ?? "unknown"}=${summary.api.url}`);
|
|
305
928
|
lines.push(` Bridge: ${summary.web.bridgeFiles.length > 0 ? summary.web.bridgeFiles.join(", ") : "missing"}`);
|
|
@@ -325,6 +948,15 @@ function printStartupHuman(summary: DevStartupSummary): void {
|
|
|
325
948
|
lines.push("Agent checks");
|
|
326
949
|
lines.push(` ${summary.next.verify}`);
|
|
327
950
|
lines.push(` ${summary.next.inspect}`);
|
|
951
|
+
lines.push(" forge changed --json");
|
|
952
|
+
lines.push("");
|
|
953
|
+
|
|
954
|
+
lines.push("Preview");
|
|
955
|
+
lines.push(` Target app: ${summary.preview.targetAppUrl}`);
|
|
956
|
+
if (summary.preview.studioUrl) {
|
|
957
|
+
lines.push(` Studio: ${summary.preview.studioUrl}`);
|
|
958
|
+
}
|
|
959
|
+
lines.push(` Note: ${summary.preview.note}`);
|
|
328
960
|
lines.push("");
|
|
329
961
|
|
|
330
962
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
@@ -350,10 +982,152 @@ function openBrowser(url: string): void {
|
|
|
350
982
|
}
|
|
351
983
|
}
|
|
352
984
|
|
|
985
|
+
export async function ensureGeneratedForDev(workspaceRoot: string): Promise<DevGenerateResult> {
|
|
986
|
+
resetCompileSessions();
|
|
987
|
+
const result = await run({
|
|
988
|
+
workspaceRoot,
|
|
989
|
+
check: false,
|
|
990
|
+
dryRun: false,
|
|
991
|
+
json: false,
|
|
992
|
+
concurrency: 4,
|
|
993
|
+
});
|
|
994
|
+
return {
|
|
995
|
+
ok: result.exitCode === 0,
|
|
996
|
+
changed: result.changed,
|
|
997
|
+
unchanged: result.unchanged,
|
|
998
|
+
diagnostics: [...result.errors, ...result.warnings],
|
|
999
|
+
exitCode: result.exitCode,
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
export function generatedEvidenceFromCycle(cycle: DevConsoleCycle): DevStartupGeneratedEvidence {
|
|
1004
|
+
const phase = cycle.phases.find((item) => item.name === "generated");
|
|
1005
|
+
const changedFiles = Number(phase?.details?.changed ?? 0);
|
|
1006
|
+
const sampleChanged = Array.isArray(phase?.details?.sampleChanged)
|
|
1007
|
+
? phase.details.sampleChanged.filter((item): item is string => typeof item === "string")
|
|
1008
|
+
: [];
|
|
1009
|
+
const hiddenChanged = Number(phase?.details?.hiddenChanged ?? 0);
|
|
1010
|
+
return {
|
|
1011
|
+
ok: phase?.ok === true,
|
|
1012
|
+
state: phase?.ok === true
|
|
1013
|
+
? changedFiles > 0 ? "regenerated" : "fresh"
|
|
1014
|
+
: "stale-risk",
|
|
1015
|
+
changedFiles,
|
|
1016
|
+
sampleChanged,
|
|
1017
|
+
hiddenChanged,
|
|
1018
|
+
message: phase?.message ?? "generated phase did not report a message",
|
|
1019
|
+
command: cycle.summary.generated.command,
|
|
1020
|
+
checkCommand: cycle.summary.generated.checkCommand,
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
export function generatedEvidenceFromGenerateResult(
|
|
1025
|
+
result: DevGenerateResult,
|
|
1026
|
+
options: { workspaceRoot?: string } = {},
|
|
1027
|
+
): DevStartupGeneratedEvidence {
|
|
1028
|
+
return {
|
|
1029
|
+
ok: result.ok,
|
|
1030
|
+
state: result.ok ? result.changed.length > 0 ? "regenerated" : "fresh" : "stale-risk",
|
|
1031
|
+
changedFiles: result.changed.length,
|
|
1032
|
+
sampleChanged: result.changed.slice(0, 12),
|
|
1033
|
+
hiddenChanged: Math.max(0, result.changed.length - 12),
|
|
1034
|
+
message: result.ok
|
|
1035
|
+
? result.changed.length > 0
|
|
1036
|
+
? `regenerated ${result.changed.length} generated artifacts`
|
|
1037
|
+
: "generated artifacts are up to date"
|
|
1038
|
+
: "generated artifacts could not be regenerated",
|
|
1039
|
+
command: options.workspaceRoot
|
|
1040
|
+
? forgeCliCommandForWorkspace(options.workspaceRoot, "forge generate")
|
|
1041
|
+
: "forge generate",
|
|
1042
|
+
checkCommand: options.workspaceRoot
|
|
1043
|
+
? forgeCliCommandForWorkspace(options.workspaceRoot, "forge generate --check --json")
|
|
1044
|
+
: "forge generate --check --json",
|
|
1045
|
+
};
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
function printDevGenerateFailure(result: DevGenerateResult, json: boolean): void {
|
|
1049
|
+
if (json) {
|
|
1050
|
+
process.stdout.write(`${JSON.stringify({
|
|
1051
|
+
ok: false,
|
|
1052
|
+
phase: "generated",
|
|
1053
|
+
diagnostics: result.diagnostics,
|
|
1054
|
+
exitCode: 1,
|
|
1055
|
+
})}\n`);
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
console.error("error: forge dev could not regenerate generated artifacts");
|
|
1059
|
+
for (const diagnostic of result.diagnostics.filter((item) => item.severity === "error").slice(0, 5)) {
|
|
1060
|
+
console.error(`error ${diagnostic.code}: ${diagnostic.message}`);
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
export function buildDevWatchGenerateFailureEvent(input: {
|
|
1065
|
+
changedCount: number;
|
|
1066
|
+
changedPaths: string[];
|
|
1067
|
+
result: DevGenerateResult;
|
|
1068
|
+
workspaceRoot?: string;
|
|
1069
|
+
}): DevWatchGenerateFailureEvent {
|
|
1070
|
+
const nextActions = input.workspaceRoot
|
|
1071
|
+
? forgeCliCommandsForWorkspace(input.workspaceRoot, ["forge dev --once --json", "forge check --json"])
|
|
1072
|
+
: ["forge dev --once --json", "forge check --json"];
|
|
1073
|
+
return {
|
|
1074
|
+
schemaVersion: "0.1.0",
|
|
1075
|
+
event: "dev.generate_failed",
|
|
1076
|
+
ok: false,
|
|
1077
|
+
changedFiles: input.changedCount,
|
|
1078
|
+
changedPaths: input.changedPaths,
|
|
1079
|
+
generated: generatedEvidenceFromGenerateResult(input.result, { workspaceRoot: input.workspaceRoot }),
|
|
1080
|
+
diagnostics: input.result.diagnostics,
|
|
1081
|
+
nextActions,
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
1085
|
+
export function buildDevWatchReloadEvent(input: {
|
|
1086
|
+
changedCount: number;
|
|
1087
|
+
changedPaths: string[];
|
|
1088
|
+
generated: DevGenerateResult;
|
|
1089
|
+
reload: Awaited<ReturnType<DevServerHandle["reload"]>>;
|
|
1090
|
+
cycle: DevConsoleCycle;
|
|
1091
|
+
workspaceRoot?: string;
|
|
1092
|
+
}): DevWatchReloadEvent {
|
|
1093
|
+
const failureNextActions = input.workspaceRoot
|
|
1094
|
+
? forgeCliCommandsForWorkspace(input.workspaceRoot, ["forge dev --once --json", "forge check --json"])
|
|
1095
|
+
: ["forge dev --once --json", "forge check --json"];
|
|
1096
|
+
return {
|
|
1097
|
+
schemaVersion: "0.1.0",
|
|
1098
|
+
event: "dev.reload",
|
|
1099
|
+
ok: input.reload.ok,
|
|
1100
|
+
changedFiles: input.changedCount,
|
|
1101
|
+
changedPaths: input.changedPaths,
|
|
1102
|
+
migrated: input.reload.migrated,
|
|
1103
|
+
routes: input.reload.routes,
|
|
1104
|
+
runtimeEntries: input.reload.runtimeEntries,
|
|
1105
|
+
worker: input.reload.worker,
|
|
1106
|
+
diagnostics: input.reload.diagnostics,
|
|
1107
|
+
generated: generatedEvidenceFromGenerateResult(input.generated, { workspaceRoot: input.workspaceRoot }),
|
|
1108
|
+
preview: input.cycle.summary.preview,
|
|
1109
|
+
agentContext: input.cycle.summary.agentContext,
|
|
1110
|
+
nextActions: input.reload.ok
|
|
1111
|
+
? input.cycle.summary.agentContext.recommendedCommands
|
|
1112
|
+
: failureNextActions,
|
|
1113
|
+
};
|
|
1114
|
+
}
|
|
1115
|
+
|
|
353
1116
|
export async function runDevCommand(
|
|
354
1117
|
options: DevCommandOptions,
|
|
355
1118
|
): Promise<DevCommandResult> {
|
|
356
1119
|
const workspaceRoot = options.workspaceRoot.replace(/\\/g, "/");
|
|
1120
|
+
const startedAt = new Date();
|
|
1121
|
+
|
|
1122
|
+
if (options.lifecycle === "status") {
|
|
1123
|
+
return runDevStatus({ ...options, workspaceRoot });
|
|
1124
|
+
}
|
|
1125
|
+
if (options.lifecycle === "stop") {
|
|
1126
|
+
return runDevStop({ ...options, workspaceRoot });
|
|
1127
|
+
}
|
|
1128
|
+
if (options.detach) {
|
|
1129
|
+
return runDevDetach({ ...options, workspaceRoot });
|
|
1130
|
+
}
|
|
357
1131
|
|
|
358
1132
|
if (options.once) {
|
|
359
1133
|
const cycle = await runDevConsoleCycle({
|
|
@@ -368,22 +1142,59 @@ export async function runDevCommand(
|
|
|
368
1142
|
|
|
369
1143
|
const host = resolveDevHost(options.host);
|
|
370
1144
|
const port = resolveDevPort(options.port);
|
|
371
|
-
|
|
1145
|
+
if (!options.webOnly && !(await isPortAvailable(host, port))) {
|
|
1146
|
+
const failure = classifyDevStartFailure({
|
|
1147
|
+
rawMessage: `listen EADDRINUSE: address already in use ${host}:${port}`,
|
|
1148
|
+
host,
|
|
1149
|
+
port,
|
|
1150
|
+
webPort: options.webPort,
|
|
1151
|
+
db: options.db,
|
|
1152
|
+
});
|
|
1153
|
+
return writeDevStartFailure({
|
|
1154
|
+
workspaceRoot,
|
|
1155
|
+
startedAt,
|
|
1156
|
+
finishedAt: new Date(),
|
|
1157
|
+
options,
|
|
1158
|
+
host,
|
|
1159
|
+
port,
|
|
1160
|
+
failure,
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
const requestedWebPort = options.webPort ?? detectDefaultWebPort(workspaceRoot);
|
|
372
1164
|
const shouldStartWeb =
|
|
373
1165
|
options.webOnly === true ||
|
|
374
1166
|
(options.withWeb !== false && !options.apiOnly && hasWebApp(workspaceRoot));
|
|
1167
|
+
const webPortSelection = shouldStartWeb
|
|
1168
|
+
? await resolveAvailableWebPort({ host, preferredPort: requestedWebPort })
|
|
1169
|
+
: { port: requestedWebPort, autoPortSelected: false };
|
|
1170
|
+
const webPort = webPortSelection.port;
|
|
375
1171
|
const webUrl = shouldStartWeb ? `http://${host}:${webPort}` : undefined;
|
|
1172
|
+
let startupGenerated: DevStartupGeneratedEvidence | undefined;
|
|
376
1173
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
1174
|
+
if (!options.skipStartupConsole) {
|
|
1175
|
+
const startupCycle = await runDevConsoleCycle({
|
|
1176
|
+
workspaceRoot,
|
|
1177
|
+
mode: "startup",
|
|
1178
|
+
strictSecrets: false,
|
|
1179
|
+
includeImpact: true,
|
|
1180
|
+
...devStartupConsoleUrlOptions({ host, port, ...(webUrl ? { webUrl } : {}) }),
|
|
1181
|
+
});
|
|
1182
|
+
if (options.json) {
|
|
1183
|
+
process.stdout.write(formatDevConsoleJson(startupCycle));
|
|
1184
|
+
} else {
|
|
1185
|
+
process.stdout.write(formatDevConsoleHuman(startupCycle));
|
|
1186
|
+
}
|
|
1187
|
+
startupGenerated = generatedEvidenceFromCycle(startupCycle);
|
|
1188
|
+
if (startupCycle.exitCode !== 0) {
|
|
1189
|
+
return { exitCode: 1 };
|
|
1190
|
+
}
|
|
385
1191
|
} else {
|
|
386
|
-
|
|
1192
|
+
const generated = await ensureGeneratedForDev(workspaceRoot);
|
|
1193
|
+
startupGenerated = generatedEvidenceFromGenerateResult(generated);
|
|
1194
|
+
if (!generated.ok) {
|
|
1195
|
+
printDevGenerateFailure(generated, options.json);
|
|
1196
|
+
return { exitCode: 1 };
|
|
1197
|
+
}
|
|
387
1198
|
}
|
|
388
1199
|
|
|
389
1200
|
if (options.webOnly) {
|
|
@@ -392,6 +1203,8 @@ export async function runDevCommand(
|
|
|
392
1203
|
workspaceRoot,
|
|
393
1204
|
host,
|
|
394
1205
|
port: webPort,
|
|
1206
|
+
requestedPort: webPortSelection.requestedPort,
|
|
1207
|
+
autoPortSelected: webPortSelection.autoPortSelected,
|
|
395
1208
|
apiUrl,
|
|
396
1209
|
json: options.json,
|
|
397
1210
|
});
|
|
@@ -413,6 +1226,8 @@ export async function runDevCommand(
|
|
|
413
1226
|
web: {
|
|
414
1227
|
url: webHandle.url,
|
|
415
1228
|
port: webHandle.port,
|
|
1229
|
+
...(webHandle.requestedPort !== undefined ? { requestedPort: webHandle.requestedPort } : {}),
|
|
1230
|
+
autoPortSelected: webHandle.autoPortSelected,
|
|
416
1231
|
command: webHandle.command,
|
|
417
1232
|
},
|
|
418
1233
|
api: {
|
|
@@ -440,6 +1255,8 @@ export async function runDevCommand(
|
|
|
440
1255
|
return { web: webHandle, exitCode: 0 };
|
|
441
1256
|
}
|
|
442
1257
|
|
|
1258
|
+
const deltaRecorder = await createAmbientDeltaRecorder(workspaceRoot, "forge-dev", "forge dev");
|
|
1259
|
+
|
|
443
1260
|
let handle: DevServerHandle;
|
|
444
1261
|
try {
|
|
445
1262
|
handle = await startDevServer({
|
|
@@ -457,18 +1274,29 @@ export async function runDevCommand(
|
|
|
457
1274
|
mode: options.mode,
|
|
458
1275
|
allowDevAuth: options.allowDevAuth,
|
|
459
1276
|
webUrl,
|
|
1277
|
+
deltaRecorder,
|
|
460
1278
|
});
|
|
461
1279
|
} catch (error) {
|
|
462
|
-
|
|
1280
|
+
await deltaRecorder.close("forge dev failed to start");
|
|
1281
|
+
const finishedAt = new Date();
|
|
1282
|
+
const rawMessage =
|
|
463
1283
|
error instanceof Error ? error.message : "failed to start dev server";
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
}
|
|
471
|
-
return {
|
|
1284
|
+
const failure = classifyDevStartFailure({
|
|
1285
|
+
rawMessage,
|
|
1286
|
+
host,
|
|
1287
|
+
port,
|
|
1288
|
+
webPort: options.webPort,
|
|
1289
|
+
db: options.db,
|
|
1290
|
+
});
|
|
1291
|
+
return writeDevStartFailure({
|
|
1292
|
+
workspaceRoot,
|
|
1293
|
+
startedAt,
|
|
1294
|
+
finishedAt,
|
|
1295
|
+
options,
|
|
1296
|
+
host,
|
|
1297
|
+
port,
|
|
1298
|
+
failure,
|
|
1299
|
+
});
|
|
472
1300
|
}
|
|
473
1301
|
|
|
474
1302
|
const webHandle = !shouldStartWeb
|
|
@@ -477,6 +1305,8 @@ export async function runDevCommand(
|
|
|
477
1305
|
workspaceRoot,
|
|
478
1306
|
host,
|
|
479
1307
|
port: webPort,
|
|
1308
|
+
requestedPort: webPortSelection.requestedPort,
|
|
1309
|
+
autoPortSelected: webPortSelection.autoPortSelected,
|
|
480
1310
|
apiUrl: handle.url,
|
|
481
1311
|
json: options.json,
|
|
482
1312
|
});
|
|
@@ -486,6 +1316,7 @@ export async function runDevCommand(
|
|
|
486
1316
|
handle,
|
|
487
1317
|
web: webHandle,
|
|
488
1318
|
watch: options.watch,
|
|
1319
|
+
generated: startupGenerated,
|
|
489
1320
|
});
|
|
490
1321
|
if (options.json) {
|
|
491
1322
|
printStartupJson(startupSummary);
|
|
@@ -496,6 +1327,29 @@ export async function runDevCommand(
|
|
|
496
1327
|
openBrowser(webHandle?.url ?? handle.url);
|
|
497
1328
|
}
|
|
498
1329
|
|
|
1330
|
+
{
|
|
1331
|
+
const finishedAt = new Date();
|
|
1332
|
+
writeLastRunRecord(workspaceRoot, {
|
|
1333
|
+
schemaVersion: "0.1.0",
|
|
1334
|
+
command: "forge dev",
|
|
1335
|
+
ok: true,
|
|
1336
|
+
startedAt: startedAt.toISOString(),
|
|
1337
|
+
finishedAt: finishedAt.toISOString(),
|
|
1338
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
1339
|
+
nextActions: [
|
|
1340
|
+
webHandle?.url ?? handle.url,
|
|
1341
|
+
forgeCliCommandForWorkspace(workspaceRoot, "forge last --json"),
|
|
1342
|
+
],
|
|
1343
|
+
details: {
|
|
1344
|
+
db: handle.state.db.kind,
|
|
1345
|
+
apiUrl: handle.url,
|
|
1346
|
+
webUrl: webHandle?.url,
|
|
1347
|
+
watch: options.watch,
|
|
1348
|
+
worker: options.worker,
|
|
1349
|
+
},
|
|
1350
|
+
});
|
|
1351
|
+
}
|
|
1352
|
+
|
|
499
1353
|
let watchHandle: { stop: () => void } | null = null;
|
|
500
1354
|
let outboxWorkerHandle: { stop: () => void } | null = null;
|
|
501
1355
|
|
|
@@ -503,8 +1357,17 @@ export async function runDevCommand(
|
|
|
503
1357
|
outboxWorkerHandle = handle.outboxWorker;
|
|
504
1358
|
}
|
|
505
1359
|
|
|
1360
|
+
const devConsoleUrlOptions = (): { apiUrl: string; webUrl?: string } => ({
|
|
1361
|
+
apiUrl: handle.url,
|
|
1362
|
+
...(webHandle?.url ? { webUrl: webHandle.url } : {}),
|
|
1363
|
+
});
|
|
1364
|
+
|
|
506
1365
|
if (options.watch) {
|
|
507
|
-
watchHandle = startDevWatch(workspaceRoot, async (changedCount) => {
|
|
1366
|
+
watchHandle = startDevWatch(workspaceRoot, async (changedCount, changedPaths) => {
|
|
1367
|
+
for (const changedPath of changedPaths) {
|
|
1368
|
+
await deltaRecorder.recordFileChanged(changedPath);
|
|
1369
|
+
}
|
|
1370
|
+
resetCompileSessions();
|
|
508
1371
|
const result = await run({
|
|
509
1372
|
workspaceRoot,
|
|
510
1373
|
check: false,
|
|
@@ -514,39 +1377,91 @@ export async function runDevCommand(
|
|
|
514
1377
|
});
|
|
515
1378
|
|
|
516
1379
|
if (result.exitCode === 0) {
|
|
1380
|
+
const reload = await handle.reload("watch");
|
|
1381
|
+
const cycle = await runDevConsoleCycle({
|
|
1382
|
+
workspaceRoot,
|
|
1383
|
+
mode: "watch",
|
|
1384
|
+
strictSecrets: false,
|
|
1385
|
+
includeImpact: true,
|
|
1386
|
+
...devConsoleUrlOptions(),
|
|
1387
|
+
});
|
|
517
1388
|
if (!options.json) {
|
|
518
1389
|
process.stdout.write(
|
|
519
|
-
`[forge dev] regenerated (${changedCount} changed files)
|
|
1390
|
+
`[forge dev] regenerated (${changedCount} changed files), ` +
|
|
1391
|
+
`migrations ${reload.migrated ? "applied" : "skipped"}, ` +
|
|
1392
|
+
`runtime ${reload.ok ? "reloaded" : "reload failed"}\n`,
|
|
520
1393
|
);
|
|
1394
|
+
} else {
|
|
1395
|
+
process.stdout.write(`${JSON.stringify(buildDevWatchReloadEvent({
|
|
1396
|
+
changedCount,
|
|
1397
|
+
changedPaths,
|
|
1398
|
+
generated: {
|
|
1399
|
+
ok: true,
|
|
1400
|
+
changed: result.changed,
|
|
1401
|
+
unchanged: result.unchanged,
|
|
1402
|
+
diagnostics: [...result.errors, ...result.warnings],
|
|
1403
|
+
exitCode: 0,
|
|
1404
|
+
},
|
|
1405
|
+
reload,
|
|
1406
|
+
cycle,
|
|
1407
|
+
workspaceRoot,
|
|
1408
|
+
}))}\n`);
|
|
521
1409
|
}
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
1410
|
+
if (!reload.ok && !options.json) {
|
|
1411
|
+
for (const diagnostic of reload.diagnostics.filter((item) => item.severity === "error")) {
|
|
1412
|
+
console.error(`error ${diagnostic.code}: ${diagnostic.message}`);
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
if (options.json) {
|
|
1416
|
+
process.stdout.write(formatDevConsoleJson(cycle));
|
|
1417
|
+
} else {
|
|
1418
|
+
process.stdout.write(formatDevConsoleHuman(cycle));
|
|
525
1419
|
}
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
const cycle = await runDevConsoleCycle({
|
|
529
|
-
workspaceRoot,
|
|
530
|
-
mode: "watch",
|
|
531
|
-
strictSecrets: false,
|
|
532
|
-
includeImpact: true,
|
|
533
|
-
});
|
|
534
|
-
if (options.json) {
|
|
535
|
-
process.stdout.write(formatDevConsoleJson(cycle));
|
|
536
1420
|
} else {
|
|
537
|
-
|
|
1421
|
+
if (options.json) {
|
|
1422
|
+
process.stdout.write(`${JSON.stringify(buildDevWatchGenerateFailureEvent({
|
|
1423
|
+
changedCount,
|
|
1424
|
+
changedPaths,
|
|
1425
|
+
result: {
|
|
1426
|
+
ok: false,
|
|
1427
|
+
changed: result.changed,
|
|
1428
|
+
unchanged: result.unchanged,
|
|
1429
|
+
diagnostics: [...result.errors, ...result.warnings],
|
|
1430
|
+
exitCode: 1,
|
|
1431
|
+
},
|
|
1432
|
+
workspaceRoot,
|
|
1433
|
+
}))}\n`);
|
|
1434
|
+
} else {
|
|
1435
|
+
console.error(`[forge dev] regeneration failed after ${changedCount} changed files; runtime was not reloaded`);
|
|
1436
|
+
for (const diagnostic of [...result.errors, ...result.warnings]) {
|
|
1437
|
+
console.error(`${diagnostic.severity} ${diagnostic.code}: ${diagnostic.message}`);
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
const cycle = await runDevConsoleCycle({
|
|
1441
|
+
workspaceRoot,
|
|
1442
|
+
mode: "watch",
|
|
1443
|
+
strictSecrets: false,
|
|
1444
|
+
includeImpact: true,
|
|
1445
|
+
...devConsoleUrlOptions(),
|
|
1446
|
+
});
|
|
1447
|
+
if (options.json) {
|
|
1448
|
+
process.stdout.write(formatDevConsoleJson(cycle));
|
|
1449
|
+
} else {
|
|
1450
|
+
process.stdout.write(formatDevConsoleHuman(cycle));
|
|
1451
|
+
}
|
|
538
1452
|
}
|
|
539
1453
|
});
|
|
540
1454
|
}
|
|
541
1455
|
|
|
542
1456
|
await new Promise<void>((resolve) => {
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
1457
|
+
const shutdown = () => {
|
|
1458
|
+
watchHandle?.stop();
|
|
1459
|
+
outboxWorkerHandle?.stop();
|
|
1460
|
+
webHandle?.stop();
|
|
1461
|
+
handle.stop();
|
|
1462
|
+
void deltaRecorder.close("forge dev stopped");
|
|
1463
|
+
resolve();
|
|
1464
|
+
};
|
|
550
1465
|
|
|
551
1466
|
process.once("SIGINT", shutdown);
|
|
552
1467
|
process.once("SIGTERM", shutdown);
|