@treeseed/sdk 0.10.28 → 0.12.4
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/README.md +213 -7
- package/dist/agent-capacity.d.ts +916 -0
- package/dist/agent-capacity.js +1600 -0
- package/dist/agent-tools.d.ts +37 -0
- package/dist/agent-tools.js +301 -0
- package/dist/api/app.d.ts +1 -1
- package/dist/api/auth/d1-database.d.ts +2 -2
- package/dist/api/auth/d1-provider.d.ts +4 -4
- package/dist/api/auth/d1-store.d.ts +2 -2
- package/dist/api/auth/memory-provider.d.ts +1 -1
- package/dist/api/auth/tokens.d.ts +1 -1
- package/dist/api/config.d.ts +3 -1
- package/dist/api/config.js +34 -10
- package/dist/api/http.d.ts +2 -2
- package/dist/api/index.d.ts +10 -10
- package/dist/api/index.js +4 -2
- package/dist/api/operations-routes.d.ts +3 -3
- package/dist/api/operations.d.ts +1 -1
- package/dist/api/providers.d.ts +1 -1
- package/dist/api/providers.js +21 -4
- package/dist/api/railway.d.ts +3 -2
- package/dist/api/sdk-dispatch.d.ts +4 -4
- package/dist/api/sdk-routes.d.ts +2 -2
- package/dist/api/templates.d.ts +2 -2
- package/dist/api/types.d.ts +3 -2
- package/dist/capacity-provider.d.ts +118 -59
- package/dist/capacity-provider.js +235 -43
- package/dist/capacity.d.ts +2 -2
- package/dist/cli-tools.d.ts +1 -1
- package/dist/content-operations.d.ts +97 -0
- package/dist/content-operations.js +250 -0
- package/dist/content-store.d.ts +4 -4
- package/dist/control-plane-client.d.ts +13 -18
- package/dist/control-plane-client.js +8 -86
- package/dist/control-plane.d.ts +3 -2
- package/dist/control-plane.js +38 -13
- package/dist/copilot.d.ts +3 -0
- package/dist/copilot.js +16 -1
- package/dist/d1-http.d.ts +1 -1
- package/dist/d1-store.d.ts +32 -57
- package/dist/d1-store.js +115 -318
- package/dist/db/d1.d.ts +1 -232
- package/dist/db/index.d.ts +3 -3
- package/dist/db/market-schema.d.ts +50900 -11018
- package/dist/db/market-schema.js +2313 -467
- package/dist/db/node-sqlite.d.ts +1 -232
- package/dist/db/node-sqlite.js +7 -2
- package/dist/db/schema.d.ts +0 -462
- package/dist/db/schema.js +2 -22
- package/dist/dispatch.d.ts +1 -1
- package/dist/dispatch.js +0 -11
- package/dist/fixture-support.js +52 -4
- package/dist/governance.d.ts +133 -0
- package/dist/governance.js +285 -0
- package/dist/graph/build.d.ts +2 -2
- package/dist/graph/context-query-contracts.d.ts +1 -1
- package/dist/graph/dsl.d.ts +1 -1
- package/dist/graph/query.d.ts +4 -4
- package/dist/graph/ranking.d.ts +1 -1
- package/dist/graph/schema.d.ts +1 -1
- package/dist/graph.d.ts +11 -11
- package/dist/guarantees/index.d.ts +425 -0
- package/dist/guarantees/index.js +1146 -0
- package/dist/hosting/apps.d.ts +12 -0
- package/dist/hosting/apps.js +107 -0
- package/dist/hosting/builtins.d.ts +25 -0
- package/dist/hosting/builtins.js +602 -0
- package/dist/hosting/contracts.d.ts +208 -0
- package/dist/hosting/contracts.js +0 -0
- package/dist/hosting/graph.d.ts +195 -0
- package/dist/hosting/graph.js +1092 -0
- package/dist/hosting/index.d.ts +4 -0
- package/dist/hosting/index.js +4 -0
- package/dist/index.d.ts +88 -67
- package/dist/index.js +142 -14
- package/dist/local-dev/managed-dev.d.ts +85 -0
- package/dist/local-dev/managed-dev.js +368 -0
- package/dist/managed-dependencies.d.ts +9 -0
- package/dist/managed-dependencies.js +10 -9
- package/dist/market-client.d.ts +327 -9
- package/dist/market-client.js +464 -20
- package/dist/model-registry.d.ts +1 -1
- package/dist/model-registry.js +19 -19
- package/dist/operations/agent-tools.d.ts +2 -11
- package/dist/operations/agent-tools.js +2 -14
- package/dist/operations/providers/default.d.ts +1 -1
- package/dist/operations/providers/default.js +20 -37
- package/dist/operations/repository-operations.d.ts +26 -2
- package/dist/operations/repository-operations.js +159 -1
- package/dist/operations/runtime.d.ts +3 -3
- package/dist/operations/services/bootstrap-runner.d.ts +4 -2
- package/dist/operations/services/bootstrap-runner.js +22 -2
- package/dist/operations/services/build-warning-policy.d.ts +34 -0
- package/dist/operations/services/build-warning-policy.js +81 -82
- package/dist/operations/services/commit-message-provider.js +4 -4
- package/dist/operations/services/config-runtime.d.ts +83 -24
- package/dist/operations/services/config-runtime.js +449 -130
- package/dist/operations/services/deploy.d.ts +106 -624
- package/dist/operations/services/deploy.js +746 -263
- package/dist/operations/services/deployment-readiness.d.ts +30 -0
- package/dist/operations/services/deployment-readiness.js +206 -0
- package/dist/operations/services/git-remote-policy.js +12 -4
- package/dist/operations/services/git-runner.d.ts +66 -0
- package/dist/operations/services/git-runner.js +479 -0
- package/dist/operations/services/git-workflow.d.ts +45 -6
- package/dist/operations/services/git-workflow.js +251 -23
- package/dist/operations/services/github-actions-verification.d.ts +4 -1
- package/dist/operations/services/github-actions-verification.js +12 -3
- package/dist/operations/services/github-api.js +22 -6
- package/dist/operations/services/github-automation.d.ts +4 -4
- package/dist/operations/services/github-automation.js +19 -21
- package/dist/operations/services/github-credentials.d.ts +13 -0
- package/dist/operations/services/github-credentials.js +59 -0
- package/dist/operations/services/hosted-service-checks.d.ts +81 -0
- package/dist/operations/services/hosted-service-checks.js +478 -0
- package/dist/operations/services/hosting-audit.d.ts +2 -2
- package/dist/operations/services/hosting-audit.js +7 -7
- package/dist/operations/services/hub-launch.d.ts +1 -1
- package/dist/operations/services/hub-provider-launch.d.ts +5 -5
- package/dist/operations/services/hub-provider-launch.js +22 -17
- package/dist/operations/services/live-hosted-service-checks.d.ts +26 -0
- package/dist/operations/services/live-hosted-service-checks.js +463 -0
- package/dist/operations/services/local-dev.js +0 -1
- package/dist/operations/services/managed-host-security.d.ts +1 -1
- package/dist/operations/services/managed-host-security.js +1 -1
- package/dist/operations/services/managed-repositories.d.ts +37 -0
- package/dist/operations/services/managed-repositories.js +177 -0
- package/dist/operations/services/operations-runner-smoke.d.ts +31 -0
- package/dist/operations/services/operations-runner-smoke.js +198 -0
- package/dist/operations/services/package-adapters.d.ts +170 -0
- package/dist/operations/services/package-adapters.js +859 -0
- package/dist/operations/services/package-reference-policy.d.ts +13 -88
- package/dist/operations/services/package-reference-policy.js +96 -215
- package/dist/operations/services/project-host-operations.d.ts +4 -4
- package/dist/operations/services/project-platform.d.ts +57 -173
- package/dist/operations/services/project-platform.js +80 -111
- package/dist/operations/services/project-web-monitor.d.ts +2 -2
- package/dist/operations/services/project-web-monitor.js +122 -5
- package/dist/operations/services/railway-api.d.ts +117 -5
- package/dist/operations/services/railway-api.js +915 -93
- package/dist/operations/services/railway-deploy.d.ts +30 -112
- package/dist/operations/services/railway-deploy.js +521 -961
- package/dist/operations/services/release-history.js +3 -4
- package/dist/operations/services/release-proof-ledger.d.ts +13 -0
- package/dist/operations/services/release-proof-ledger.js +81 -0
- package/dist/operations/services/release-proof-planner.d.ts +48 -0
- package/dist/operations/services/release-proof-planner.js +195 -0
- package/dist/operations/services/release-proof-runner.d.ts +16 -0
- package/dist/operations/services/release-proof-runner.js +169 -0
- package/dist/operations/services/release-proof.d.ts +81 -0
- package/dist/operations/services/release-proof.js +79 -0
- package/dist/operations/services/repository-save-orchestrator.d.ts +8 -7
- package/dist/operations/services/repository-save-orchestrator.js +604 -212
- package/dist/operations/services/runtime-paths.d.ts +1 -3
- package/dist/operations/services/runtime-paths.js +0 -4
- package/dist/operations/services/runtime-tools.d.ts +1 -191
- package/dist/operations/services/runtime-tools.js +4 -443
- package/dist/operations/services/save-deploy-preflight.js +9 -1
- package/dist/operations/services/template-host-bindings.d.ts +2 -2
- package/dist/operations/services/template-host-bindings.js +3 -2
- package/dist/operations/services/template-registry.d.ts +6 -6
- package/dist/operations/services/template-registry.js +33 -14
- package/dist/operations/services/template-secret-sync.d.ts +3 -3
- package/dist/operations/services/verification-cache.d.ts +25 -0
- package/dist/operations/services/verification-cache.js +71 -0
- package/dist/operations/services/workflow-timing.d.ts +25 -0
- package/dist/operations/services/workflow-timing.js +79 -0
- package/dist/operations/services/workspace-dependency-mode.js +50 -5
- package/dist/operations/services/workspace-preflight.js +8 -8
- package/dist/operations/services/workspace-save-script.d.ts +4 -4
- package/dist/operations/services/workspace-save.d.ts +1 -0
- package/dist/operations/services/workspace-save.js +42 -19
- package/dist/operations/services/workspace-tools.js +42 -7
- package/dist/operations-registry.d.ts +1 -1
- package/dist/operations-registry.js +5 -6
- package/dist/operations-types.d.ts +1 -1
- package/dist/operations.d.ts +17 -11
- package/dist/operations.js +57 -3
- package/dist/platform/book-export.js +2 -0
- package/dist/platform/books-data.d.ts +1 -1
- package/dist/platform/config.d.ts +27 -0
- package/dist/platform/config.js +100 -0
- package/dist/platform/content-runtime-source.d.ts +54 -0
- package/dist/platform/content-runtime-source.js +169 -0
- package/dist/platform/contracts.d.ts +38 -2
- package/dist/platform/deploy-config.d.ts +1 -1
- package/dist/platform/deploy-config.js +80 -9
- package/dist/platform/deploy-runtime.d.ts +1 -1
- package/dist/platform/desired-state.d.ts +63 -0
- package/dist/platform/desired-state.js +1021 -0
- package/dist/platform/env.yaml +568 -45
- package/dist/platform/environment.d.ts +3 -2
- package/dist/platform/environment.js +122 -21
- package/dist/platform/local-content-materialization.d.ts +9 -0
- package/dist/platform/local-content-materialization.js +212 -0
- package/dist/platform/plugin.d.ts +13 -4
- package/dist/platform/plugins/runtime.d.ts +3 -3
- package/dist/platform/plugins/runtime.js +62 -1
- package/dist/platform/plugins.d.ts +6 -6
- package/dist/platform/published-content-pipeline.d.ts +3 -3
- package/dist/platform/published-content-pipeline.js +5 -1
- package/dist/platform/published-content.d.ts +4 -3
- package/dist/platform/site-config-schema.d.ts +1 -0
- package/dist/platform/site-config-schema.js +4 -1
- package/dist/platform/tenant/runtime-config.d.ts +152 -2
- package/dist/platform/tenant-config.d.ts +1 -1
- package/dist/platform/utils/site-config-schema.d.ts +154 -0
- package/dist/platform/utils/site-config-schema.js +400 -461
- package/dist/platform-operation-store.d.ts +1 -1
- package/dist/platform-operation-store.js +2 -2
- package/dist/platform-operations.js +1 -1
- package/dist/plugin-default.d.ts +1 -1
- package/dist/plugin-default.js +27 -12
- package/dist/project-import.d.ts +91 -0
- package/dist/project-import.js +257 -0
- package/dist/project-workflow.d.ts +1 -1
- package/dist/reconcile/bootstrap-systems.d.ts +2 -2
- package/dist/reconcile/bootstrap-systems.js +5 -5
- package/dist/reconcile/builtin-adapters.d.ts +7 -1
- package/dist/reconcile/builtin-adapters.js +3190 -429
- package/dist/reconcile/contracts.d.ts +23 -6
- package/dist/reconcile/desired-state.d.ts +2 -192
- package/dist/reconcile/desired-state.js +29 -38
- package/dist/reconcile/engine.d.ts +61 -395
- package/dist/reconcile/engine.js +229 -44
- package/dist/reconcile/index.d.ts +10 -8
- package/dist/reconcile/index.js +2 -0
- package/dist/reconcile/live-acceptance.d.ts +100 -0
- package/dist/reconcile/live-acceptance.js +2027 -0
- package/dist/reconcile/platform.d.ts +104 -0
- package/dist/reconcile/platform.js +100 -0
- package/dist/reconcile/providers/docker-private.d.ts +54 -0
- package/dist/reconcile/providers/docker-private.js +101 -0
- package/dist/reconcile/providers/github-private.d.ts +65 -0
- package/dist/reconcile/providers/github-private.js +119 -0
- package/dist/reconcile/providers/local-private.d.ts +28 -0
- package/dist/reconcile/providers/local-private.js +75 -0
- package/dist/reconcile/providers/railway-iac.d.ts +66 -0
- package/dist/reconcile/providers/railway-iac.js +275 -0
- package/dist/reconcile/providers/release-private.d.ts +92 -0
- package/dist/reconcile/providers/release-private.js +152 -0
- package/dist/reconcile/registry.d.ts +2 -2
- package/dist/reconcile/registry.js +16 -1
- package/dist/reconcile/state.d.ts +1 -1
- package/dist/reconcile/state.js +7 -44
- package/dist/reconcile/units.d.ts +1 -1
- package/dist/reconcile/units.js +9 -3
- package/dist/remote.d.ts +6 -6
- package/dist/scenes/artifacts.d.ts +16 -0
- package/dist/scenes/artifacts.js +61 -0
- package/dist/scenes/auth.d.ts +2 -0
- package/dist/scenes/auth.js +28 -0
- package/dist/scenes/base-url.d.ts +12 -0
- package/dist/scenes/base-url.js +123 -0
- package/dist/scenes/builtin-plugins.d.ts +2 -0
- package/dist/scenes/builtin-plugins.js +447 -0
- package/dist/scenes/checkpoints.d.ts +14 -0
- package/dist/scenes/checkpoints.js +33 -0
- package/dist/scenes/device-matrix.d.ts +2 -0
- package/dist/scenes/device-matrix.js +97 -0
- package/dist/scenes/devices.d.ts +11 -0
- package/dist/scenes/devices.js +22 -0
- package/dist/scenes/diagnostics.d.ts +5 -0
- package/dist/scenes/diagnostics.js +22 -0
- package/dist/scenes/diagram-providers.d.ts +2 -0
- package/dist/scenes/diagram-providers.js +181 -0
- package/dist/scenes/diagram-validation.d.ts +22 -0
- package/dist/scenes/diagram-validation.js +74 -0
- package/dist/scenes/environment.d.ts +2 -0
- package/dist/scenes/environment.js +65 -0
- package/dist/scenes/evidence-bundle.d.ts +5 -0
- package/dist/scenes/evidence-bundle.js +42 -0
- package/dist/scenes/evidence-report.d.ts +10 -0
- package/dist/scenes/evidence-report.js +73 -0
- package/dist/scenes/evidence.d.ts +16 -0
- package/dist/scenes/evidence.js +277 -0
- package/dist/scenes/index.d.ts +48 -0
- package/dist/scenes/index.js +312 -0
- package/dist/scenes/inspect.d.ts +9 -0
- package/dist/scenes/inspect.js +64 -0
- package/dist/scenes/loader.d.ts +3 -0
- package/dist/scenes/loader.js +38 -0
- package/dist/scenes/logs.d.ts +2 -0
- package/dist/scenes/logs.js +47 -0
- package/dist/scenes/operations.d.ts +3 -0
- package/dist/scenes/operations.js +128 -0
- package/dist/scenes/phase0.d.ts +9 -0
- package/dist/scenes/phase0.js +637 -0
- package/dist/scenes/planner.d.ts +12 -0
- package/dist/scenes/planner.js +166 -0
- package/dist/scenes/playwright-adapter.d.ts +2 -0
- package/dist/scenes/playwright-adapter.js +63 -0
- package/dist/scenes/plugins.d.ts +5 -0
- package/dist/scenes/plugins.js +115 -0
- package/dist/scenes/progress.d.ts +17 -0
- package/dist/scenes/progress.js +24 -0
- package/dist/scenes/publish-destinations.d.ts +12 -0
- package/dist/scenes/publish-destinations.js +99 -0
- package/dist/scenes/publish-plan-report.d.ts +10 -0
- package/dist/scenes/publish-plan-report.js +70 -0
- package/dist/scenes/publish-plan.d.ts +15 -0
- package/dist/scenes/publish-plan.js +248 -0
- package/dist/scenes/publish-redaction.d.ts +16 -0
- package/dist/scenes/publish-redaction.js +148 -0
- package/dist/scenes/publish-release-record.d.ts +13 -0
- package/dist/scenes/publish-release-record.js +34 -0
- package/dist/scenes/publish-report.d.ts +10 -0
- package/dist/scenes/publish-report.js +67 -0
- package/dist/scenes/publish.d.ts +16 -0
- package/dist/scenes/publish.js +298 -0
- package/dist/scenes/registry.d.ts +12 -0
- package/dist/scenes/registry.js +65 -0
- package/dist/scenes/remotion-adapter.d.ts +3 -0
- package/dist/scenes/remotion-adapter.js +94 -0
- package/dist/scenes/remotion-composition-registry.d.ts +2 -0
- package/dist/scenes/remotion-composition-registry.js +12 -0
- package/dist/scenes/remotion-compositions.d.ts +15 -0
- package/dist/scenes/remotion-compositions.js +464 -0
- package/dist/scenes/remotion-diagrams.d.ts +9 -0
- package/dist/scenes/remotion-diagrams.js +188 -0
- package/dist/scenes/remotion-input.d.ts +13 -0
- package/dist/scenes/remotion-input.js +291 -0
- package/dist/scenes/remotion-visuals.d.ts +29 -0
- package/dist/scenes/remotion-visuals.js +198 -0
- package/dist/scenes/render-media-assets.d.ts +9 -0
- package/dist/scenes/render-media-assets.js +117 -0
- package/dist/scenes/render-report.d.ts +10 -0
- package/dist/scenes/render-report.js +27 -0
- package/dist/scenes/render.d.ts +2 -0
- package/dist/scenes/render.js +218 -0
- package/dist/scenes/reporter.d.ts +3 -0
- package/dist/scenes/reporter.js +101 -0
- package/dist/scenes/resume.d.ts +2 -0
- package/dist/scenes/resume.js +79 -0
- package/dist/scenes/runner.d.ts +2 -0
- package/dist/scenes/runner.js +674 -0
- package/dist/scenes/schema.d.ts +6 -0
- package/dist/scenes/schema.js +994 -0
- package/dist/scenes/seed.d.ts +2 -0
- package/dist/scenes/seed.js +83 -0
- package/dist/scenes/segments.d.ts +15 -0
- package/dist/scenes/segments.js +79 -0
- package/dist/scenes/selectors.d.ts +3 -0
- package/dist/scenes/selectors.js +21 -0
- package/dist/scenes/timeline.d.ts +11 -0
- package/dist/scenes/timeline.js +26 -0
- package/dist/scenes/timeouts.d.ts +6 -0
- package/dist/scenes/timeouts.js +17 -0
- package/dist/scenes/training-captions.d.ts +14 -0
- package/dist/scenes/training-captions.js +98 -0
- package/dist/scenes/training-report.d.ts +10 -0
- package/dist/scenes/training-report.js +78 -0
- package/dist/scenes/training-transcript.d.ts +24 -0
- package/dist/scenes/training-transcript.js +204 -0
- package/dist/scenes/training.d.ts +10 -0
- package/dist/scenes/training.js +134 -0
- package/dist/scenes/types.d.ts +1872 -0
- package/dist/scenes/types.js +8 -0
- package/dist/scenes/visual-audit-contact-sheets.d.ts +6 -0
- package/dist/scenes/visual-audit-contact-sheets.js +119 -0
- package/dist/scenes/visual-audit-fixtures.d.ts +25 -0
- package/dist/scenes/visual-audit-fixtures.js +210 -0
- package/dist/scenes/visual-audit-report.d.ts +11 -0
- package/dist/scenes/visual-audit-report.js +85 -0
- package/dist/scenes/visual-audit-review.d.ts +23 -0
- package/dist/scenes/visual-audit-review.js +829 -0
- package/dist/scenes/visual-audit-routes.d.ts +13 -0
- package/dist/scenes/visual-audit-routes.js +308 -0
- package/dist/scenes/visual-audit.d.ts +3 -0
- package/dist/scenes/visual-audit.js +596 -0
- package/dist/scripts/build-dist.js +154 -40
- package/dist/scripts/check-build-warnings.js +42 -53
- package/dist/scripts/cleanup-markdown.js +1 -1
- package/dist/scripts/cli-test-fixtures.js +24 -28
- package/dist/scripts/deployment-readiness.js +20 -0
- package/dist/scripts/generate-treedx-openapi-types.js +186 -0
- package/dist/scripts/migrate-journey-csv-to-guarantees.js +140 -0
- package/dist/scripts/operations-runner-smoke.js +16 -0
- package/dist/scripts/package-tools.js +12 -1
- package/dist/scripts/prepare.js +3 -7
- package/dist/scripts/release-verify.js +6 -6
- package/dist/scripts/run-fixture-astro-command.js +1 -1
- package/dist/scripts/template-catalog.test.js +68 -82
- package/dist/scripts/tenant-dev.js +0 -10
- package/dist/scripts/tenant-workflow-action.js +10 -1
- package/dist/scripts/test-cloudflare-local.js +1 -1
- package/dist/scripts/test-scaffold.js +0 -1
- package/dist/scripts/test-smoke.js +3 -3
- package/dist/scripts/treeseed-build-dist.js +4 -7
- package/dist/scripts/treeseed-release-verify.js +1 -1
- package/dist/scripts/verify-driver.js +30 -21
- package/dist/scripts/workflow-commands.test.js +18 -27
- package/dist/scripts/workspace-command-e2e.js +1 -1
- package/dist/scripts/workspace-lint.js +6 -0
- package/dist/scripts/workspace-release-test.js +90 -1
- package/dist/scripts/workspace-test.js +6 -0
- package/dist/sdk-dispatch.d.ts +1 -1
- package/dist/sdk-dispatch.js +0 -16
- package/dist/sdk-fields.d.ts +1 -1
- package/dist/sdk-filters.d.ts +1 -1
- package/dist/sdk-types.d.ts +1352 -116
- package/dist/sdk-types.js +413 -2
- package/dist/sdk.d.ts +106 -100
- package/dist/sdk.js +211 -51
- package/dist/secrets-capability.d.ts +421 -0
- package/dist/secrets-capability.js +530 -0
- package/dist/seeds/normalize.js +1 -0
- package/dist/seeds/planner.js +38 -0
- package/dist/seeds/schema.js +293 -4
- package/dist/seeds/types.d.ts +60 -0
- package/dist/seeds/types.js +9 -1
- package/dist/service-credentials.d.ts +54 -0
- package/dist/service-credentials.js +112 -0
- package/dist/stores/cursor-store.d.ts +2 -2
- package/dist/stores/envelopes.d.ts +1 -1
- package/dist/stores/helpers.d.ts +1 -1
- package/dist/stores/lease-store.d.ts +2 -2
- package/dist/stores/message-store.d.ts +2 -2
- package/dist/stores/operational-store.d.ts +3 -26
- package/dist/stores/operational-store.js +0 -225
- package/dist/stores/project-workflow-store.d.ts +2 -2
- package/dist/stores/run-store.d.ts +2 -3
- package/dist/stores/run-store.js +0 -98
- package/dist/stores/subscription-store.d.ts +2 -3
- package/dist/stores/subscription-store.js +0 -100
- package/dist/template-catalog.d.ts +1 -1
- package/dist/template-launch-requirements.d.ts +2 -2
- package/dist/template-launch-requirements.js +9 -0
- package/dist/template-launch-ui.d.ts +2 -2
- package/dist/treedx/adapters.d.ts +6 -0
- package/dist/treedx/adapters.js +36 -0
- package/dist/treedx/client.d.ts +222 -0
- package/dist/treedx/client.js +871 -0
- package/dist/treedx/errors.d.ts +13 -0
- package/dist/treedx/errors.js +17 -0
- package/dist/treedx/federated-client.d.ts +27 -0
- package/dist/treedx/federated-client.js +158 -0
- package/dist/treedx/generated/openapi-types.d.ts +3558 -0
- package/dist/treedx/generated/openapi-types.js +0 -0
- package/dist/treedx/graph-adapter.d.ts +33 -0
- package/dist/treedx/graph-adapter.js +156 -0
- package/dist/treedx/index.d.ts +14 -0
- package/dist/treedx/index.js +48 -0
- package/dist/treedx/market-integration.d.ts +27 -0
- package/dist/treedx/market-integration.js +131 -0
- package/dist/treedx/ports.d.ts +166 -0
- package/dist/treedx/ports.js +231 -0
- package/dist/treedx/query-adapter.d.ts +19 -0
- package/dist/treedx/query-adapter.js +62 -0
- package/dist/treedx/registry-client.d.ts +11 -0
- package/dist/treedx/registry-client.js +19 -0
- package/dist/treedx/repository-adapter.d.ts +45 -0
- package/dist/treedx/repository-adapter.js +319 -0
- package/dist/treedx/sdk-integration.d.ts +27 -0
- package/dist/treedx/sdk-integration.js +63 -0
- package/dist/treedx/types.d.ts +1084 -0
- package/dist/treedx/types.js +8 -0
- package/dist/treedx/workspace-adapter.d.ts +27 -0
- package/dist/treedx/workspace-adapter.js +65 -0
- package/dist/treedx-backends.d.ts +220 -0
- package/dist/treedx-backends.js +634 -0
- package/dist/treedx-client.d.ts +89 -0
- package/dist/treedx-client.js +184 -0
- package/dist/treeseed/services/compose.yml +2 -3
- package/dist/treeseed/template-catalog/catalog.fixture.json +27 -27
- package/dist/types/agents.d.ts +256 -0
- package/dist/types/agents.js +42 -1
- package/dist/verification.d.ts +1 -0
- package/dist/verification.js +143 -14
- package/dist/workflow/operations.d.ts +580 -523
- package/dist/workflow/operations.js +2084 -1279
- package/dist/workflow/policy.js +19 -7
- package/dist/workflow/runs.d.ts +9 -3
- package/dist/workflow/runs.js +66 -26
- package/dist/workflow/session.d.ts +4 -1
- package/dist/workflow/session.js +50 -10
- package/dist/workflow/worktrees.d.ts +6 -2
- package/dist/workflow/worktrees.js +98 -15
- package/dist/workflow-state.d.ts +17 -4
- package/dist/workflow-state.js +68 -41
- package/dist/workflow-support.d.ts +26 -17
- package/dist/workflow-support.js +84 -24
- package/dist/workflow.d.ts +51 -16
- package/dist/workflow.js +19 -7
- package/drizzle/d1/0000_treeseed_d1.sql +0 -18
- package/drizzle/market/0000_market_control_plane.sql +1793 -1499
- package/drizzle/market/0004_treedx_market_integration.sql +99 -0
- package/drizzle/market/0005_agent_capacity_coordination.sql +145 -0
- package/drizzle/market/0006_provider_assignment_lifecycle.sql +9 -0
- package/drizzle/market/0007_agent_architecture_gap_closure.sql +157 -0
- package/drizzle/market/0008_agent_capacity_operational_closure.sql +27 -0
- package/drizzle/market/0009_agent_capacity_runtime_hardening.sql +29 -0
- package/drizzle/market/0010_secrets_capability_persistence.sql +130 -0
- package/drizzle/market/0011_github_app_adapter.sql +47 -0
- package/drizzle/market/0012_workday_test_runs.sql +44 -0
- package/drizzle/market/0013_runtime_task_lifecycle.sql +39 -0
- package/drizzle/market/0014_governance_pipeline.sql +189 -0
- package/package.json +106 -18
- package/scripts/{verify-driver.mjs → verify-driver.ts} +19 -4
- package/templates/github/deploy-web.workflow.yml +49 -12
- package/dist/operations/services/mailpit-runtime.d.ts +0 -20
- package/dist/operations/services/mailpit-runtime.js +0 -67
- package/dist/operations/services/release-candidate.d.ts +0 -41
- package/dist/operations/services/release-candidate.js +0 -616
- package/dist/scripts/ensure-mailpit.js +0 -48
- package/dist/scripts/logs-mailpit.js +0 -2
- package/dist/scripts/run-ts.js +0 -45
- package/dist/scripts/stop-mailpit.js +0 -5
- package/dist/scripts/treeseed-run-ts.js +0 -45
|
@@ -1,16 +1,19 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { relative, resolve } from "node:path";
|
|
3
|
+
import { parse as parseYaml } from "yaml";
|
|
4
4
|
import { loadCliDeployConfig } from "./runtime-tools.js";
|
|
5
|
+
import { resolveTreeseedMachineEnvironmentValues } from "./config-runtime.js";
|
|
5
6
|
import { createPersistentDeployTarget, resolveTreeseedResourceIdentity } from "./deploy.js";
|
|
7
|
+
import { classifyTreeseedGitMode, runTreeseedGitText } from "./git-runner.js";
|
|
8
|
+
import { discoverTreeseedApplications } from "../../hosting/apps.js";
|
|
6
9
|
import { runPrefixedCommand, sleep } from "./bootstrap-runner.js";
|
|
7
|
-
import { resolveTreeseedToolCommand } from "../../managed-dependencies.js";
|
|
8
10
|
import {
|
|
9
11
|
ensureRailwayEnvironment,
|
|
10
12
|
ensureRailwayProject,
|
|
11
13
|
ensureRailwayService,
|
|
12
14
|
ensureRailwayServiceInstanceConfiguration,
|
|
13
15
|
ensureRailwayServiceVolume,
|
|
16
|
+
deployRailwayServiceInstance,
|
|
14
17
|
getRailwayServiceInstance,
|
|
15
18
|
listRailwayEnvironments,
|
|
16
19
|
listRailwayProjects,
|
|
@@ -20,7 +23,6 @@ import {
|
|
|
20
23
|
railwayGraphqlRequest,
|
|
21
24
|
resolveRailwayApiToken,
|
|
22
25
|
resolveRailwayApiUrl,
|
|
23
|
-
resolveRailwayWorkspace,
|
|
24
26
|
resolveRailwayWorkspaceContext,
|
|
25
27
|
upsertRailwayVariables
|
|
26
28
|
} from "./railway-api.js";
|
|
@@ -31,37 +33,87 @@ function normalizeScope(scope) {
|
|
|
31
33
|
function resolveRailwayEnvironmentForScope(scope, configuredEnvironment) {
|
|
32
34
|
return normalizeRailwayEnvironmentName(configuredEnvironment || normalizeScope(scope));
|
|
33
35
|
}
|
|
34
|
-
const RAILWAY_SERVICE_KEYS = ["api", "
|
|
36
|
+
const RAILWAY_SERVICE_KEYS = ["api", "operationsRunner", "capacityProviderManager", "capacityProviderRunner"];
|
|
35
37
|
const HOSTED_PROJECT_SERVICE_KEYS = ["api"];
|
|
36
38
|
const WORKER_RUNNER_BOOTSTRAP_INDEX = 1;
|
|
37
39
|
const WORKER_RUNNER_VOLUME_MOUNT_PATH = "/data";
|
|
38
|
-
const
|
|
40
|
+
const OPERATIONS_RUNNER_BOOTSTRAP_COUNT = 2;
|
|
41
|
+
const PUBLIC_TREEDX_NODE_SERVICE_KEY_PREFIX = "public-treedx-node-";
|
|
42
|
+
function isTreeseedOperationsRunnerResourceName(value) {
|
|
43
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
44
|
+
if (!normalized) {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
if (normalized.startsWith("market-ops")) {
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
return normalized.includes("operations-runner");
|
|
51
|
+
}
|
|
52
|
+
function findStaleTreeseedOperationsRunnerResources(resources, desiredNames) {
|
|
53
|
+
const desired = new Set([...desiredNames].map((value) => String(value ?? "").trim()).filter(Boolean));
|
|
54
|
+
return resources.filter((resource) => {
|
|
55
|
+
const name = String(resource?.name ?? "").trim();
|
|
56
|
+
return name && isTreeseedOperationsRunnerResourceName(name) && !desired.has(name);
|
|
57
|
+
});
|
|
58
|
+
}
|
|
39
59
|
function shouldManageRailwaySchedules(scope, phase = "deploy") {
|
|
40
60
|
const environment = normalizeRailwayEnvironmentName(scope);
|
|
41
61
|
return phase === "deploy" && (environment === "staging" || environment === "production");
|
|
42
62
|
}
|
|
43
63
|
function railwayServiceNameSuffix(serviceKey) {
|
|
44
|
-
return serviceKey === "workdayManager" ? "workday-manager" : serviceKey === "workerRunner" ? "worker-runner" : serviceKey === "
|
|
64
|
+
return serviceKey === "capacityProviderManager" ? "capacity-provider-manager" : serviceKey === "capacityProviderRunner" ? "capacity-provider-runner" : serviceKey === "workdayManager" ? "workday-manager" : serviceKey === "workerRunner" ? "worker-runner" : serviceKey === "operationsRunner" ? "operations-runner" : serviceKey;
|
|
45
65
|
}
|
|
46
66
|
function deriveRailwayWorkerRunnerServiceName(projectSlug, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
|
|
47
67
|
const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
|
|
48
68
|
return `${projectSlug}-worker-runner-${String(normalizedIndex).padStart(2, "0")}`;
|
|
49
69
|
}
|
|
50
|
-
function
|
|
70
|
+
function deriveRailwayOperationsRunnerServiceName(baseServiceName, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
|
|
51
71
|
const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
|
|
52
|
-
const base = String(baseServiceName ?? "").trim().replace(/-\d+$/u, "") || "treeseed-
|
|
72
|
+
const base = String(baseServiceName ?? "").trim().replace(/-\d+$/u, "").replace(/-\d{2}$/u, "") || "treeseed-api-operations-runner";
|
|
53
73
|
return `${base}-${String(normalizedIndex).padStart(2, "0")}`;
|
|
54
74
|
}
|
|
75
|
+
function railwayImageRefEnvForService(serviceKey) {
|
|
76
|
+
if (serviceKey === "api") return "TREESEED_API_IMAGE_REF";
|
|
77
|
+
if (serviceKey === "operationsRunner") return "TREESEED_OPERATIONS_RUNNER_IMAGE_REF";
|
|
78
|
+
if (serviceKey === "capacityProviderManager") return "TREESEED_AGENT_MANAGER_IMAGE_REF";
|
|
79
|
+
if (serviceKey === "capacityProviderRunner") return "TREESEED_AGENT_RUNNER_IMAGE_REF";
|
|
80
|
+
if (isPublicTreeDxNodeServiceKey(serviceKey)) return "TREESEED_PUBLIC_TREEDX_IMAGE_REF";
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
function defaultRailwayImageRef(serviceKey, scope = "staging", env = process.env) {
|
|
84
|
+
if (serviceKey === "api") {
|
|
85
|
+
return envValue("TREESEED_API_IMAGE_REF", env) || null;
|
|
86
|
+
}
|
|
87
|
+
if (serviceKey === "operationsRunner") {
|
|
88
|
+
return envValue("TREESEED_OPERATIONS_RUNNER_IMAGE_REF", env) || null;
|
|
89
|
+
}
|
|
90
|
+
if (serviceKey === "capacityProviderManager") {
|
|
91
|
+
return envValue("TREESEED_AGENT_MANAGER_IMAGE_REF", env) || null;
|
|
92
|
+
}
|
|
93
|
+
if (serviceKey === "capacityProviderRunner") {
|
|
94
|
+
return envValue("TREESEED_AGENT_RUNNER_IMAGE_REF", env) || null;
|
|
95
|
+
}
|
|
96
|
+
if (isPublicTreeDxNodeServiceKey(serviceKey)) {
|
|
97
|
+
return envValue("TREESEED_PUBLIC_TREEDX_IMAGE_REF", env) || null;
|
|
98
|
+
}
|
|
99
|
+
return null;
|
|
100
|
+
}
|
|
101
|
+
function isPublicTreeDxNodeServiceKey(serviceKey) {
|
|
102
|
+
return String(serviceKey ?? "").startsWith(PUBLIC_TREEDX_NODE_SERVICE_KEY_PREFIX);
|
|
103
|
+
}
|
|
55
104
|
function deriveRailwayWorkerRunnerVolumeName(serviceName, environmentName = "") {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
105
|
+
return `${serviceName}-volume`;
|
|
106
|
+
}
|
|
107
|
+
function deriveRailwayOperationsRunnerVolumeName(serviceName, environmentName = "") {
|
|
108
|
+
return `${serviceName}-volume`;
|
|
59
109
|
}
|
|
60
|
-
function
|
|
61
|
-
const
|
|
62
|
-
const
|
|
63
|
-
|
|
64
|
-
|
|
110
|
+
function deriveRailwayCapacityProviderRunnerServiceName(baseServiceName, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
|
|
111
|
+
const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
|
|
112
|
+
const base = String(baseServiceName ?? "").trim().replace(/-\d+$/u, "").replace(/-\d{2}$/u, "") || "treeseed-capacity-provider-runner";
|
|
113
|
+
return `${base}-${String(normalizedIndex).padStart(2, "0")}`;
|
|
114
|
+
}
|
|
115
|
+
function deriveRailwayCapacityProviderRunnerVolumeName(serviceName, environmentName = "") {
|
|
116
|
+
return `${serviceName}-volume`;
|
|
65
117
|
}
|
|
66
118
|
function railwayServiceRuntimeStartCommand(service) {
|
|
67
119
|
return service.startCommand;
|
|
@@ -75,8 +127,8 @@ function normalizeScheduleExpressions(value) {
|
|
|
75
127
|
}
|
|
76
128
|
return [];
|
|
77
129
|
}
|
|
78
|
-
function envValue(name) {
|
|
79
|
-
const value =
|
|
130
|
+
function envValue(name, env = process.env) {
|
|
131
|
+
const value = env?.[name];
|
|
80
132
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
81
133
|
}
|
|
82
134
|
function relativeRailwayRootDir(tenantRoot, serviceRoot) {
|
|
@@ -87,6 +139,10 @@ function configuredEnvValue(env, name) {
|
|
|
87
139
|
const value = env?.[name];
|
|
88
140
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
89
141
|
}
|
|
142
|
+
function railwayDeployTransport(env) {
|
|
143
|
+
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_TRANSPORT").toLowerCase();
|
|
144
|
+
return configured === "cli-fallback" ? "cli-fallback" : "api";
|
|
145
|
+
}
|
|
90
146
|
async function timedRailwayPhase(timings, name, run, metadata) {
|
|
91
147
|
const startMs = performance.now();
|
|
92
148
|
try {
|
|
@@ -149,6 +205,10 @@ function railwayStatusDeploymentSettled(status) {
|
|
|
149
205
|
const normalized = String(status ?? "").trim().toUpperCase();
|
|
150
206
|
return normalized === "SUCCESS" || normalized === "SLEEPING";
|
|
151
207
|
}
|
|
208
|
+
function railwayStatusDeploymentTerminalFailure(status) {
|
|
209
|
+
const normalized = String(status ?? "").trim().toUpperCase();
|
|
210
|
+
return ["FAILED", "CRASHED", "REMOVED"].includes(normalized);
|
|
211
|
+
}
|
|
152
212
|
function formatRailwayDeploymentStatusSummary(scope, checks) {
|
|
153
213
|
const aliases = {
|
|
154
214
|
api: "api",
|
|
@@ -219,12 +279,14 @@ function collectRailwayDeploymentStatusChecks(statusPayload, scope, services) {
|
|
|
219
279
|
const status = String(deployment?.status ?? "").trim().toUpperCase();
|
|
220
280
|
const instanceStatuses = Array.isArray(deployment?.instances) ? deployment.instances.map((entry) => String(entry?.status ?? "").trim()).filter(Boolean) : [];
|
|
221
281
|
const ok = railwayStatusDeploymentSettled(status);
|
|
282
|
+
const terminalFailure = railwayStatusDeploymentTerminalFailure(status);
|
|
222
283
|
return {
|
|
223
284
|
type: "deployment-status",
|
|
224
285
|
service: service.key,
|
|
225
286
|
serviceName: service.serviceName,
|
|
226
287
|
environment: normalizeRailwayEnvironmentName(environment.name),
|
|
227
288
|
ok,
|
|
289
|
+
terminalFailure,
|
|
228
290
|
status: status || "missing_deployment",
|
|
229
291
|
observed: {
|
|
230
292
|
status: status || null,
|
|
@@ -234,74 +296,10 @@ function collectRailwayDeploymentStatusChecks(statusPayload, scope, services) {
|
|
|
234
296
|
instanceStatuses,
|
|
235
297
|
volumeMounts: Array.isArray(deployment?.meta?.volumeMounts) ? deployment.meta.volumeMounts : []
|
|
236
298
|
},
|
|
237
|
-
message: ok ? void 0 : `Railway deployment for ${service.serviceName} is not settled yet; observed ${status || "missing deployment status"}.`
|
|
299
|
+
message: ok ? void 0 : terminalFailure ? `Railway deployment for ${service.serviceName} failed with terminal status ${status}.` : `Railway deployment for ${service.serviceName} is not settled yet; observed ${status || "missing deployment status"}.`
|
|
238
300
|
};
|
|
239
301
|
});
|
|
240
302
|
}
|
|
241
|
-
function normalizeRailwayCliVolume(value, { serviceId, serviceName, environmentId, fallbackName, fallbackMountPath }) {
|
|
242
|
-
if (!value || typeof value !== "object") {
|
|
243
|
-
return null;
|
|
244
|
-
}
|
|
245
|
-
const record = value;
|
|
246
|
-
const id = typeof record.id === "string" && record.id.trim() ? record.id.trim() : "";
|
|
247
|
-
if (!id) {
|
|
248
|
-
return null;
|
|
249
|
-
}
|
|
250
|
-
const listedServiceName = typeof record.serviceName === "string" && record.serviceName.trim() ? record.serviceName.trim() : "";
|
|
251
|
-
if (listedServiceName && serviceName && listedServiceName !== serviceName) {
|
|
252
|
-
return null;
|
|
253
|
-
}
|
|
254
|
-
const name = typeof record.name === "string" && record.name.trim() ? record.name.trim() : fallbackName;
|
|
255
|
-
const mountPath = typeof record.mountPath === "string" && record.mountPath.trim() ? record.mountPath.trim() : fallbackMountPath;
|
|
256
|
-
const sizeMb = typeof record.sizeMB === "number" ? record.sizeMB : null;
|
|
257
|
-
const currentSizeMb = typeof record.currentSizeMB === "number" ? record.currentSizeMB : null;
|
|
258
|
-
return {
|
|
259
|
-
id,
|
|
260
|
-
name,
|
|
261
|
-
projectId: null,
|
|
262
|
-
instances: [{
|
|
263
|
-
id,
|
|
264
|
-
serviceId,
|
|
265
|
-
environmentId,
|
|
266
|
-
mountPath,
|
|
267
|
-
state: "READY",
|
|
268
|
-
sizeGb: sizeMb === null ? null : sizeMb / 1e3,
|
|
269
|
-
usedGb: currentSizeMb === null ? null : currentSizeMb / 1e3
|
|
270
|
-
}]
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
function normalizeRailwayCliVolumeList(value, options) {
|
|
274
|
-
if (!value || typeof value !== "object" || !Array.isArray(value.volumes)) {
|
|
275
|
-
return [];
|
|
276
|
-
}
|
|
277
|
-
return value.volumes.map((entry) => normalizeRailwayCliVolume(entry, options)).filter(Boolean);
|
|
278
|
-
}
|
|
279
|
-
function listRailwayServiceVolumesWithCli({
|
|
280
|
-
cwd,
|
|
281
|
-
serviceId,
|
|
282
|
-
serviceName,
|
|
283
|
-
environmentId,
|
|
284
|
-
name,
|
|
285
|
-
mountPath,
|
|
286
|
-
env = process.env
|
|
287
|
-
}) {
|
|
288
|
-
const listResult = runRailway(["volume", "--service", serviceId, "--environment", environmentId, "list", "--json"], {
|
|
289
|
-
cwd,
|
|
290
|
-
capture: true,
|
|
291
|
-
allowFailure: true,
|
|
292
|
-
env
|
|
293
|
-
});
|
|
294
|
-
if ((listResult.status ?? 1) !== 0) {
|
|
295
|
-
return [];
|
|
296
|
-
}
|
|
297
|
-
return normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
|
|
298
|
-
serviceId,
|
|
299
|
-
serviceName,
|
|
300
|
-
environmentId,
|
|
301
|
-
fallbackName: name,
|
|
302
|
-
fallbackMountPath: mountPath
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
303
|
function isUsableRailwayToken(value) {
|
|
306
304
|
return typeof value === "string" && value.trim().length >= 8;
|
|
307
305
|
}
|
|
@@ -311,7 +309,7 @@ function resolveRailwayAuthToken(env = process.env) {
|
|
|
311
309
|
function buildRailwayCommandEnv(env = process.env) {
|
|
312
310
|
const merged = { ...env };
|
|
313
311
|
const token = resolveRailwayAuthToken(merged);
|
|
314
|
-
const projectToken = configuredEnvValue(merged, "
|
|
312
|
+
const projectToken = configuredEnvValue(merged, "TREESEED_RAILWAY_TOKEN");
|
|
315
313
|
if (token) {
|
|
316
314
|
merged.RAILWAY_API_TOKEN = token;
|
|
317
315
|
} else {
|
|
@@ -320,13 +318,6 @@ function buildRailwayCommandEnv(env = process.env) {
|
|
|
320
318
|
merged.RAILWAY_TOKEN = projectToken || void 0;
|
|
321
319
|
return merged;
|
|
322
320
|
}
|
|
323
|
-
function buildRailwayDeployCommandEnv(env = process.env) {
|
|
324
|
-
const merged = buildRailwayCommandEnv(env);
|
|
325
|
-
if (shouldAttachRailwayDeployLogs(merged)) {
|
|
326
|
-
merged.CI = "true";
|
|
327
|
-
}
|
|
328
|
-
return merged;
|
|
329
|
-
}
|
|
330
321
|
function normalizeRailwaySchedule(schedule) {
|
|
331
322
|
if (!schedule || typeof schedule !== "object") {
|
|
332
323
|
return null;
|
|
@@ -376,47 +367,6 @@ function collectRailwaySchedules(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
376
367
|
visit(value);
|
|
377
368
|
return matches;
|
|
378
369
|
}
|
|
379
|
-
function normalizeRailwayProjectList(payload) {
|
|
380
|
-
try {
|
|
381
|
-
const parsed = JSON.parse(payload);
|
|
382
|
-
if (!Array.isArray(parsed)) {
|
|
383
|
-
return [];
|
|
384
|
-
}
|
|
385
|
-
return parsed.map((entry) => {
|
|
386
|
-
if (!entry || typeof entry !== "object") {
|
|
387
|
-
return null;
|
|
388
|
-
}
|
|
389
|
-
const id = typeof entry.id === "string" ? entry.id.trim() : "";
|
|
390
|
-
const name = typeof entry.name === "string" ? entry.name.trim() : "";
|
|
391
|
-
if (!id && !name) {
|
|
392
|
-
return null;
|
|
393
|
-
}
|
|
394
|
-
return { id, name };
|
|
395
|
-
}).filter(Boolean);
|
|
396
|
-
} catch {
|
|
397
|
-
return [];
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
function railwayMessage(result) {
|
|
401
|
-
return `${result?.stderr ?? ""}
|
|
402
|
-
${result?.stdout ?? ""}`.trim();
|
|
403
|
-
}
|
|
404
|
-
function isRailwayAlreadyExistsMessage(result) {
|
|
405
|
-
return /already exists|already taken|duplicate|has already been taken/iu.test(railwayMessage(result));
|
|
406
|
-
}
|
|
407
|
-
function isRailwayTransientFailure(result) {
|
|
408
|
-
const message = railwayMessage(result);
|
|
409
|
-
if (!message.trim() && result?.status === 1) {
|
|
410
|
-
return true;
|
|
411
|
-
}
|
|
412
|
-
return /timed out|failed to fetch|error decoding response body|expected value at line 1 column 1|temporarily unavailable|econnreset|etimedout|failed to stream build logs|failed to retrieve build log/iu.test(message);
|
|
413
|
-
}
|
|
414
|
-
function sleepSync(milliseconds) {
|
|
415
|
-
if (!Number.isFinite(milliseconds) || milliseconds <= 0) {
|
|
416
|
-
return;
|
|
417
|
-
}
|
|
418
|
-
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
419
|
-
}
|
|
420
370
|
function isRailwayScheduleCapabilityError(error) {
|
|
421
371
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
422
372
|
return /cronTriggers|cronTriggerCreate|cronTriggerUpdate/iu.test(message);
|
|
@@ -489,33 +439,6 @@ mutation TreeseedScheduleUpdate($id: String!, $name: String!, $schedule: String!
|
|
|
489
439
|
`.trim()
|
|
490
440
|
};
|
|
491
441
|
}
|
|
492
|
-
function runRailway(args, { cwd, capture = false, allowFailure = false, input, env, retryTransient = true, retryAttempts = 3, retryDelayMs = 2e3 } = {}) {
|
|
493
|
-
const effectiveEnv = buildRailwayCommandEnv({ ...process.env, ...env ?? {} });
|
|
494
|
-
const railway = resolveTreeseedToolCommand("railway", { env: effectiveEnv });
|
|
495
|
-
if (!railway) {
|
|
496
|
-
throw new Error("Railway CLI is unavailable.");
|
|
497
|
-
}
|
|
498
|
-
const runWithEnv = (spawnEnv) => spawnSync(railway.command, [...railway.argsPrefix, ...args], {
|
|
499
|
-
cwd,
|
|
500
|
-
stdio: input !== void 0 ? ["pipe", capture ? "pipe" : "inherit", capture ? "pipe" : "inherit"] : capture ? "pipe" : "inherit",
|
|
501
|
-
encoding: "utf8",
|
|
502
|
-
env: spawnEnv,
|
|
503
|
-
input
|
|
504
|
-
});
|
|
505
|
-
let result = null;
|
|
506
|
-
const maxAttempts = retryTransient && !allowFailure ? Math.max(1, Number(retryAttempts) || 1) : 1;
|
|
507
|
-
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
508
|
-
result = runWithEnv(effectiveEnv);
|
|
509
|
-
if (result.status === 0 || allowFailure || !isRailwayTransientFailure(result) || attempt === maxAttempts) {
|
|
510
|
-
break;
|
|
511
|
-
}
|
|
512
|
-
sleepSync((Number(retryDelayMs) || 0) * attempt);
|
|
513
|
-
}
|
|
514
|
-
if (result?.status !== 0 && !allowFailure) {
|
|
515
|
-
throw new Error(result.stderr?.trim() || result.stdout?.trim() || `railway ${args.join(" ")} failed`);
|
|
516
|
-
}
|
|
517
|
-
return result;
|
|
518
|
-
}
|
|
519
442
|
async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
|
|
520
443
|
services = configuredRailwayServices(tenantRoot, scope),
|
|
521
444
|
env = process.env,
|
|
@@ -602,6 +525,26 @@ async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
|
|
|
602
525
|
}
|
|
603
526
|
};
|
|
604
527
|
}
|
|
528
|
+
if (checks.some((entry) => entry.terminalFailure === true)) {
|
|
529
|
+
return {
|
|
530
|
+
ok: false,
|
|
531
|
+
checks: checks.map((check) => ({
|
|
532
|
+
...check,
|
|
533
|
+
settle: {
|
|
534
|
+
durationMs: elapsedMs(startMs),
|
|
535
|
+
pollCount,
|
|
536
|
+
finalStatus: check.status,
|
|
537
|
+
terminalFailure: check.terminalFailure === true
|
|
538
|
+
}
|
|
539
|
+
})),
|
|
540
|
+
settle: {
|
|
541
|
+
durationMs: elapsedMs(startMs),
|
|
542
|
+
pollCount,
|
|
543
|
+
status: "failed"
|
|
544
|
+
},
|
|
545
|
+
message: "Railway deployment reached a terminal failed state."
|
|
546
|
+
};
|
|
547
|
+
}
|
|
605
548
|
if (Date.now() >= deadline) {
|
|
606
549
|
return {
|
|
607
550
|
ok: false,
|
|
@@ -668,264 +611,21 @@ query TreeseedRailwayDeploymentStatus($projectId: String!) {
|
|
|
668
611
|
});
|
|
669
612
|
return payload.data?.project ?? null;
|
|
670
613
|
}
|
|
671
|
-
function
|
|
672
|
-
const effectiveEnv = buildRailwayCommandEnv({
|
|
673
|
-
...process.env,
|
|
674
|
-
...env ?? {}
|
|
675
|
-
});
|
|
676
|
-
let result = null;
|
|
677
|
-
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
678
|
-
const railway = resolveTreeseedToolCommand("railway", { env: effectiveEnv });
|
|
679
|
-
if (!railway) {
|
|
680
|
-
throw new Error("Railway CLI is unavailable.");
|
|
681
|
-
}
|
|
682
|
-
result = spawnSync(railway.command, [
|
|
683
|
-
...railway.argsPrefix,
|
|
684
|
-
"variable",
|
|
685
|
-
"set",
|
|
686
|
-
"--service",
|
|
687
|
-
service,
|
|
688
|
-
"--environment",
|
|
689
|
-
environment,
|
|
690
|
-
"--stdin",
|
|
691
|
-
"--skip-deploys",
|
|
692
|
-
key
|
|
693
|
-
], {
|
|
694
|
-
cwd,
|
|
695
|
-
stdio: ["pipe", capture ? "pipe" : "inherit", capture ? "pipe" : "inherit"],
|
|
696
|
-
encoding: "utf8",
|
|
697
|
-
env: effectiveEnv,
|
|
698
|
-
input: `${value}
|
|
699
|
-
`
|
|
700
|
-
});
|
|
701
|
-
if (result.status === 0) {
|
|
702
|
-
return result;
|
|
703
|
-
}
|
|
704
|
-
if (!isRailwayTransientFailure(result)) {
|
|
705
|
-
break;
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
if (result?.status !== 0 && !allowFailure) {
|
|
709
|
-
throw new Error(result?.stderr?.trim() || result?.stdout?.trim() || `railway variable set --stdin ${key} failed`);
|
|
710
|
-
}
|
|
711
|
-
return result;
|
|
712
|
-
}
|
|
713
|
-
function ensureRailwayProjectExists(service, { env = process.env } = {}) {
|
|
714
|
-
const projectName = typeof service?.projectName === "string" ? service.projectName.trim() : "";
|
|
715
|
-
const projectId = typeof service?.projectId === "string" ? service.projectId.trim() : "";
|
|
716
|
-
const listed = runRailway(["list", "--json"], {
|
|
717
|
-
cwd: service.rootDir,
|
|
718
|
-
capture: true,
|
|
719
|
-
allowFailure: true,
|
|
720
|
-
env
|
|
721
|
-
});
|
|
722
|
-
if (listed.status === 0) {
|
|
723
|
-
const match = normalizeRailwayProjectList(listed.stdout ?? "").find((entry) => entry.name === projectName || entry.id === projectName || entry.id === projectId);
|
|
724
|
-
if (match) {
|
|
725
|
-
return match;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
if (!projectName) {
|
|
729
|
-
if (projectId) {
|
|
730
|
-
return { id: projectId, name: "" };
|
|
731
|
-
}
|
|
732
|
-
throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a projectName.`);
|
|
733
|
-
}
|
|
734
|
-
const args = ["init", "--name", projectName, "--json"];
|
|
735
|
-
const workspace = resolveRailwayWorkspace(env);
|
|
736
|
-
if (workspace) {
|
|
737
|
-
args.push("--workspace", workspace);
|
|
738
|
-
}
|
|
739
|
-
const created = runRailway(args, {
|
|
740
|
-
cwd: service.rootDir,
|
|
741
|
-
capture: true,
|
|
742
|
-
allowFailure: true,
|
|
743
|
-
env
|
|
744
|
-
});
|
|
745
|
-
if (created.status !== 0 && !isRailwayAlreadyExistsMessage(created)) {
|
|
746
|
-
throw new Error(railwayMessage(created) || `railway ${args.join(" ")} failed`);
|
|
747
|
-
}
|
|
748
|
-
return null;
|
|
749
|
-
}
|
|
750
|
-
function ensureRailwayEnvironmentExists(service, { env = process.env } = {}) {
|
|
751
|
-
const environmentName = normalizeRailwayEnvironmentName(service?.railwayEnvironment);
|
|
752
|
-
if (!environmentName) {
|
|
753
|
-
return null;
|
|
754
|
-
}
|
|
755
|
-
const linkResult = runRailway(["environment", "link", environmentName, "--json"], {
|
|
756
|
-
cwd: service.rootDir,
|
|
757
|
-
capture: true,
|
|
758
|
-
allowFailure: true,
|
|
759
|
-
env
|
|
760
|
-
});
|
|
761
|
-
if (linkResult.status === 0) {
|
|
762
|
-
return linkResult;
|
|
763
|
-
}
|
|
764
|
-
const createResult = runRailway(["environment", "new", environmentName, "--json"], {
|
|
765
|
-
cwd: service.rootDir,
|
|
766
|
-
capture: true,
|
|
767
|
-
allowFailure: true,
|
|
768
|
-
env
|
|
769
|
-
});
|
|
770
|
-
if (createResult.status !== 0 && !isRailwayAlreadyExistsMessage(createResult)) {
|
|
771
|
-
throw new Error(railwayMessage(createResult) || `railway environment new ${environmentName} failed`);
|
|
772
|
-
}
|
|
773
|
-
const relinkResult = runRailway(["environment", "link", environmentName, "--json"], {
|
|
774
|
-
cwd: service.rootDir,
|
|
775
|
-
capture: true,
|
|
776
|
-
allowFailure: true,
|
|
777
|
-
env
|
|
778
|
-
});
|
|
779
|
-
if (relinkResult.status !== 0) {
|
|
780
|
-
throw new Error(railwayMessage(relinkResult) || `railway environment link ${environmentName} failed`);
|
|
781
|
-
}
|
|
782
|
-
return relinkResult;
|
|
783
|
-
}
|
|
784
|
-
function ensureRailwayServiceExists(service, { env = process.env } = {}) {
|
|
785
|
-
const serviceSelector = typeof (service?.serviceName ?? service?.serviceId) === "string" ? String(service.serviceName ?? service.serviceId).trim() : "";
|
|
786
|
-
if (!serviceSelector) {
|
|
787
|
-
throw new Error(`Railway service ${service?.key ?? "(unknown)"} is missing a service selector.`);
|
|
788
|
-
}
|
|
789
|
-
const statusArgs = ["service", "status", "--service", serviceSelector, "--environment", service.railwayEnvironment, "--json"];
|
|
790
|
-
const statusResult = runRailway(statusArgs, {
|
|
791
|
-
cwd: service.rootDir,
|
|
792
|
-
capture: true,
|
|
793
|
-
allowFailure: true,
|
|
794
|
-
env
|
|
795
|
-
});
|
|
796
|
-
if (statusResult.status === 0) {
|
|
797
|
-
return statusResult;
|
|
798
|
-
}
|
|
799
|
-
const createResult = runRailway(["add", "--service", serviceSelector, "--json"], {
|
|
800
|
-
cwd: service.rootDir,
|
|
801
|
-
capture: true,
|
|
802
|
-
allowFailure: true,
|
|
803
|
-
env
|
|
804
|
-
});
|
|
805
|
-
if (createResult.status !== 0 && !isRailwayAlreadyExistsMessage(createResult)) {
|
|
806
|
-
throw new Error(railwayMessage(createResult) || `railway add --service ${serviceSelector} failed`);
|
|
807
|
-
}
|
|
808
|
-
const refreshed = runRailway(statusArgs, {
|
|
809
|
-
cwd: service.rootDir,
|
|
810
|
-
capture: true,
|
|
811
|
-
allowFailure: true,
|
|
812
|
-
env
|
|
813
|
-
});
|
|
814
|
-
if (refreshed.status !== 0) {
|
|
815
|
-
throw new Error(railwayMessage(refreshed) || `railway service status --service ${serviceSelector} failed`);
|
|
816
|
-
}
|
|
817
|
-
return refreshed;
|
|
818
|
-
}
|
|
819
|
-
function ensureRailwayDatabaseServiceExists(service, {
|
|
820
|
-
database = "postgres",
|
|
821
|
-
env = process.env
|
|
822
|
-
} = {}) {
|
|
823
|
-
const serviceSelector = typeof (service?.serviceName ?? service?.serviceId) === "string" ? String(service.serviceName ?? service.serviceId).trim() : "";
|
|
824
|
-
if (!serviceSelector) {
|
|
825
|
-
throw new Error(`Railway database service ${service?.key ?? "(unknown)"} is missing a service selector.`);
|
|
826
|
-
}
|
|
827
|
-
const projectSelector = typeof service?.projectId === "string" && service.projectId.trim() ? service.projectId.trim() : typeof service?.projectName === "string" && service.projectName.trim() ? service.projectName.trim() : "";
|
|
828
|
-
if (!projectSelector) {
|
|
829
|
-
throw new Error(`Railway database service ${service?.key ?? serviceSelector} is missing a project selector.`);
|
|
830
|
-
}
|
|
831
|
-
const linkArgs = ["link", "--project", projectSelector];
|
|
832
|
-
const workspace = resolveRailwayWorkspace(env);
|
|
833
|
-
if (workspace) {
|
|
834
|
-
linkArgs.push("--workspace", workspace);
|
|
835
|
-
}
|
|
836
|
-
const environmentName = normalizeRailwayEnvironmentName(service?.railwayEnvironment);
|
|
837
|
-
if (environmentName) {
|
|
838
|
-
linkArgs.push("--environment", environmentName);
|
|
839
|
-
}
|
|
840
|
-
const linkResult = runRailway(linkArgs, {
|
|
841
|
-
cwd: service.rootDir,
|
|
842
|
-
capture: true,
|
|
843
|
-
allowFailure: true,
|
|
844
|
-
env
|
|
845
|
-
});
|
|
846
|
-
if ((linkResult.status ?? 1) !== 0) {
|
|
847
|
-
throw new Error(railwayMessage(linkResult) || `railway ${linkArgs.join(" ")} failed`);
|
|
848
|
-
}
|
|
849
|
-
const statusArgs = ["service", "status", "--service", serviceSelector, "--environment", service.railwayEnvironment, "--json"];
|
|
850
|
-
const statusResult = runRailway(statusArgs, {
|
|
851
|
-
cwd: service.rootDir,
|
|
852
|
-
capture: true,
|
|
853
|
-
allowFailure: true,
|
|
854
|
-
env
|
|
855
|
-
});
|
|
856
|
-
if (statusResult.status === 0) {
|
|
857
|
-
return statusResult;
|
|
858
|
-
}
|
|
859
|
-
const addResult = runRailway(["add", "--database", database, "--service", serviceSelector, "--json"], {
|
|
860
|
-
cwd: service.rootDir,
|
|
861
|
-
capture: true,
|
|
862
|
-
allowFailure: true,
|
|
863
|
-
env
|
|
864
|
-
});
|
|
865
|
-
if (addResult.status !== 0 && !isRailwayAlreadyExistsMessage(addResult)) {
|
|
866
|
-
throw new Error(railwayMessage(addResult) || `railway add --database ${database} --service ${serviceSelector} failed`);
|
|
867
|
-
}
|
|
868
|
-
const refreshed = runRailway(statusArgs, {
|
|
869
|
-
cwd: service.rootDir,
|
|
870
|
-
capture: true,
|
|
871
|
-
allowFailure: true,
|
|
872
|
-
env
|
|
873
|
-
});
|
|
874
|
-
if (refreshed.status !== 0) {
|
|
875
|
-
throw new Error(railwayMessage(refreshed) || `railway service status --service ${serviceSelector} failed`);
|
|
876
|
-
}
|
|
877
|
-
return refreshed;
|
|
878
|
-
}
|
|
879
|
-
function ensureRailwayProjectContext(service, { env = process.env, allowFailure = false, capture = false } = {}) {
|
|
880
|
-
ensureRailwayProjectExists(service, { env });
|
|
881
|
-
let projectSelector = service?.projectId ?? "";
|
|
882
|
-
if ((!projectSelector || !String(projectSelector).trim()) && service?.projectName) {
|
|
883
|
-
const listed = runRailway(["list", "--json"], {
|
|
884
|
-
cwd: service.rootDir,
|
|
885
|
-
capture: true,
|
|
886
|
-
allowFailure: true,
|
|
887
|
-
env
|
|
888
|
-
});
|
|
889
|
-
if (listed.status === 0) {
|
|
890
|
-
const match = normalizeRailwayProjectList(listed.stdout ?? "").find((entry) => entry.name === service.projectName || entry.id === service.projectName);
|
|
891
|
-
projectSelector = match?.id || match?.name || service.projectName;
|
|
892
|
-
} else {
|
|
893
|
-
projectSelector = service.projectName;
|
|
894
|
-
}
|
|
895
|
-
}
|
|
896
|
-
projectSelector = typeof projectSelector === "string" ? projectSelector.trim() : "";
|
|
897
|
-
if (typeof projectSelector !== "string" || projectSelector.trim().length === 0) {
|
|
898
|
-
throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a project selector.`);
|
|
899
|
-
}
|
|
900
|
-
const args = ["link", "--project", projectSelector];
|
|
901
|
-
const workspace = resolveRailwayWorkspace(env);
|
|
902
|
-
if (workspace) {
|
|
903
|
-
args.push("--workspace", workspace);
|
|
904
|
-
}
|
|
905
|
-
const environmentName = normalizeRailwayEnvironmentName(service?.railwayEnvironment);
|
|
906
|
-
if (environmentName) {
|
|
907
|
-
args.push("--environment", environmentName);
|
|
908
|
-
}
|
|
909
|
-
runRailway(args, {
|
|
910
|
-
cwd: service.rootDir,
|
|
911
|
-
capture,
|
|
912
|
-
allowFailure,
|
|
913
|
-
env
|
|
914
|
-
});
|
|
915
|
-
if (environmentName) {
|
|
916
|
-
ensureRailwayEnvironmentExists(service, { env });
|
|
917
|
-
return runRailway(["environment", "link", environmentName, "--json"], {
|
|
918
|
-
cwd: service.rootDir,
|
|
919
|
-
capture,
|
|
920
|
-
allowFailure,
|
|
921
|
-
env
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
return null;
|
|
925
|
-
}
|
|
926
|
-
function configuredRailwayServices(tenantRoot, scope) {
|
|
927
|
-
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
614
|
+
function configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, application = null, machineConfigRoot = tenantRoot) {
|
|
928
615
|
const normalizedScope = normalizeScope(scope);
|
|
616
|
+
const imageRefKeys = [
|
|
617
|
+
"TREESEED_API_IMAGE_REF",
|
|
618
|
+
"TREESEED_OPERATIONS_RUNNER_IMAGE_REF",
|
|
619
|
+
"TREESEED_AGENT_MANAGER_IMAGE_REF",
|
|
620
|
+
"TREESEED_AGENT_RUNNER_IMAGE_REF"
|
|
621
|
+
];
|
|
622
|
+
let machineEnv = {};
|
|
623
|
+
try {
|
|
624
|
+
machineEnv = resolveTreeseedMachineEnvironmentValues(machineConfigRoot, normalizedScope, imageRefKeys);
|
|
625
|
+
} catch {
|
|
626
|
+
machineEnv = {};
|
|
627
|
+
}
|
|
628
|
+
const imageRefEnv = { ...process.env, ...machineEnv };
|
|
929
629
|
let identity;
|
|
930
630
|
try {
|
|
931
631
|
identity = resolveTreeseedResourceIdentity(deployConfig, createPersistentDeployTarget(normalizedScope));
|
|
@@ -939,13 +639,14 @@ function configuredRailwayServices(tenantRoot, scope) {
|
|
|
939
639
|
}
|
|
940
640
|
const configuredOptionalServiceKeys = Object.keys(deployConfig.services ?? {}).filter((serviceKey) => RAILWAY_SERVICE_KEYS.includes(serviceKey));
|
|
941
641
|
const serviceKeys = hostingKind === "hosted_project" ? [.../* @__PURE__ */ new Set([...HOSTED_PROJECT_SERVICE_KEYS, ...configuredOptionalServiceKeys])] : RAILWAY_SERVICE_KEYS;
|
|
942
|
-
|
|
642
|
+
const configuredServices = serviceKeys.flatMap((serviceKey) => {
|
|
943
643
|
const service = deployConfig.services?.[serviceKey];
|
|
944
644
|
if (!service || service.enabled === false || (service.provider ?? "railway") !== "railway") {
|
|
945
645
|
return [];
|
|
946
646
|
}
|
|
947
|
-
const
|
|
948
|
-
const
|
|
647
|
+
const isCapacityProviderService = String(serviceKey).startsWith("capacityProvider");
|
|
648
|
+
const defaultRootDir = ["api", "operationsRunner"].includes(serviceKey) ? "." : isCapacityProviderService ? "packages/agent" : "packages/core";
|
|
649
|
+
const serviceRoot = isCapacityProviderService ? resolveRailwayCapacityProviderRoot(tenantRoot, service) : resolve(tenantRoot, service.railway?.rootDir ?? service.rootDir ?? defaultRootDir);
|
|
949
650
|
const railwayEnvironment = resolveRailwayEnvironmentForScope(
|
|
950
651
|
normalizedScope,
|
|
951
652
|
service.environments?.[normalizedScope]?.railwayEnvironment
|
|
@@ -953,45 +654,249 @@ function configuredRailwayServices(tenantRoot, scope) {
|
|
|
953
654
|
const publicBaseUrl = service.environments?.[normalizedScope]?.baseUrl ?? service.publicBaseUrl ?? null;
|
|
954
655
|
const configuredServiceName = service.railway?.serviceName ?? (serviceKey === "workerRunner" ? deriveRailwayWorkerRunnerServiceName(identity.deploymentKey) : `${identity.deploymentKey}-${railwayServiceNameSuffix(serviceKey)}`);
|
|
955
656
|
const configuredRunnerPool = service.railway?.runnerPool && typeof service.railway.runnerPool === "object" ? service.railway.runnerPool : null;
|
|
956
|
-
const runnerPool = serviceKey === "
|
|
957
|
-
bootstrapCount: Math.max(1, Number.parseInt(String(configuredRunnerPool?.bootstrapCount ??
|
|
958
|
-
maxRunners: Math.max(1, Number.parseInt(String(configuredRunnerPool?.maxRunners ?? configuredRunnerPool?.bootstrapCount ??
|
|
657
|
+
const runnerPool = serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? {
|
|
658
|
+
bootstrapCount: Math.max(1, Number.parseInt(String(configuredRunnerPool?.bootstrapCount ?? (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)), 10) || (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)),
|
|
659
|
+
maxRunners: Math.max(1, Number.parseInt(String(configuredRunnerPool?.maxRunners ?? configuredRunnerPool?.bootstrapCount ?? (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)), 10) || (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)),
|
|
959
660
|
volumeMountPath: service.railway?.volumeMountPath ?? configuredRunnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH
|
|
960
661
|
} : serviceKey === "workerRunner" ? {
|
|
961
662
|
bootstrapIndex: WORKER_RUNNER_BOOTSTRAP_INDEX,
|
|
962
663
|
volumeMountPath: WORKER_RUNNER_VOLUME_MOUNT_PATH
|
|
963
664
|
} : null;
|
|
964
|
-
const instanceCount = serviceKey === "
|
|
665
|
+
const instanceCount = serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? runnerPool.bootstrapCount : 1;
|
|
965
666
|
return Array.from({ length: instanceCount }, (_, offset) => {
|
|
966
667
|
const runnerIndex = offset + 1;
|
|
967
|
-
const serviceName = serviceKey === "
|
|
668
|
+
const serviceName = serviceKey === "operationsRunner" ? deriveRailwayOperationsRunnerServiceName(configuredServiceName, runnerIndex) : serviceKey === "capacityProviderRunner" ? deriveRailwayCapacityProviderRunnerServiceName(configuredServiceName, runnerIndex) : configuredServiceName;
|
|
669
|
+
const configuredImageRefEnv = service.railway?.imageRefEnv ?? railwayImageRefEnvForService(serviceKey);
|
|
670
|
+
const imageRef = service.railway?.imageRef ?? (configuredImageRefEnv ? envValue(configuredImageRefEnv, imageRefEnv) || null : null) ?? defaultRailwayImageRef(serviceKey, normalizedScope, imageRefEnv);
|
|
671
|
+
const sourcePolicy = resolveRailwayServiceSourcePolicy({
|
|
672
|
+
tenantRoot,
|
|
673
|
+
scope: normalizedScope,
|
|
674
|
+
serviceKey,
|
|
675
|
+
service,
|
|
676
|
+
serviceRoot,
|
|
677
|
+
imageRef
|
|
678
|
+
});
|
|
679
|
+
const resolvedImageRef = sourcePolicy.sourceMode === "image" ? imageRef : null;
|
|
968
680
|
return {
|
|
969
681
|
key: serviceKey,
|
|
970
|
-
instanceKey: serviceKey === "
|
|
971
|
-
runnerIndex: serviceKey === "
|
|
682
|
+
instanceKey: serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? `${serviceKey}:${runnerIndex}` : serviceKey,
|
|
683
|
+
runnerIndex: serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? runnerIndex : null,
|
|
684
|
+
serviceConfig: service,
|
|
972
685
|
scope: normalizedScope,
|
|
973
686
|
projectId: service.railway?.projectId ?? null,
|
|
974
687
|
projectName: service.railway?.projectName ?? identity.deploymentKey,
|
|
975
688
|
serviceId: service.railway?.serviceId ?? null,
|
|
976
689
|
serviceName,
|
|
977
|
-
runnerId: serviceKey === "
|
|
690
|
+
runnerId: serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? serviceName : null,
|
|
978
691
|
rootDir: serviceRoot,
|
|
979
692
|
publicBaseUrl,
|
|
980
693
|
railwayEnvironment,
|
|
981
|
-
buildCommand: service.railway?.buildCommand ?? null,
|
|
982
|
-
startCommand: service.railway?.startCommand ?? null,
|
|
694
|
+
buildCommand: resolvedImageRef ? null : service.railway?.buildCommand ?? null,
|
|
695
|
+
startCommand: isCapacityProviderService ? null : resolvedImageRef ? null : service.railway?.startCommand ?? null,
|
|
696
|
+
imageRef: resolvedImageRef,
|
|
697
|
+
sourceMode: sourcePolicy.sourceMode,
|
|
698
|
+
sourceRepo: sourcePolicy.sourceRepo,
|
|
699
|
+
sourceBranch: sourcePolicy.sourceBranch,
|
|
700
|
+
sourceCommit: sourcePolicy.sourceCommit,
|
|
701
|
+
sourceRootDirectory: sourcePolicy.sourceRootDirectory,
|
|
702
|
+
dockerfilePath: sourcePolicy.sourceMode === "git" ? service.railway?.dockerfilePath ?? null : null,
|
|
983
703
|
healthcheckPath: service.railway?.healthcheckPath ?? null,
|
|
984
704
|
healthcheckTimeoutSeconds: service.railway?.healthcheckTimeoutSeconds ?? null,
|
|
985
705
|
healthcheckIntervalSeconds: service.railway?.healthcheckIntervalSeconds ?? null,
|
|
986
706
|
restartPolicy: service.railway?.restartPolicy ?? null,
|
|
987
707
|
runtimeMode: service.railway?.runtimeMode ?? null,
|
|
988
|
-
volumeMountPath: serviceKey === "
|
|
708
|
+
volumeMountPath: serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? runnerPool.volumeMountPath : service.railway?.volumeMountPath ?? null,
|
|
989
709
|
schedule: normalizeScheduleExpressions(service.railway?.schedule),
|
|
990
710
|
hostingKind,
|
|
991
|
-
runnerPool
|
|
711
|
+
runnerPool,
|
|
712
|
+
application,
|
|
713
|
+
secretRefs: Array.isArray(service.secretRefs) ? service.secretRefs : [],
|
|
714
|
+
variableRefs: Array.isArray(service.variableRefs) ? service.variableRefs : []
|
|
992
715
|
};
|
|
993
716
|
});
|
|
994
717
|
}).filter(Boolean);
|
|
718
|
+
return [
|
|
719
|
+
...configuredServices,
|
|
720
|
+
...configuredPublicTreeDxRailwayServices({
|
|
721
|
+
tenantRoot,
|
|
722
|
+
scope: normalizedScope,
|
|
723
|
+
deployConfig,
|
|
724
|
+
identity,
|
|
725
|
+
hostingKind,
|
|
726
|
+
application,
|
|
727
|
+
imageRefEnv,
|
|
728
|
+
workspaceRoot: machineConfigRoot
|
|
729
|
+
})
|
|
730
|
+
];
|
|
731
|
+
}
|
|
732
|
+
function configuredPublicTreeDxRailwayServices({ tenantRoot, scope, deployConfig, identity, hostingKind, application, imageRefEnv, workspaceRoot }) {
|
|
733
|
+
if (deployConfig.hosting?.kind !== "treeseed_control_plane") {
|
|
734
|
+
return [];
|
|
735
|
+
}
|
|
736
|
+
const railway = deployConfig.publicTreeDxFederation?.railway ?? {};
|
|
737
|
+
const nodePool = railway.nodePool && typeof railway.nodePool === "object" && !Array.isArray(railway.nodePool) ? railway.nodePool : {};
|
|
738
|
+
const bootstrapCount = Math.max(0, Number.parseInt(String(nodePool.bootstrapCount ?? 1), 10) || 0);
|
|
739
|
+
if (bootstrapCount <= 0) {
|
|
740
|
+
return [];
|
|
741
|
+
}
|
|
742
|
+
const configuredSource = railway.source && typeof railway.source === "object" && !Array.isArray(railway.source) ? railway.source : {};
|
|
743
|
+
const treeDxRoot = resolve(workspaceRoot ?? tenantRoot, "packages", "treedx");
|
|
744
|
+
const configuredMode = typeof railway.sourceMode === "string" ? railway.sourceMode : null;
|
|
745
|
+
const sourceMode = scope === "prod" ? "image" : configuredMode === "git" || configuredMode === "image" ? configuredMode : "git";
|
|
746
|
+
const repository = typeof railway.sourceRepo === "string" ? railway.sourceRepo : typeof configuredSource.repository === "string" ? configuredSource.repository : typeof configuredSource.repo === "string" ? configuredSource.repo : readTreeseedPackageRepository(treeDxRoot) ?? "treeseed-ai/treedx";
|
|
747
|
+
const sourceBranch = typeof railway.sourceBranch === "string" ? railway.sourceBranch : typeof configuredSource.branch === "string" ? configuredSource.branch : scope === "staging" ? "staging" : null;
|
|
748
|
+
const sourceRootDirectory = typeof railway.sourceRootDirectory === "string" ? railway.sourceRootDirectory : typeof configuredSource.rootDirectory === "string" ? configuredSource.rootDirectory : ".";
|
|
749
|
+
const projectName = typeof railway.projectName === "string" && railway.projectName.trim() ? railway.projectName.trim() : typeof imageRefEnv.TREESEED_PUBLIC_TREEDX_RAILWAY_PROJECT_NAME === "string" && imageRefEnv.TREESEED_PUBLIC_TREEDX_RAILWAY_PROJECT_NAME.trim() ? imageRefEnv.TREESEED_PUBLIC_TREEDX_RAILWAY_PROJECT_NAME.trim() : identity.deploymentKey;
|
|
750
|
+
const railwayEnvironment = resolveRailwayEnvironmentForScope(scope, railway.environmentName);
|
|
751
|
+
const baseImageRef = envValue("TREESEED_PUBLIC_TREEDX_IMAGE_REF", imageRefEnv) || "treeseed/treedx";
|
|
752
|
+
return Array.from({ length: bootstrapCount }, (_, offset) => {
|
|
753
|
+
const index = offset + 1;
|
|
754
|
+
const serviceName = `${PUBLIC_TREEDX_NODE_SERVICE_KEY_PREFIX}${String(index).padStart(2, "0")}`;
|
|
755
|
+
return {
|
|
756
|
+
key: serviceName,
|
|
757
|
+
instanceKey: serviceName,
|
|
758
|
+
runnerIndex: null,
|
|
759
|
+
serviceConfig: null,
|
|
760
|
+
scope,
|
|
761
|
+
projectId: typeof railway.projectId === "string" ? railway.projectId : null,
|
|
762
|
+
projectName,
|
|
763
|
+
serviceId: typeof railway.serviceId === "string" && bootstrapCount === 1 ? railway.serviceId : null,
|
|
764
|
+
serviceName,
|
|
765
|
+
runnerId: null,
|
|
766
|
+
rootDir: treeDxRoot,
|
|
767
|
+
publicBaseUrl: null,
|
|
768
|
+
railwayEnvironment,
|
|
769
|
+
buildCommand: sourceMode === "git" ? railway.buildCommand ?? null : null,
|
|
770
|
+
startCommand: sourceMode === "git" ? railway.startCommand ?? null : null,
|
|
771
|
+
imageRef: sourceMode === "image" ? baseImageRef : null,
|
|
772
|
+
sourceMode,
|
|
773
|
+
sourceRepo: sourceMode === "git" ? repository : null,
|
|
774
|
+
sourceBranch: sourceMode === "git" ? sourceBranch : null,
|
|
775
|
+
sourceCommit: sourceMode === "git" ? headCommitSafe(treeDxRoot) ?? headCommitSafe(tenantRoot) : null,
|
|
776
|
+
sourceRootDirectory: sourceMode === "git" ? sourceRootDirectory : null,
|
|
777
|
+
dockerfilePath: sourceMode === "git" ? railway.dockerfilePath ?? "/Dockerfile" : null,
|
|
778
|
+
healthcheckPath: railway.healthcheckPath ?? null,
|
|
779
|
+
healthcheckTimeoutSeconds: railway.healthcheckTimeoutSeconds ?? null,
|
|
780
|
+
healthcheckIntervalSeconds: railway.healthcheckIntervalSeconds ?? null,
|
|
781
|
+
restartPolicy: railway.restartPolicy ?? null,
|
|
782
|
+
runtimeMode: railway.runtimeMode ?? "replicated",
|
|
783
|
+
volumeMountPath: railway.volumeMountPath ?? "/data",
|
|
784
|
+
schedule: [],
|
|
785
|
+
hostingKind,
|
|
786
|
+
runnerPool: null,
|
|
787
|
+
application,
|
|
788
|
+
environmentVariables: {
|
|
789
|
+
PORT: "4000",
|
|
790
|
+
TREEDX_DATA_DIR: railway.volumeMountPath ?? "/data",
|
|
791
|
+
TREEDX_AUTH_MODE: "connected",
|
|
792
|
+
TREEDX_AUTH_VERIFIER: "hs256_dev",
|
|
793
|
+
TREEDX_ALLOW_DEV_VERIFIER_IN_PROD: "true",
|
|
794
|
+
TREEDX_EXEC_BACKEND: "container_sandbox",
|
|
795
|
+
TREEDX_FEDERATION_MODE: "connected_library",
|
|
796
|
+
TREEDX_JWT_AUDIENCE: "treedx-public-federation",
|
|
797
|
+
TREEDX_JWT_ISSUER: "https://api.treeseed.local/treedx",
|
|
798
|
+
TREEDX_BOOTSTRAP_TRUST_ACTOR_ID: "treeseed-api",
|
|
799
|
+
TREEDX_BOOTSTRAP_TRUST_TENANT_ID: "treeseed-control-plane",
|
|
800
|
+
TREEDX_BOOTSTRAP_TRUST_REPO_IDS: "*",
|
|
801
|
+
TREEDX_BOOTSTRAP_TRUST_REFS: "*",
|
|
802
|
+
TREEDX_BOOTSTRAP_TRUST_PATHS: "**",
|
|
803
|
+
TREEDX_SCOPE: "public_federation"
|
|
804
|
+
},
|
|
805
|
+
secretRefs: ["TREEDX_SECRET_KEY_BASE", "TREEDX_ADMIN_TOKEN", "TREEDX_JWT_HS256_SECRET"],
|
|
806
|
+
variableRefs: [
|
|
807
|
+
"PORT",
|
|
808
|
+
"TREEDX_DATA_DIR",
|
|
809
|
+
"TREEDX_AUTH_MODE",
|
|
810
|
+
"TREEDX_AUTH_VERIFIER",
|
|
811
|
+
"TREEDX_ALLOW_DEV_VERIFIER_IN_PROD",
|
|
812
|
+
"TREEDX_EXEC_BACKEND",
|
|
813
|
+
"TREEDX_FEDERATION_MODE",
|
|
814
|
+
"TREEDX_JWT_AUDIENCE",
|
|
815
|
+
"TREEDX_JWT_ISSUER",
|
|
816
|
+
"TREEDX_BOOTSTRAP_TRUST_ACTOR_ID",
|
|
817
|
+
"TREEDX_BOOTSTRAP_TRUST_TENANT_ID",
|
|
818
|
+
"TREEDX_BOOTSTRAP_TRUST_REPO_IDS",
|
|
819
|
+
"TREEDX_BOOTSTRAP_TRUST_REFS",
|
|
820
|
+
"TREEDX_BOOTSTRAP_TRUST_PATHS",
|
|
821
|
+
"TREEDX_SCOPE"
|
|
822
|
+
]
|
|
823
|
+
};
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
function readTreeseedPackageRepository(packageRoot) {
|
|
827
|
+
const manifestPath = resolve(packageRoot, "treeseed.package.yaml");
|
|
828
|
+
if (!existsSync(manifestPath)) return null;
|
|
829
|
+
try {
|
|
830
|
+
const manifest = parseYaml(readFileSync(manifestPath, "utf8"));
|
|
831
|
+
const repository = manifest && typeof manifest === "object" && !Array.isArray(manifest) ? manifest.repository : null;
|
|
832
|
+
return typeof repository === "string" && /^[^/\s]+\/[^/\s]+$/u.test(repository.trim()) ? repository.trim() : null;
|
|
833
|
+
} catch {
|
|
834
|
+
return null;
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
function headCommitSafe(cwd) {
|
|
838
|
+
try {
|
|
839
|
+
return runTreeseedGitText(["rev-parse", "HEAD"], {
|
|
840
|
+
cwd,
|
|
841
|
+
mode: classifyTreeseedGitMode(["rev-parse", "HEAD"])
|
|
842
|
+
}).trim();
|
|
843
|
+
} catch {
|
|
844
|
+
return null;
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
function resolveRailwayServiceSourcePolicy({ tenantRoot, scope, serviceKey, service, serviceRoot, imageRef }) {
|
|
848
|
+
const configuredMode = typeof service.railway?.sourceMode === "string" ? service.railway.sourceMode : null;
|
|
849
|
+
const configuredSource = service.railway?.source && typeof service.railway.source === "object" && !Array.isArray(service.railway.source) ? service.railway.source : {};
|
|
850
|
+
const configuredRepo = typeof service.railway?.sourceRepo === "string" ? service.railway.sourceRepo : typeof configuredSource.repository === "string" ? configuredSource.repository : typeof configuredSource.repo === "string" ? configuredSource.repo : null;
|
|
851
|
+
const packageRepository = configuredRepo ?? readTreeseedPackageRepository(serviceRoot) ?? readTreeseedPackageRepository(tenantRoot);
|
|
852
|
+
const sourceEligible = ["api", "operationsRunner", "capacityProviderManager", "capacityProviderRunner"].includes(serviceKey);
|
|
853
|
+
const sourceMode = scope === "prod" ? "image" : configuredMode === "git" || configuredMode === "image" ? configuredMode : imageRef ? "image" : scope === "staging" && sourceEligible && packageRepository ? "git" : "git";
|
|
854
|
+
if (sourceMode !== "git") {
|
|
855
|
+
return {
|
|
856
|
+
sourceMode: "image",
|
|
857
|
+
sourceRepo: null,
|
|
858
|
+
sourceBranch: null,
|
|
859
|
+
sourceCommit: null,
|
|
860
|
+
sourceRootDirectory: null
|
|
861
|
+
};
|
|
862
|
+
}
|
|
863
|
+
return {
|
|
864
|
+
sourceMode: "git",
|
|
865
|
+
sourceRepo: packageRepository,
|
|
866
|
+
sourceBranch: typeof service.railway?.sourceBranch === "string" ? service.railway.sourceBranch : typeof configuredSource.branch === "string" ? configuredSource.branch : scope === "staging" ? "staging" : null,
|
|
867
|
+
sourceCommit: headCommitSafe(serviceRoot),
|
|
868
|
+
sourceRootDirectory: typeof service.railway?.sourceRootDirectory === "string" ? service.railway.sourceRootDirectory : typeof configuredSource.rootDirectory === "string" ? configuredSource.rootDirectory : "."
|
|
869
|
+
};
|
|
870
|
+
}
|
|
871
|
+
function resolveRailwayCapacityProviderRoot(tenantRoot, service) {
|
|
872
|
+
if (service.railway?.rootDir) {
|
|
873
|
+
return resolve(tenantRoot, service.railway.rootDir);
|
|
874
|
+
}
|
|
875
|
+
const candidates = [
|
|
876
|
+
resolve(tenantRoot, "..", "agent"),
|
|
877
|
+
resolve(tenantRoot, "packages", "agent"),
|
|
878
|
+
resolve(tenantRoot, "..", "..", "packages", "agent")
|
|
879
|
+
];
|
|
880
|
+
const found = candidates.find(
|
|
881
|
+
(candidate) => existsSync(resolve(candidate, "treeseed.package.yaml")) || existsSync(resolve(candidate, "package.json"))
|
|
882
|
+
);
|
|
883
|
+
return found ?? resolve(tenantRoot, "packages", "agent");
|
|
884
|
+
}
|
|
885
|
+
function configuredRailwayServices(tenantRoot, scope) {
|
|
886
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
887
|
+
const direct = configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig);
|
|
888
|
+
const nested = discoverTreeseedApplications(tenantRoot).filter((application) => application.root !== resolve(tenantRoot)).flatMap((application) => configuredRailwayServicesForConfig(
|
|
889
|
+
application.root,
|
|
890
|
+
scope,
|
|
891
|
+
application.config,
|
|
892
|
+
{
|
|
893
|
+
id: application.id,
|
|
894
|
+
root: application.root,
|
|
895
|
+
relativeRoot: application.relativeRoot
|
|
896
|
+
},
|
|
897
|
+
tenantRoot
|
|
898
|
+
));
|
|
899
|
+
return [...direct, ...nested];
|
|
995
900
|
}
|
|
996
901
|
function configuredRailwayScheduledJobs(tenantRoot, scope, { phase = "deploy" } = {}) {
|
|
997
902
|
if (!shouldManageRailwaySchedules(scope, phase)) {
|
|
@@ -1087,7 +992,11 @@ function validateRailwayServiceConfiguration(tenantRoot, scope) {
|
|
|
1087
992
|
if (!service.projectName && !service.projectId) {
|
|
1088
993
|
issues.push(`${service.key}: set railway.projectName or railway.projectId in treeseed.site.yaml.`);
|
|
1089
994
|
}
|
|
1090
|
-
if (!
|
|
995
|
+
if (service.sourceMode === "git" && !service.sourceRepo) {
|
|
996
|
+
issues.push(`${service.key}: staging source builds require railway.source.repository or package repository metadata.`);
|
|
997
|
+
}
|
|
998
|
+
const usesExternalGitSource = service.sourceMode === "git" && Boolean(service.sourceRepo);
|
|
999
|
+
if (!service.imageRef && !usesExternalGitSource && !existsSync(service.rootDir)) {
|
|
1091
1000
|
issues.push(`${service.key}: service root ${service.rootDir} does not exist.`);
|
|
1092
1001
|
}
|
|
1093
1002
|
if (service.schedule?.length && !service.startCommand) {
|
|
@@ -1109,7 +1018,7 @@ function validateRailwayDeployPrerequisites(tenantRoot, scope, { env = process.e
|
|
|
1109
1018
|
const validation = validateRailwayServiceConfiguration(tenantRoot, scope);
|
|
1110
1019
|
const token = resolveRailwayAuthToken(env);
|
|
1111
1020
|
if (typeof token !== "string" || token.trim().length === 0) {
|
|
1112
|
-
throw new Error("Configure
|
|
1021
|
+
throw new Error("Configure TREESEED_RAILWAY_API_TOKEN before deploying Railway-managed services.");
|
|
1113
1022
|
}
|
|
1114
1023
|
return validation;
|
|
1115
1024
|
}
|
|
@@ -1121,7 +1030,7 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
|
|
|
1121
1030
|
const effectiveApiToken = apiToken || resolveRailwayAuthToken(env);
|
|
1122
1031
|
const effectiveApiUrl = apiUrl || resolveRailwayApiUrl(env);
|
|
1123
1032
|
if (typeof effectiveApiToken !== "string" || effectiveApiToken.trim().length === 0) {
|
|
1124
|
-
throw new Error("Configure
|
|
1033
|
+
throw new Error("Configure TREESEED_RAILWAY_API_TOKEN before deploying Railway-managed services.");
|
|
1125
1034
|
}
|
|
1126
1035
|
const results = [];
|
|
1127
1036
|
try {
|
|
@@ -1147,7 +1056,7 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
|
|
|
1147
1056
|
const current = await getRailwayServiceInstance({
|
|
1148
1057
|
serviceId: target.service.id,
|
|
1149
1058
|
environmentId: target.environment.id,
|
|
1150
|
-
env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
|
|
1059
|
+
env: { ...env, TREESEED_RAILWAY_API_TOKEN: effectiveApiToken, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
|
|
1151
1060
|
fetchImpl
|
|
1152
1061
|
});
|
|
1153
1062
|
const desired = {
|
|
@@ -1177,7 +1086,7 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
|
|
|
1177
1086
|
environmentId: target.environment.id,
|
|
1178
1087
|
startCommand: desired.command,
|
|
1179
1088
|
cronSchedule: desired.schedule,
|
|
1180
|
-
env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
|
|
1089
|
+
env: { ...env, TREESEED_RAILWAY_API_TOKEN: effectiveApiToken, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
|
|
1181
1090
|
fetchImpl
|
|
1182
1091
|
});
|
|
1183
1092
|
results.push({
|
|
@@ -1207,7 +1116,7 @@ async function ensureRailwayScheduledJobs(tenantRoot, scope, { dryRun = false, f
|
|
|
1207
1116
|
return results;
|
|
1208
1117
|
}
|
|
1209
1118
|
async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch, apiToken, apiUrl, env = process.env } = {}) {
|
|
1210
|
-
const effectiveApiToken = apiToken || env
|
|
1119
|
+
const effectiveApiToken = apiToken || resolveRailwayAuthToken(env);
|
|
1211
1120
|
const effectiveApiUrl = apiUrl || resolveRailwayApiUrl(env);
|
|
1212
1121
|
const configured = configuredRailwayScheduledJobs(tenantRoot, scope);
|
|
1213
1122
|
const checks = [];
|
|
@@ -1231,7 +1140,7 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
|
|
|
1231
1140
|
const existing = await getRailwayServiceInstance({
|
|
1232
1141
|
serviceId: target.service.id,
|
|
1233
1142
|
environmentId: target.environment.id,
|
|
1234
|
-
env: { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
|
|
1143
|
+
env: { ...env, TREESEED_RAILWAY_API_TOKEN: effectiveApiToken, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl },
|
|
1235
1144
|
fetchImpl
|
|
1236
1145
|
});
|
|
1237
1146
|
checks.push({
|
|
@@ -1286,7 +1195,7 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
|
|
|
1286
1195
|
} = {}) {
|
|
1287
1196
|
const effectiveApiToken = apiToken || resolveRailwayAuthToken(env);
|
|
1288
1197
|
const effectiveApiUrl = apiUrl || resolveRailwayApiUrl(env);
|
|
1289
|
-
const effectiveEnv = { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl };
|
|
1198
|
+
const effectiveEnv = { ...env, TREESEED_RAILWAY_API_TOKEN: effectiveApiToken, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl };
|
|
1290
1199
|
const services = configuredRailwayServices(tenantRoot, scope);
|
|
1291
1200
|
const checks = [];
|
|
1292
1201
|
const deploymentStatusServices = [];
|
|
@@ -1346,18 +1255,19 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
|
|
|
1346
1255
|
} : null,
|
|
1347
1256
|
message: instance.id ? void 0 : `Railway service instance for ${target.service.name} is missing in ${target.environment.name}.`
|
|
1348
1257
|
});
|
|
1349
|
-
|
|
1350
|
-
|
|
1258
|
+
const expectedVolumeMountPath = service.volumeMountPath ?? service.runnerPool?.volumeMountPath ?? null;
|
|
1259
|
+
if (expectedVolumeMountPath) {
|
|
1260
|
+
const expectedVolumeName = service.key === "operationsRunner" ? deriveRailwayOperationsRunnerVolumeName(target.service.name, target.environment.name) : service.key === "capacityProviderRunner" ? deriveRailwayCapacityProviderRunnerVolumeName(target.service.name, target.environment.name) : deriveRailwayWorkerRunnerVolumeName(target.service.name, target.environment.name);
|
|
1351
1261
|
const volumes = await listRailwayVolumes({
|
|
1352
1262
|
projectId: target.project.id,
|
|
1353
1263
|
env: effectiveEnv,
|
|
1354
1264
|
fetchImpl
|
|
1355
1265
|
});
|
|
1356
1266
|
const volume = volumes.find(
|
|
1357
|
-
(candidate) => candidate.name === expectedVolumeName && candidate.instances.some((entry) => entry.serviceId === target.service.id && entry.environmentId === target.environment.id && entry.mountPath ===
|
|
1267
|
+
(candidate) => candidate.name === expectedVolumeName && candidate.instances.some((entry) => entry.serviceId === target.service.id && entry.environmentId === target.environment.id && entry.mountPath === expectedVolumeMountPath)
|
|
1358
1268
|
) ?? null;
|
|
1359
1269
|
checks.push({
|
|
1360
|
-
type: "
|
|
1270
|
+
type: "service-volume",
|
|
1361
1271
|
service: service.key,
|
|
1362
1272
|
serviceName: target.service.name,
|
|
1363
1273
|
serviceId: target.service.id,
|
|
@@ -1365,7 +1275,7 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
|
|
|
1365
1275
|
environment: target.environment.name,
|
|
1366
1276
|
environmentId: target.environment.id,
|
|
1367
1277
|
volumeName: expectedVolumeName,
|
|
1368
|
-
mountPath:
|
|
1278
|
+
mountPath: expectedVolumeMountPath,
|
|
1369
1279
|
ok: Boolean(volume),
|
|
1370
1280
|
status: volume ? "checked" : "missing",
|
|
1371
1281
|
observed: volume ? {
|
|
@@ -1373,7 +1283,7 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
|
|
|
1373
1283
|
name: volume.name,
|
|
1374
1284
|
instances: volume.instances
|
|
1375
1285
|
} : null,
|
|
1376
|
-
message: volume ? void 0 : `Railway
|
|
1286
|
+
message: volume ? void 0 : `Railway volume ${expectedVolumeName} is missing or is not mounted on ${target.service.name} at ${expectedVolumeMountPath}.`
|
|
1377
1287
|
});
|
|
1378
1288
|
}
|
|
1379
1289
|
}
|
|
@@ -1407,29 +1317,34 @@ async function verifyRailwayManagedResources(tenantRoot, scope, {
|
|
|
1407
1317
|
checks
|
|
1408
1318
|
};
|
|
1409
1319
|
}
|
|
1410
|
-
function
|
|
1411
|
-
const configured = configuredEnvValue(env, "
|
|
1412
|
-
if (configured
|
|
1413
|
-
return
|
|
1414
|
-
}
|
|
1415
|
-
if (configured === "0" || configured === "false") {
|
|
1416
|
-
return false;
|
|
1320
|
+
function railwayPhaseTimeoutMs(env = process.env, phase = "default") {
|
|
1321
|
+
const configured = Number.parseInt(configuredEnvValue(env, `TREESEED_RAILWAY_${String(phase).toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}_TIMEOUT_MS`), 10);
|
|
1322
|
+
if (Number.isFinite(configured) && configured > 0) {
|
|
1323
|
+
return configured;
|
|
1417
1324
|
}
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_VERBOSE");
|
|
1422
|
-
if (configured === "1" || configured === "true") {
|
|
1423
|
-
return true;
|
|
1325
|
+
const defaultConfigured = Number.parseInt(configuredEnvValue(env, "TREESEED_RAILWAY_PHASE_TIMEOUT_MS"), 10);
|
|
1326
|
+
if (Number.isFinite(defaultConfigured) && defaultConfigured > 0) {
|
|
1327
|
+
return defaultConfigured;
|
|
1424
1328
|
}
|
|
1425
|
-
if (
|
|
1426
|
-
return
|
|
1329
|
+
if (phase === "sync_runtime_config") {
|
|
1330
|
+
return 6e5;
|
|
1427
1331
|
}
|
|
1428
|
-
return
|
|
1332
|
+
return phase === "deploy" ? 3e5 : 18e4;
|
|
1429
1333
|
}
|
|
1430
|
-
function
|
|
1431
|
-
|
|
1432
|
-
|
|
1334
|
+
async function withRailwayPhaseTimeout(run, timeoutMs, message) {
|
|
1335
|
+
let timer = null;
|
|
1336
|
+
try {
|
|
1337
|
+
return await Promise.race([
|
|
1338
|
+
Promise.resolve().then(run),
|
|
1339
|
+
new Promise((_, reject) => {
|
|
1340
|
+
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
1341
|
+
})
|
|
1342
|
+
]);
|
|
1343
|
+
} finally {
|
|
1344
|
+
if (timer) {
|
|
1345
|
+
clearTimeout(timer);
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1433
1348
|
}
|
|
1434
1349
|
function shouldRunRailwayPredeployBuild(env = process.env) {
|
|
1435
1350
|
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_PREDEPLOY_BUILD");
|
|
@@ -1441,56 +1356,6 @@ function shouldRunRailwayPredeployBuild(env = process.env) {
|
|
|
1441
1356
|
}
|
|
1442
1357
|
return configuredEnvValue(env, "CI") !== "true";
|
|
1443
1358
|
}
|
|
1444
|
-
function planRailwayServiceDeploy(service, { env = process.env, projectTokenMode = false } = {}) {
|
|
1445
|
-
const serviceSelector = service.serviceName ?? service.serviceId;
|
|
1446
|
-
const args = ["up"];
|
|
1447
|
-
if (serviceSelector) {
|
|
1448
|
-
args.push("--service", serviceSelector);
|
|
1449
|
-
}
|
|
1450
|
-
if (shouldIncludeRailwayIgnoredFiles(env)) {
|
|
1451
|
-
args.push("--no-gitignore");
|
|
1452
|
-
}
|
|
1453
|
-
args.push(shouldAttachRailwayDeployLogs(env) ? "--ci" : "--detach");
|
|
1454
|
-
if (shouldUseVerboseRailwayDeploy(env)) {
|
|
1455
|
-
args.push("--verbose");
|
|
1456
|
-
}
|
|
1457
|
-
if (!projectTokenMode && service.projectId) {
|
|
1458
|
-
args.push("--project", service.projectId);
|
|
1459
|
-
}
|
|
1460
|
-
const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
|
|
1461
|
-
if (!projectTokenMode && environmentName) {
|
|
1462
|
-
args.push("--environment", environmentName);
|
|
1463
|
-
}
|
|
1464
|
-
return {
|
|
1465
|
-
command: "railway",
|
|
1466
|
-
args,
|
|
1467
|
-
cwd: service.rootDir
|
|
1468
|
-
};
|
|
1469
|
-
}
|
|
1470
|
-
function planRailwayProjectEnvironmentLink(service) {
|
|
1471
|
-
const args = ["link"];
|
|
1472
|
-
if (service.projectId) {
|
|
1473
|
-
args.push("--project", service.projectId);
|
|
1474
|
-
}
|
|
1475
|
-
const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
|
|
1476
|
-
if (environmentName) {
|
|
1477
|
-
args.push("--environment", environmentName);
|
|
1478
|
-
}
|
|
1479
|
-
args.push("--json");
|
|
1480
|
-
return {
|
|
1481
|
-
command: "railway",
|
|
1482
|
-
args,
|
|
1483
|
-
cwd: service.rootDir
|
|
1484
|
-
};
|
|
1485
|
-
}
|
|
1486
|
-
function buildRailwayCliContextEnv(env, service) {
|
|
1487
|
-
return {
|
|
1488
|
-
...env,
|
|
1489
|
-
RAILWAY_PROJECT_ID: configuredEnvValue(service, "projectId") || configuredEnvValue(env, "RAILWAY_PROJECT_ID") || void 0,
|
|
1490
|
-
RAILWAY_ENVIRONMENT_ID: configuredEnvValue(service, "environmentId") || configuredEnvValue(env, "RAILWAY_ENVIRONMENT_ID") || void 0,
|
|
1491
|
-
RAILWAY_SERVICE_ID: configuredEnvValue(service, "serviceId") || configuredEnvValue(env, "RAILWAY_SERVICE_ID") || void 0
|
|
1492
|
-
};
|
|
1493
|
-
}
|
|
1494
1359
|
async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
|
|
1495
1360
|
if (service.key !== "api") {
|
|
1496
1361
|
return null;
|
|
@@ -1505,7 +1370,7 @@ async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
|
|
|
1505
1370
|
[
|
|
1506
1371
|
"TREESEED_API_AUTH_APPROVAL_BASE_URL",
|
|
1507
1372
|
"TREESEED_SITE_URL",
|
|
1508
|
-
"
|
|
1373
|
+
"TREESEED_BETTER_AUTH_URL"
|
|
1509
1374
|
].map((key) => [key, configuredEnvValue(env, key)]).filter(([, value]) => value)
|
|
1510
1375
|
);
|
|
1511
1376
|
if (Object.keys(variables).length === 0) {
|
|
@@ -1521,166 +1386,6 @@ async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
|
|
|
1521
1386
|
write ? write(`[${prefix.scope}][${prefix.system}][${prefix.task}][vars] Synced device login approval URL variables for ${service.serviceName ?? serviceId}.`, "stdout") : null;
|
|
1522
1387
|
return { variables: Object.keys(variables) };
|
|
1523
1388
|
}
|
|
1524
|
-
function buildRailwayLinkCommandEnv(env = process.env, service = {}) {
|
|
1525
|
-
return buildRailwayCliContextEnv({
|
|
1526
|
-
...buildRailwayDeployCommandEnv({ ...env, RAILWAY_TOKEN: void 0 }),
|
|
1527
|
-
RAILWAY_TOKEN: void 0
|
|
1528
|
-
}, service);
|
|
1529
|
-
}
|
|
1530
|
-
function railwayCliConfigPath(env = process.env) {
|
|
1531
|
-
const railwayHome = configuredEnvValue(env, "RAILWAY_HOME");
|
|
1532
|
-
if (railwayHome) {
|
|
1533
|
-
return resolve(railwayHome, "config.json");
|
|
1534
|
-
}
|
|
1535
|
-
const home = configuredEnvValue(env, "HOME");
|
|
1536
|
-
if (!home) {
|
|
1537
|
-
return "";
|
|
1538
|
-
}
|
|
1539
|
-
return resolve(home, ".railway", "config.json");
|
|
1540
|
-
}
|
|
1541
|
-
function readRailwayCliConfig(configPath) {
|
|
1542
|
-
if (!configPath || !existsSync(configPath)) {
|
|
1543
|
-
return {};
|
|
1544
|
-
}
|
|
1545
|
-
try {
|
|
1546
|
-
const parsed = JSON.parse(readFileSync(configPath, "utf8"));
|
|
1547
|
-
return parsed && typeof parsed === "object" ? parsed : {};
|
|
1548
|
-
} catch {
|
|
1549
|
-
return {};
|
|
1550
|
-
}
|
|
1551
|
-
}
|
|
1552
|
-
function writeRailwayCliProjectConfig(service, { env = process.env, cwd = service.rootDir } = {}) {
|
|
1553
|
-
const projectId = configuredEnvValue(service, "projectId");
|
|
1554
|
-
const environmentId = configuredEnvValue(service, "environmentId");
|
|
1555
|
-
const serviceId = configuredEnvValue(service, "serviceId");
|
|
1556
|
-
const projectPath = cwd ? resolve(cwd) : "";
|
|
1557
|
-
const configPath = railwayCliConfigPath(env);
|
|
1558
|
-
if (!configPath || !projectPath || !projectId || !environmentId || !serviceId) {
|
|
1559
|
-
return null;
|
|
1560
|
-
}
|
|
1561
|
-
const existing = readRailwayCliConfig(configPath);
|
|
1562
|
-
const projects = existing.projects && typeof existing.projects === "object" ? existing.projects : {};
|
|
1563
|
-
const next = {
|
|
1564
|
-
...existing,
|
|
1565
|
-
projects: {
|
|
1566
|
-
...projects,
|
|
1567
|
-
[projectPath]: {
|
|
1568
|
-
projectPath,
|
|
1569
|
-
name: configuredEnvValue(service, "projectName") || configuredEnvValue(service, "serviceName") || projectId,
|
|
1570
|
-
project: projectId,
|
|
1571
|
-
environment: environmentId,
|
|
1572
|
-
environmentName: normalizeRailwayEnvironmentName(service.railwayEnvironment) || configuredEnvValue(service, "environmentName") || environmentId,
|
|
1573
|
-
service: serviceId
|
|
1574
|
-
}
|
|
1575
|
-
},
|
|
1576
|
-
user: existing.user && typeof existing.user === "object" ? existing.user : {
|
|
1577
|
-
token: null,
|
|
1578
|
-
accessToken: null,
|
|
1579
|
-
refreshToken: null,
|
|
1580
|
-
tokenExpiresAt: null
|
|
1581
|
-
},
|
|
1582
|
-
linkedFunctions: existing.linkedFunctions ?? null
|
|
1583
|
-
};
|
|
1584
|
-
mkdirSync(dirname(configPath), { recursive: true });
|
|
1585
|
-
writeFileSync(configPath, `${JSON.stringify(next, null, 2)}
|
|
1586
|
-
`, "utf8");
|
|
1587
|
-
return {
|
|
1588
|
-
configPath,
|
|
1589
|
-
projectPath,
|
|
1590
|
-
projectId,
|
|
1591
|
-
environmentId,
|
|
1592
|
-
serviceId
|
|
1593
|
-
};
|
|
1594
|
-
}
|
|
1595
|
-
function planRailwayServiceLink(service, { env = process.env } = {}) {
|
|
1596
|
-
const args = ["link"];
|
|
1597
|
-
if (service.projectId) {
|
|
1598
|
-
args.push("--project", service.projectId);
|
|
1599
|
-
}
|
|
1600
|
-
const workspace = resolveRailwayWorkspace(env);
|
|
1601
|
-
if (workspace) {
|
|
1602
|
-
args.push("--workspace", workspace);
|
|
1603
|
-
}
|
|
1604
|
-
const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
|
|
1605
|
-
if (environmentName) {
|
|
1606
|
-
args.push("--environment", environmentName);
|
|
1607
|
-
}
|
|
1608
|
-
const serviceSelector = service.serviceName ?? service.serviceId;
|
|
1609
|
-
if (serviceSelector) {
|
|
1610
|
-
args.push("--service", serviceSelector);
|
|
1611
|
-
}
|
|
1612
|
-
args.push("--json");
|
|
1613
|
-
return {
|
|
1614
|
-
command: "railway",
|
|
1615
|
-
args,
|
|
1616
|
-
cwd: service.rootDir
|
|
1617
|
-
};
|
|
1618
|
-
}
|
|
1619
|
-
function railwayProjectTokenName(service) {
|
|
1620
|
-
const environment = normalizeRailwayEnvironmentName(service.railwayEnvironment) || "environment";
|
|
1621
|
-
const serviceName = String(service.serviceName ?? service.serviceId ?? service.key ?? "service").toLowerCase().replace(/[^a-z0-9-]+/gu, "-").replace(/^-+|-+$/gu, "").slice(0, 48) || "service";
|
|
1622
|
-
return `treeseed-ci-${environment}-${serviceName}`;
|
|
1623
|
-
}
|
|
1624
|
-
async function createRailwayCliProjectToken(service, { env = process.env } = {}) {
|
|
1625
|
-
const projectId = configuredEnvValue(service, "projectId");
|
|
1626
|
-
const environmentId = configuredEnvValue(service, "environmentId");
|
|
1627
|
-
if (!projectId || !environmentId) {
|
|
1628
|
-
return "";
|
|
1629
|
-
}
|
|
1630
|
-
const name = railwayProjectTokenName(service);
|
|
1631
|
-
try {
|
|
1632
|
-
const existingPayload = await railwayGraphqlRequest({
|
|
1633
|
-
query: `
|
|
1634
|
-
query TreeseedProjectTokens($projectId: String!) {
|
|
1635
|
-
projectTokens(projectId: $projectId, first: 100) {
|
|
1636
|
-
edges {
|
|
1637
|
-
node {
|
|
1638
|
-
id
|
|
1639
|
-
name
|
|
1640
|
-
projectId
|
|
1641
|
-
environmentId
|
|
1642
|
-
}
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
}
|
|
1646
|
-
`.trim(),
|
|
1647
|
-
variables: { projectId },
|
|
1648
|
-
env
|
|
1649
|
-
});
|
|
1650
|
-
const existingTokens = railwayEdgeNodes(existingPayload.data?.projectTokens);
|
|
1651
|
-
for (const token2 of existingTokens) {
|
|
1652
|
-
if (token2?.name === name && token2?.projectId === projectId && token2?.environmentId === environmentId && token2?.id) {
|
|
1653
|
-
await railwayGraphqlRequest({
|
|
1654
|
-
query: `
|
|
1655
|
-
mutation TreeseedProjectTokenDelete($id: String!) {
|
|
1656
|
-
projectTokenDelete(id: $id)
|
|
1657
|
-
}
|
|
1658
|
-
`.trim(),
|
|
1659
|
-
variables: { id: token2.id },
|
|
1660
|
-
env
|
|
1661
|
-
});
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
} catch {
|
|
1665
|
-
}
|
|
1666
|
-
const payload = await railwayGraphqlRequest({
|
|
1667
|
-
query: `
|
|
1668
|
-
mutation TreeseedProjectTokenCreate($input: ProjectTokenCreateInput!) {
|
|
1669
|
-
projectTokenCreate(input: $input)
|
|
1670
|
-
}
|
|
1671
|
-
`.trim(),
|
|
1672
|
-
variables: {
|
|
1673
|
-
input: {
|
|
1674
|
-
projectId,
|
|
1675
|
-
environmentId,
|
|
1676
|
-
name
|
|
1677
|
-
}
|
|
1678
|
-
},
|
|
1679
|
-
env
|
|
1680
|
-
});
|
|
1681
|
-
const token = payload.data?.projectTokenCreate;
|
|
1682
|
-
return typeof token === "string" && token.trim() ? token.trim() : "";
|
|
1683
|
-
}
|
|
1684
1389
|
async function resolveRailwayDeployProjectContext(service, { env = process.env } = {}) {
|
|
1685
1390
|
if (service.projectId) {
|
|
1686
1391
|
return service;
|
|
@@ -1699,15 +1404,23 @@ async function resolveRailwayDeployProjectContext(service, { env = process.env }
|
|
|
1699
1404
|
projectName: project.name ?? service.projectName
|
|
1700
1405
|
};
|
|
1701
1406
|
}
|
|
1702
|
-
async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env } = {}) {
|
|
1703
|
-
const
|
|
1704
|
-
|
|
1407
|
+
async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env, writePhase = null } = {}) {
|
|
1408
|
+
const writeSyncPhase = (stage, message) => {
|
|
1409
|
+
if (typeof writePhase === "function") {
|
|
1410
|
+
writePhase(`sync-runtime-config:${stage}`, message);
|
|
1411
|
+
}
|
|
1412
|
+
};
|
|
1413
|
+
const wantsInstanceConfig = service.buildCommand || service.startCommand || !(service.imageRef || service.sourceMode === "image") && service.rootDir || service.healthcheckPath || service.healthcheckTimeoutSeconds !== null || service.healthcheckTimeoutSeconds !== void 0 || service.healthcheckIntervalSeconds !== null || service.healthcheckIntervalSeconds !== void 0 || service.restartPolicy || service.runtimeMode;
|
|
1414
|
+
const wantsRunnerVolume = service.key === "workerRunner" || service.key === "operationsRunner" || service.key === "capacityProviderRunner";
|
|
1705
1415
|
if (!wantsInstanceConfig && !wantsRunnerVolume) {
|
|
1416
|
+
writeSyncPhase("skip", "No runtime configuration changes requested.");
|
|
1706
1417
|
return null;
|
|
1707
1418
|
}
|
|
1419
|
+
writeSyncPhase("workspace", "Resolving Railway workspace.");
|
|
1708
1420
|
const workspace = await resolveRailwayWorkspaceContext({ env });
|
|
1709
1421
|
let project = null;
|
|
1710
1422
|
if (service.projectId) {
|
|
1423
|
+
writeSyncPhase("project", `Resolving Railway project ${service.projectName ?? service.projectId}.`);
|
|
1711
1424
|
project = await ensureRailwayProject({
|
|
1712
1425
|
projectId: service.projectId,
|
|
1713
1426
|
projectName: service.projectName,
|
|
@@ -1716,9 +1429,11 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1716
1429
|
workspace: workspace.id
|
|
1717
1430
|
}).then((result) => result.project);
|
|
1718
1431
|
} else {
|
|
1432
|
+
writeSyncPhase("project", `Looking up Railway project ${service.projectName}.`);
|
|
1719
1433
|
const projects = await listRailwayProjects({ env, workspaceId: workspace.id });
|
|
1720
1434
|
project = projects.find((entry) => entry.name === service.projectName) ?? null;
|
|
1721
1435
|
if (!project) {
|
|
1436
|
+
writeSyncPhase("project", `Creating Railway project ${service.projectName}.`);
|
|
1722
1437
|
project = await ensureRailwayProject({
|
|
1723
1438
|
projectName: service.projectName,
|
|
1724
1439
|
defaultEnvironmentName: service.railwayEnvironment,
|
|
@@ -1730,6 +1445,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1730
1445
|
const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
|
|
1731
1446
|
let environment = project.environments.find((entry) => entry.name === environmentName || entry.id === environmentName) ?? null;
|
|
1732
1447
|
if (!environment) {
|
|
1448
|
+
writeSyncPhase("environment", `Creating Railway environment ${environmentName}.`);
|
|
1733
1449
|
environment = await ensureRailwayEnvironment({
|
|
1734
1450
|
projectId: project.id,
|
|
1735
1451
|
environmentName,
|
|
@@ -1738,63 +1454,107 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1738
1454
|
}
|
|
1739
1455
|
let railwayService = project.services.find((entry) => entry.id === service.serviceId || entry.name === service.serviceName) ?? null;
|
|
1740
1456
|
if (!railwayService) {
|
|
1457
|
+
writeSyncPhase("service", `Creating Railway service ${service.serviceName ?? service.key}.`);
|
|
1741
1458
|
railwayService = await ensureRailwayService({
|
|
1742
1459
|
projectId: project.id,
|
|
1743
1460
|
serviceId: service.serviceId,
|
|
1744
1461
|
serviceName: service.serviceName,
|
|
1462
|
+
environmentId: environment.id,
|
|
1463
|
+
imageRef: service.imageRef,
|
|
1464
|
+
sourceRepo: service.sourceRepo,
|
|
1465
|
+
sourceBranch: service.sourceBranch,
|
|
1466
|
+
env
|
|
1467
|
+
}).then((result) => result.service);
|
|
1468
|
+
} else if (service.imageRef) {
|
|
1469
|
+
railwayService = await ensureRailwayService({
|
|
1470
|
+
projectId: project.id,
|
|
1471
|
+
serviceId: service.serviceId,
|
|
1472
|
+
serviceName: service.serviceName,
|
|
1473
|
+
environmentId: environment.id,
|
|
1474
|
+
imageRef: service.imageRef,
|
|
1475
|
+
sourceRepo: service.sourceRepo,
|
|
1476
|
+
sourceBranch: service.sourceBranch,
|
|
1745
1477
|
env
|
|
1746
1478
|
}).then((result) => result.service);
|
|
1747
1479
|
}
|
|
1480
|
+
if (wantsInstanceConfig) {
|
|
1481
|
+
writeSyncPhase("instance", "Ensuring Railway service instance configuration.");
|
|
1482
|
+
}
|
|
1748
1483
|
const runtimeConfiguration = wantsInstanceConfig ? await ensureRailwayServiceInstanceConfiguration({
|
|
1749
1484
|
serviceId: railwayService.id,
|
|
1750
1485
|
environmentId: environment.id,
|
|
1751
1486
|
buildCommand: service.buildCommand,
|
|
1752
1487
|
startCommand: railwayServiceRuntimeStartCommand(service),
|
|
1753
1488
|
cronSchedule: service.schedule?.[0] ?? null,
|
|
1754
|
-
rootDirectory:
|
|
1489
|
+
rootDirectory: service.imageRef || service.sourceMode === "image" ? null : service.sourceRootDirectory ?? ".",
|
|
1755
1490
|
healthcheckPath: service.healthcheckPath,
|
|
1756
1491
|
healthcheckTimeoutSeconds: service.healthcheckTimeoutSeconds,
|
|
1757
1492
|
healthcheckIntervalSeconds: service.healthcheckIntervalSeconds,
|
|
1758
1493
|
restartPolicy: service.restartPolicy,
|
|
1759
1494
|
runtimeMode: service.runtimeMode,
|
|
1495
|
+
deploymentRegion: wantsRunnerVolume ? configuredEnvValue(env, "TREESEED_RAILWAY_STATEFUL_REGION") || "us-west2" : null,
|
|
1760
1496
|
env
|
|
1761
1497
|
}) : null;
|
|
1498
|
+
writeSyncPhase("variables", "Upserting Railway runtime variables.");
|
|
1762
1499
|
await upsertRailwayVariables({
|
|
1763
1500
|
projectId: project.id,
|
|
1764
1501
|
environmentId: environment.id,
|
|
1765
1502
|
serviceId: railwayService.id,
|
|
1766
1503
|
variables: {
|
|
1767
1504
|
TREESEED_SKIP_PACKAGE_PREPARE: "1",
|
|
1768
|
-
...service.
|
|
1505
|
+
...service.sourceMode === "git" ? {
|
|
1506
|
+
TREESEED_DEPLOY_SOURCE_MODE: "git",
|
|
1507
|
+
...service.sourceRepo ? { TREESEED_DEPLOY_SOURCE_REPOSITORY: service.sourceRepo } : {},
|
|
1508
|
+
...service.sourceBranch ? { TREESEED_DEPLOY_SOURCE_BRANCH: service.sourceBranch } : {},
|
|
1509
|
+
...service.sourceCommit ? { TREESEED_DEPLOY_SOURCE_COMMIT: service.sourceCommit } : {}
|
|
1510
|
+
} : {
|
|
1511
|
+
TREESEED_DEPLOY_SOURCE_MODE: "image"
|
|
1512
|
+
},
|
|
1513
|
+
...service.key === "operationsRunner" ? {
|
|
1769
1514
|
NIXPACKS_APT_PKGS: "git",
|
|
1770
1515
|
NIXPACKS_PKGS: "git",
|
|
1771
1516
|
TREESEED_PLATFORM_RUNNER_ID: service.runnerId ?? railwayService.name,
|
|
1772
1517
|
TREESEED_PLATFORM_RUNNER_DATA_DIR: service.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH,
|
|
1773
1518
|
TREESEED_PLATFORM_RUNNER_ENVIRONMENT: normalizeScope(service.scope) === "prod" ? "production" : normalizeScope(service.scope),
|
|
1774
|
-
|
|
1519
|
+
TREESEED_MANAGER_ID: normalizeScope(service.scope),
|
|
1520
|
+
...configuredEnvValue(env, "TREESEED_RAILWAY_API_TOKEN") ? { TREESEED_RAILWAY_API_TOKEN: configuredEnvValue(env, "TREESEED_RAILWAY_API_TOKEN") } : {},
|
|
1521
|
+
...configuredEnvValue(env, "TREESEED_RAILWAY_WORKSPACE") ? { TREESEED_RAILWAY_WORKSPACE: configuredEnvValue(env, "TREESEED_RAILWAY_WORKSPACE") } : {},
|
|
1775
1522
|
...configuredEnvValue(env, "TREESEED_PLATFORM_RUNNER_SECRET") ? { TREESEED_PLATFORM_RUNNER_SECRET: configuredEnvValue(env, "TREESEED_PLATFORM_RUNNER_SECRET") } : {},
|
|
1776
|
-
...configuredEnvValue(env, "
|
|
1777
|
-
|
|
1523
|
+
...configuredEnvValue(env, "TREESEED_API_BASE_URL") || configuredEnvValue(env, "TREESEED_URL") ? {
|
|
1524
|
+
TREESEED_API_BASE_URL: configuredEnvValue(env, "TREESEED_API_BASE_URL") || configuredEnvValue(env, "TREESEED_URL")
|
|
1778
1525
|
} : {}
|
|
1526
|
+
} : {},
|
|
1527
|
+
...String(service.key).startsWith("capacityProvider") ? {
|
|
1528
|
+
TREESEED_PROVIDER_ENVIRONMENT: normalizeScope(service.scope) === "prod" ? "production" : normalizeScope(service.scope),
|
|
1529
|
+
TREESEED_MANAGER_ID: normalizeScope(service.scope),
|
|
1530
|
+
TREESEED_MARKET_ID: normalizeScope(service.scope),
|
|
1531
|
+
TREESEED_PROVIDER_ROLE: service.key === "capacityProviderManager" ? "manager" : "runner",
|
|
1532
|
+
...service.key === "capacityProviderRunner" ? {
|
|
1533
|
+
TREESEED_PROVIDER_DATA_DIR: service.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH,
|
|
1534
|
+
TREESEED_PROVIDER_RUNNER_ID: service.runnerId ?? railwayService.name
|
|
1535
|
+
} : {},
|
|
1536
|
+
...configuredEnvValue(env, "TREESEED_MARKET_URL") ? { TREESEED_MARKET_URL: configuredEnvValue(env, "TREESEED_MARKET_URL") } : {},
|
|
1537
|
+
...configuredEnvValue(env, "TREESEED_CAPACITY_PROVIDER_API_KEY") ? { TREESEED_CAPACITY_PROVIDER_API_KEY: configuredEnvValue(env, "TREESEED_CAPACITY_PROVIDER_API_KEY") } : {},
|
|
1538
|
+
...configuredEnvValue(env, "TREESEED_CODEX_AUTH_JSON_B64") ? { TREESEED_CODEX_AUTH_JSON_B64: configuredEnvValue(env, "TREESEED_CODEX_AUTH_JSON_B64") } : {}
|
|
1779
1539
|
} : {}
|
|
1780
1540
|
},
|
|
1781
1541
|
env
|
|
1782
1542
|
});
|
|
1783
1543
|
const volumeMountPath = service.volumeMountPath ?? service.runnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH;
|
|
1784
|
-
|
|
1785
|
-
|
|
1544
|
+
if (wantsRunnerVolume) {
|
|
1545
|
+
writeSyncPhase("volume", `Ensuring Railway volume mounted at ${volumeMountPath}.`);
|
|
1546
|
+
}
|
|
1547
|
+
const volumeConfiguration = wantsRunnerVolume ? await ensureRailwayServiceVolume({
|
|
1786
1548
|
projectId: project.id,
|
|
1787
1549
|
environmentId: environment.id,
|
|
1788
|
-
environmentName: environment.name,
|
|
1789
1550
|
serviceId: railwayService.id,
|
|
1790
|
-
|
|
1791
|
-
name: service.key === "marketOperationsRunner" ? deriveRailwayMarketOperationsRunnerVolumeName(railwayService.name, environment.name) : deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
|
|
1551
|
+
name: service.key === "operationsRunner" ? deriveRailwayOperationsRunnerVolumeName(railwayService.name, environment.name) : service.key === "capacityProviderRunner" ? deriveRailwayCapacityProviderRunnerVolumeName(railwayService.name, environment.name) : deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
|
|
1792
1552
|
mountPath: volumeMountPath,
|
|
1793
|
-
preferCli: service.key === "marketOperationsRunner",
|
|
1794
1553
|
env
|
|
1795
1554
|
}) : null;
|
|
1796
1555
|
if (wantsRunnerVolume) {
|
|
1797
1556
|
if (service.key === "workerRunner") {
|
|
1557
|
+
writeSyncPhase("volume-vars", "Upserting Railway worker volume variables.");
|
|
1798
1558
|
await upsertRailwayVariables({
|
|
1799
1559
|
projectId: project.id,
|
|
1800
1560
|
environmentId: environment.id,
|
|
@@ -1810,6 +1570,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1810
1570
|
});
|
|
1811
1571
|
}
|
|
1812
1572
|
}
|
|
1573
|
+
writeSyncPhase("done", "Runtime configuration is synchronized.");
|
|
1813
1574
|
return {
|
|
1814
1575
|
projectId: project.id,
|
|
1815
1576
|
projectName: project.name ?? service.projectName ?? null,
|
|
@@ -1828,163 +1589,6 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1828
1589
|
} : null
|
|
1829
1590
|
};
|
|
1830
1591
|
}
|
|
1831
|
-
async function ensureRailwayServiceVolumeWithCliFallback({
|
|
1832
|
-
tenantRoot,
|
|
1833
|
-
projectId,
|
|
1834
|
-
environmentId,
|
|
1835
|
-
environmentName,
|
|
1836
|
-
serviceId,
|
|
1837
|
-
serviceName,
|
|
1838
|
-
name,
|
|
1839
|
-
mountPath,
|
|
1840
|
-
preferCli = false,
|
|
1841
|
-
env = process.env
|
|
1842
|
-
}) {
|
|
1843
|
-
if (!preferCli) {
|
|
1844
|
-
try {
|
|
1845
|
-
return await ensureRailwayServiceVolume({
|
|
1846
|
-
projectId,
|
|
1847
|
-
environmentId,
|
|
1848
|
-
serviceId,
|
|
1849
|
-
name,
|
|
1850
|
-
mountPath,
|
|
1851
|
-
env
|
|
1852
|
-
});
|
|
1853
|
-
} catch (error) {
|
|
1854
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
1855
|
-
if (!message.includes("Problem processing request")) {
|
|
1856
|
-
throw error;
|
|
1857
|
-
}
|
|
1858
|
-
}
|
|
1859
|
-
}
|
|
1860
|
-
const cliOptions = {
|
|
1861
|
-
cwd: tenantRoot,
|
|
1862
|
-
capture: true,
|
|
1863
|
-
env
|
|
1864
|
-
};
|
|
1865
|
-
ensureRailwayProjectContext({
|
|
1866
|
-
key: serviceName,
|
|
1867
|
-
projectId,
|
|
1868
|
-
serviceName,
|
|
1869
|
-
rootDir: tenantRoot,
|
|
1870
|
-
railwayEnvironment: environmentName
|
|
1871
|
-
}, {
|
|
1872
|
-
env,
|
|
1873
|
-
capture: true
|
|
1874
|
-
});
|
|
1875
|
-
const volumeArgs = ["volume", "--service", serviceId, "--environment", environmentId];
|
|
1876
|
-
const listResult = runRailway([...volumeArgs, "list", "--json"], cliOptions);
|
|
1877
|
-
const existingVolumes = normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
|
|
1878
|
-
serviceId,
|
|
1879
|
-
serviceName,
|
|
1880
|
-
environmentId,
|
|
1881
|
-
fallbackName: name,
|
|
1882
|
-
fallbackMountPath: mountPath
|
|
1883
|
-
});
|
|
1884
|
-
let volume = existingVolumes.find((entry) => entry.name === name) ?? existingVolumes.find((entry) => entry.instances.some((instance2) => instance2.mountPath === mountPath)) ?? existingVolumes[0] ?? null;
|
|
1885
|
-
let created = false;
|
|
1886
|
-
let updated = false;
|
|
1887
|
-
if (!volume) {
|
|
1888
|
-
const createResult = runRailway([...volumeArgs, "add", "--mount-path", mountPath, "--json"], cliOptions);
|
|
1889
|
-
volume = normalizeRailwayCliVolume(parseRailwayJsonOutput(createResult.stdout ?? ""), {
|
|
1890
|
-
serviceId,
|
|
1891
|
-
serviceName,
|
|
1892
|
-
environmentId,
|
|
1893
|
-
fallbackName: name,
|
|
1894
|
-
fallbackMountPath: mountPath
|
|
1895
|
-
});
|
|
1896
|
-
if (!volume) {
|
|
1897
|
-
throw new Error(`Railway CLI volume add did not return a usable volume for ${serviceName} in ${environmentName}.`);
|
|
1898
|
-
}
|
|
1899
|
-
created = true;
|
|
1900
|
-
}
|
|
1901
|
-
let instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
|
|
1902
|
-
if (!instance || instance.mountPath !== mountPath) {
|
|
1903
|
-
const attachResult = runRailway([...volumeArgs, "attach", "--volume", volume.id, "--yes", "--json"], {
|
|
1904
|
-
...cliOptions,
|
|
1905
|
-
allowFailure: true
|
|
1906
|
-
});
|
|
1907
|
-
if ((attachResult.status ?? 1) !== 0) {
|
|
1908
|
-
const attachMessage = attachResult.stderr?.trim() || attachResult.stdout?.trim() || "";
|
|
1909
|
-
if (!/already mounted/iu.test(attachMessage)) {
|
|
1910
|
-
throw new Error(attachMessage || `Railway volume attach failed for ${serviceName} in ${environmentName}.`);
|
|
1911
|
-
}
|
|
1912
|
-
}
|
|
1913
|
-
const attachedVolume = (attachResult.status ?? 1) === 0 ? normalizeRailwayCliVolume(parseRailwayJsonOutput(attachResult.stdout ?? ""), {
|
|
1914
|
-
serviceId,
|
|
1915
|
-
serviceName,
|
|
1916
|
-
environmentId,
|
|
1917
|
-
fallbackName: name,
|
|
1918
|
-
fallbackMountPath: mountPath
|
|
1919
|
-
}) : null;
|
|
1920
|
-
volume = attachedVolume ?? {
|
|
1921
|
-
...volume,
|
|
1922
|
-
instances: [{
|
|
1923
|
-
...instance ?? {
|
|
1924
|
-
id: volume.id,
|
|
1925
|
-
serviceId,
|
|
1926
|
-
environmentId,
|
|
1927
|
-
state: "READY",
|
|
1928
|
-
sizeGb: null,
|
|
1929
|
-
usedGb: null
|
|
1930
|
-
},
|
|
1931
|
-
serviceId,
|
|
1932
|
-
environmentId,
|
|
1933
|
-
mountPath
|
|
1934
|
-
}]
|
|
1935
|
-
};
|
|
1936
|
-
instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
|
|
1937
|
-
updated = true;
|
|
1938
|
-
}
|
|
1939
|
-
const apiVolume = await waitForRailwayServiceVolumeMount({
|
|
1940
|
-
projectId,
|
|
1941
|
-
volumeId: volume.id,
|
|
1942
|
-
volumeName: name,
|
|
1943
|
-
serviceId,
|
|
1944
|
-
environmentId,
|
|
1945
|
-
mountPath,
|
|
1946
|
-
env
|
|
1947
|
-
});
|
|
1948
|
-
if (apiVolume) {
|
|
1949
|
-
volume = apiVolume;
|
|
1950
|
-
} else {
|
|
1951
|
-
throw new Error(`Railway volume ${name} was not attached to ${serviceName} at ${mountPath}.`);
|
|
1952
|
-
}
|
|
1953
|
-
return {
|
|
1954
|
-
volume,
|
|
1955
|
-
instance: volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null,
|
|
1956
|
-
created,
|
|
1957
|
-
updated
|
|
1958
|
-
};
|
|
1959
|
-
}
|
|
1960
|
-
async function waitForRailwayServiceVolumeMount({
|
|
1961
|
-
projectId,
|
|
1962
|
-
volumeId,
|
|
1963
|
-
volumeName,
|
|
1964
|
-
serviceId,
|
|
1965
|
-
environmentId,
|
|
1966
|
-
mountPath,
|
|
1967
|
-
env
|
|
1968
|
-
}) {
|
|
1969
|
-
for (let attempt = 0; attempt <= 24; attempt += 1) {
|
|
1970
|
-
const volumes = await listRailwayVolumes({ projectId, env });
|
|
1971
|
-
const mounted = volumes.find(
|
|
1972
|
-
(entry) => entry.instances.some(
|
|
1973
|
-
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
|
|
1974
|
-
)
|
|
1975
|
-
) ?? null;
|
|
1976
|
-
const match = mounted ?? volumes.find((entry) => entry.id === volumeId) ?? volumes.find((entry) => entry.name === volumeName) ?? null;
|
|
1977
|
-
if (match?.instances.some(
|
|
1978
|
-
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
|
|
1979
|
-
)) {
|
|
1980
|
-
return match;
|
|
1981
|
-
}
|
|
1982
|
-
if (attempt < 24) {
|
|
1983
|
-
await sleep(5e3);
|
|
1984
|
-
}
|
|
1985
|
-
}
|
|
1986
|
-
return null;
|
|
1987
|
-
}
|
|
1988
1592
|
async function deployRailwayService(tenantRoot, service, {
|
|
1989
1593
|
dryRun = false,
|
|
1990
1594
|
write,
|
|
@@ -1993,34 +1597,42 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1993
1597
|
} = {}) {
|
|
1994
1598
|
const timings = [];
|
|
1995
1599
|
if (dryRun) {
|
|
1996
|
-
const plan2 = planRailwayServiceDeploy(service, { env });
|
|
1997
1600
|
return {
|
|
1998
1601
|
service: service.key,
|
|
1999
1602
|
status: "planned",
|
|
2000
|
-
command:
|
|
2001
|
-
cwd:
|
|
1603
|
+
command: "railway-api serviceInstanceDeployV2",
|
|
1604
|
+
cwd: service.rootDir,
|
|
2002
1605
|
publicBaseUrl: service.publicBaseUrl,
|
|
2003
|
-
timings
|
|
1606
|
+
timings,
|
|
1607
|
+
transport: {
|
|
1608
|
+
railway: {
|
|
1609
|
+
reconcile: "api",
|
|
1610
|
+
deploy: railwayDeployTransport(env)
|
|
1611
|
+
}
|
|
1612
|
+
}
|
|
2004
1613
|
};
|
|
2005
1614
|
}
|
|
2006
1615
|
const deployService = await timedRailwayPhase(timings, "railway:resolve-context", () => resolveRailwayDeployProjectContext(service, { env }), {
|
|
2007
1616
|
service: service.key
|
|
2008
1617
|
});
|
|
2009
1618
|
const commandEnv = buildRailwayCommandEnv({ ...process.env, ...env });
|
|
2010
|
-
|
|
2011
|
-
const railway = resolveTreeseedToolCommand("railway", { env: commandEnv });
|
|
2012
|
-
if (!railway) {
|
|
2013
|
-
throw new Error("Railway CLI is unavailable.");
|
|
2014
|
-
}
|
|
1619
|
+
const deployTransport = railwayDeployTransport(commandEnv);
|
|
2015
1620
|
const taskPrefix = prefix ?? {
|
|
2016
1621
|
scope: normalizeScope(deployService.scope ?? deployService.railwayEnvironment ?? "railway"),
|
|
2017
1622
|
system: deployService.key === "api" ? "api" : "agents",
|
|
2018
1623
|
task: `${deployService.key}-railway-deploy`,
|
|
2019
1624
|
stage: "deploy"
|
|
2020
1625
|
};
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
}
|
|
1626
|
+
const writePhase = (stage, message) => {
|
|
1627
|
+
write ? write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][${stage}] ${message}`, "stdout") : null;
|
|
1628
|
+
};
|
|
1629
|
+
writePhase("resolve-context", `Resolved Railway service ${deployService.serviceName ?? deployService.serviceId ?? deployService.key}.`);
|
|
1630
|
+
writePhase("sync-runtime-config", "Syncing Railway runtime configuration.");
|
|
1631
|
+
const runtimeConfiguration = await timedRailwayPhase(timings, "railway:sync-runtime-config", () => withRailwayPhaseTimeout(
|
|
1632
|
+
() => syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, { env: commandEnv, writePhase }),
|
|
1633
|
+
railwayPhaseTimeoutMs(commandEnv, "sync_runtime_config"),
|
|
1634
|
+
`Railway runtime configuration sync timed out for ${deployService.serviceName ?? deployService.key}.`
|
|
1635
|
+
), { service: deployService.key });
|
|
2024
1636
|
const cliDeployService = {
|
|
2025
1637
|
...deployService,
|
|
2026
1638
|
projectId: runtimeConfiguration?.projectId ?? deployService.projectId,
|
|
@@ -2030,31 +1642,15 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2030
1642
|
serviceName: runtimeConfiguration?.serviceName ?? deployService.serviceName,
|
|
2031
1643
|
railwayEnvironment: runtimeConfiguration?.environmentName ?? runtimeConfiguration?.environmentId ?? deployService.railwayEnvironment
|
|
2032
1644
|
};
|
|
2033
|
-
|
|
1645
|
+
writePhase("device-login-vars", "Syncing Railway device-login variables.");
|
|
1646
|
+
await timedRailwayPhase(timings, "railway:device-login-vars", () => withRailwayPhaseTimeout(
|
|
1647
|
+
() => syncRailwayApiDeviceLoginVariables(cliDeployService, commandEnv, write, taskPrefix),
|
|
1648
|
+
railwayPhaseTimeoutMs(commandEnv, "device_login_vars"),
|
|
1649
|
+
`Railway device-login variable sync timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
|
|
1650
|
+
), {
|
|
2034
1651
|
service: cliDeployService.key
|
|
2035
1652
|
});
|
|
2036
|
-
|
|
2037
|
-
const hasCommandApiToken = Boolean(configuredEnvValue(commandEnv, "RAILWAY_API_TOKEN"));
|
|
2038
|
-
let usesProjectToken = Boolean(configuredEnvValue(railwayDeployEnv, "RAILWAY_TOKEN"));
|
|
2039
|
-
if (usesProjectToken) {
|
|
2040
|
-
railwayDeployEnv = { ...railwayDeployEnv, RAILWAY_API_TOKEN: void 0 };
|
|
2041
|
-
}
|
|
2042
|
-
await timedRailwayPhase(timings, "railway:project-token", async () => {
|
|
2043
|
-
if (usesProjectToken || hasCommandApiToken) {
|
|
2044
|
-
return null;
|
|
2045
|
-
}
|
|
2046
|
-
const projectToken = await createRailwayCliProjectToken(cliDeployService, { env: commandEnv });
|
|
2047
|
-
if (projectToken) {
|
|
2048
|
-
railwayDeployEnv = buildRailwayCliContextEnv({ ...railwayDeployEnv, RAILWAY_API_TOKEN: void 0, RAILWAY_TOKEN: projectToken }, cliDeployService);
|
|
2049
|
-
usesProjectToken = true;
|
|
2050
|
-
} else if (configuredEnvValue(commandEnv, "CI") === "true") {
|
|
2051
|
-
throw new Error(`Railway CI deploy requires a project token for ${cliDeployService.serviceName ?? cliDeployService.key}. Automatic project token creation did not return a token.`);
|
|
2052
|
-
}
|
|
2053
|
-
return null;
|
|
2054
|
-
}, { service: cliDeployService.key });
|
|
2055
|
-
const linkPlan = planRailwayServiceLink(cliDeployService, { env: commandEnv });
|
|
2056
|
-
const plan = planRailwayServiceDeploy(cliDeployService, { env, projectTokenMode: usesProjectToken });
|
|
2057
|
-
if (deployService.buildCommand && shouldRunRailwayPredeployBuild(commandEnv)) {
|
|
1653
|
+
if (deployService.buildCommand && !deployService.imageRef && shouldRunRailwayPredeployBuild(commandEnv)) {
|
|
2058
1654
|
const buildResult = await timedRailwayPhase(timings, "railway:predeploy-build", () => runPrefixedCommand("bash", ["-lc", deployService.buildCommand], {
|
|
2059
1655
|
cwd: deployService.rootDir,
|
|
2060
1656
|
env: commandEnv,
|
|
@@ -2065,102 +1661,66 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2065
1661
|
throw new Error(`Railway ${deployService.key} build command failed.`);
|
|
2066
1662
|
}
|
|
2067
1663
|
}
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
}
|
|
2104
|
-
const backoffMs = 5e3 * attempt;
|
|
2105
|
-
const warning = `Railway deploy for ${deployService.serviceName ?? deployService.serviceId ?? deployService.key} hit a transient failure; retrying in ${Math.round(backoffMs / 1e3)}s...`;
|
|
2106
|
-
write ? write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][retry] ${warning}`, "stderr") : console.warn(warning);
|
|
2107
|
-
await sleep(backoffMs);
|
|
2108
|
-
}
|
|
2109
|
-
if (lastFailure) {
|
|
2110
|
-
throw new Error(lastFailure.stderr?.trim() || lastFailure.stdout?.trim() || `railway ${plan.args.join(" ")} failed`);
|
|
2111
|
-
}
|
|
2112
|
-
}, { service: cliDeployService.key });
|
|
2113
|
-
return {
|
|
2114
|
-
service: deployService.key,
|
|
2115
|
-
status: "deployed",
|
|
2116
|
-
command: [plan.command, ...plan.args].join(" "),
|
|
2117
|
-
cwd: plan.cwd,
|
|
2118
|
-
publicBaseUrl: deployService.publicBaseUrl,
|
|
2119
|
-
timings,
|
|
2120
|
-
runtimeConfiguration: runtimeConfiguration ? {
|
|
2121
|
-
updated: runtimeConfiguration.updated,
|
|
2122
|
-
healthcheckPath: runtimeConfiguration.instance?.healthcheckPath ?? null,
|
|
2123
|
-
healthcheckTimeoutSeconds: runtimeConfiguration.instance?.healthcheckTimeoutSeconds ?? null,
|
|
2124
|
-
runtimeMode: runtimeConfiguration.instance?.runtimeMode ?? null,
|
|
2125
|
-
volume: runtimeConfiguration.volume ?? null
|
|
2126
|
-
} : null
|
|
2127
|
-
};
|
|
1664
|
+
if (deployTransport !== "cli-fallback") {
|
|
1665
|
+
writePhase("deploy", `Deploying Railway service ${cliDeployService.serviceName ?? cliDeployService.serviceId ?? cliDeployService.key} through the Railway API.`);
|
|
1666
|
+
const apiDeploy = await timedRailwayPhase(timings, "railway:api-deploy", () => withRailwayPhaseTimeout(
|
|
1667
|
+
() => deployRailwayServiceInstance({
|
|
1668
|
+
serviceId: cliDeployService.serviceId,
|
|
1669
|
+
environmentId: cliDeployService.environmentId,
|
|
1670
|
+
env: commandEnv
|
|
1671
|
+
}),
|
|
1672
|
+
railwayPhaseTimeoutMs(commandEnv, "deploy"),
|
|
1673
|
+
`Railway API deploy phase timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
|
|
1674
|
+
), { service: cliDeployService.key });
|
|
1675
|
+
return {
|
|
1676
|
+
service: deployService.key,
|
|
1677
|
+
status: "deployed",
|
|
1678
|
+
command: "railway-api serviceInstanceDeployV2",
|
|
1679
|
+
cwd: deployService.rootDir,
|
|
1680
|
+
publicBaseUrl: deployService.publicBaseUrl,
|
|
1681
|
+
timings,
|
|
1682
|
+
deploymentId: apiDeploy.deploymentId,
|
|
1683
|
+
transport: {
|
|
1684
|
+
railway: {
|
|
1685
|
+
reconcile: "api",
|
|
1686
|
+
deploy: "api"
|
|
1687
|
+
}
|
|
1688
|
+
},
|
|
1689
|
+
runtimeConfiguration: runtimeConfiguration ? {
|
|
1690
|
+
updated: runtimeConfiguration.updated,
|
|
1691
|
+
healthcheckPath: runtimeConfiguration.instance?.healthcheckPath ?? null,
|
|
1692
|
+
healthcheckTimeoutSeconds: runtimeConfiguration.instance?.healthcheckTimeoutSeconds ?? null,
|
|
1693
|
+
runtimeMode: runtimeConfiguration.instance?.runtimeMode ?? null,
|
|
1694
|
+
volume: runtimeConfiguration.volume ?? null
|
|
1695
|
+
} : null
|
|
1696
|
+
};
|
|
1697
|
+
}
|
|
1698
|
+
throw new Error(`Railway deployment for ${cliDeployService.serviceName ?? cliDeployService.serviceId ?? cliDeployService.key} requires Railway API deployment support. CLI deploy fallback has been removed.`);
|
|
2128
1699
|
}
|
|
2129
1700
|
export {
|
|
2130
1701
|
buildRailwayCommandEnv,
|
|
2131
|
-
buildRailwayDeployCommandEnv,
|
|
2132
|
-
buildRailwayLinkCommandEnv,
|
|
2133
1702
|
collectRailwayDeploymentStatusChecks,
|
|
2134
1703
|
configuredRailwayScheduledJobs,
|
|
2135
1704
|
configuredRailwayServices,
|
|
2136
1705
|
deployRailwayService,
|
|
2137
|
-
|
|
2138
|
-
|
|
1706
|
+
deriveRailwayCapacityProviderRunnerServiceName,
|
|
1707
|
+
deriveRailwayCapacityProviderRunnerVolumeName,
|
|
1708
|
+
deriveRailwayOperationsRunnerServiceName,
|
|
1709
|
+
deriveRailwayOperationsRunnerVolumeName,
|
|
2139
1710
|
deriveRailwayWorkerRunnerServiceName,
|
|
2140
1711
|
deriveRailwayWorkerRunnerVolumeName,
|
|
2141
|
-
ensureRailwayDatabaseServiceExists,
|
|
2142
|
-
ensureRailwayEnvironmentExists,
|
|
2143
|
-
ensureRailwayProjectContext,
|
|
2144
|
-
ensureRailwayProjectExists,
|
|
2145
1712
|
ensureRailwayScheduledJobs,
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
isRailwayTransientFailure,
|
|
1713
|
+
findStaleTreeseedOperationsRunnerResources,
|
|
1714
|
+
isTreeseedOperationsRunnerResourceName,
|
|
2149
1715
|
isUsableRailwayToken,
|
|
2150
|
-
listRailwayServiceVolumesWithCli,
|
|
2151
1716
|
parseRailwayJsonOutput,
|
|
2152
|
-
planRailwayServiceDeploy,
|
|
2153
|
-
planRailwayServiceLink,
|
|
2154
1717
|
railwayServiceRuntimeStartCommand,
|
|
2155
1718
|
resolveRailwayAuthToken,
|
|
2156
1719
|
resolveRailwayDeploymentProfile,
|
|
2157
|
-
runRailway,
|
|
2158
|
-
setRailwaySecretVariable,
|
|
2159
1720
|
shouldRunRailwayPredeployBuild,
|
|
2160
1721
|
validateRailwayDeployPrerequisites,
|
|
2161
1722
|
validateRailwayServiceConfiguration,
|
|
2162
1723
|
verifyRailwayManagedResources,
|
|
2163
1724
|
verifyRailwayScheduledJobs,
|
|
2164
|
-
waitForRailwayManagedDeploymentsSettled
|
|
2165
|
-
writeRailwayCliProjectConfig
|
|
1725
|
+
waitForRailwayManagedDeploymentsSettled
|
|
2166
1726
|
};
|