@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,8 +1,7 @@
|
|
|
1
1
|
import { request as httpsRequest } from "node:https";
|
|
2
|
+
import { resolveTreeseedRailwayApiToken } from "../../service-credentials.js";
|
|
2
3
|
const DEFAULT_RAILWAY_API_URL = "https://backboard.railway.com/graphql/v2";
|
|
3
4
|
const DEFAULT_RAILWAY_WORKSPACE = "knowledge-coop";
|
|
4
|
-
const RAILWAY_POSTGRES_TEMPLATE_ID = "b55da7dc-09be-4140-bc65-1284d15d349c";
|
|
5
|
-
const RAILWAY_POSTGRES_TEMPLATE_SERVICE_ID = "b55da7dc-09be-4140-bc65-1284b15d349b";
|
|
6
5
|
function normalizeRailwayEnvironmentName(value) {
|
|
7
6
|
const normalized = typeof value === "string" ? value.trim() : "";
|
|
8
7
|
if (!normalized) {
|
|
@@ -18,7 +17,7 @@ function isUsableRailwayToken(value) {
|
|
|
18
17
|
return typeof value === "string" && value.trim().length >= 8;
|
|
19
18
|
}
|
|
20
19
|
function resolveRailwayApiToken(env = process.env) {
|
|
21
|
-
const token =
|
|
20
|
+
const token = resolveTreeseedRailwayApiToken(env);
|
|
22
21
|
return isUsableRailwayToken(token) ? token : "";
|
|
23
22
|
}
|
|
24
23
|
function resolveRailwayApiUrl(env = process.env) {
|
|
@@ -113,15 +112,36 @@ function normalizeProject(node) {
|
|
|
113
112
|
if (!id || !name) {
|
|
114
113
|
return null;
|
|
115
114
|
}
|
|
115
|
+
const services = /* @__PURE__ */ new Map();
|
|
116
|
+
for (const service of normalizeConnectionNodes(node.services, normalizeService)) {
|
|
117
|
+
services.set(service.id, service);
|
|
118
|
+
}
|
|
119
|
+
for (const environment of normalizeConnectionNodes(node.environments, (entry) => entry)) {
|
|
120
|
+
for (const instance of normalizeConnectionNodes(environment.serviceInstances, (entry) => entry)) {
|
|
121
|
+
const serviceId = railwayConnectionLabel(instance.serviceId);
|
|
122
|
+
const serviceName = railwayConnectionLabel(instance.serviceName);
|
|
123
|
+
if (serviceId && serviceName) {
|
|
124
|
+
services.set(serviceId, { id: serviceId, name: serviceName });
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
116
128
|
return {
|
|
117
129
|
id,
|
|
118
130
|
name,
|
|
119
131
|
workspaceId: railwayConnectionLabel(node.workspaceId) || null,
|
|
120
132
|
deletedAt: railwayConnectionLabel(node.deletedAt) || null,
|
|
121
133
|
environments: normalizeConnectionNodes(node.environments, normalizeEnvironment),
|
|
122
|
-
services:
|
|
134
|
+
services: [...services.values()]
|
|
123
135
|
};
|
|
124
136
|
}
|
|
137
|
+
function normalizeServiceInstanceService(node) {
|
|
138
|
+
const id = railwayConnectionLabel(node.serviceId);
|
|
139
|
+
const name = railwayConnectionLabel(node.serviceName);
|
|
140
|
+
if (!id || !name) {
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
return { id, name };
|
|
144
|
+
}
|
|
125
145
|
function normalizeVariableMap(value) {
|
|
126
146
|
if (!value) {
|
|
127
147
|
return {};
|
|
@@ -195,6 +215,31 @@ function normalizeRailwayCustomDomain(node) {
|
|
|
195
215
|
dnsRecords
|
|
196
216
|
};
|
|
197
217
|
}
|
|
218
|
+
function normalizeRailwayDomain(node, kind = "service") {
|
|
219
|
+
if (!node || typeof node !== "object") {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
const record = node;
|
|
223
|
+
const id = railwayConnectionLabel(record.id) || railwayConnectionLabel(record.domain);
|
|
224
|
+
const domain = railwayConnectionLabel(record.domain);
|
|
225
|
+
if (!id || !domain) {
|
|
226
|
+
return null;
|
|
227
|
+
}
|
|
228
|
+
return {
|
|
229
|
+
id,
|
|
230
|
+
domain,
|
|
231
|
+
kind,
|
|
232
|
+
environmentId: railwayConnectionLabel(record.environmentId),
|
|
233
|
+
serviceId: railwayConnectionLabel(record.serviceId),
|
|
234
|
+
targetPort: normalizeRailwayNumber(record.targetPort)
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
function normalizeRailwayDomainList(value, kind) {
|
|
238
|
+
if (!Array.isArray(value)) {
|
|
239
|
+
return [];
|
|
240
|
+
}
|
|
241
|
+
return value.map((entry) => normalizeRailwayDomain(entry, kind)).filter(Boolean);
|
|
242
|
+
}
|
|
198
243
|
function normalizeRailwayVolumeInstance(node) {
|
|
199
244
|
const id = railwayConnectionLabel(node.id);
|
|
200
245
|
if (!id) {
|
|
@@ -208,13 +253,15 @@ function normalizeRailwayVolumeInstance(node) {
|
|
|
208
253
|
environmentId: railwayConnectionLabel(node.environmentId) || railwayConnectionLabel(node.environment?.id) || null,
|
|
209
254
|
mountPath: railwayConnectionLabel(node.mountPath) || railwayConnectionLabel(node.mount_path) || null,
|
|
210
255
|
state: railwayConnectionLabel(node.state) || null,
|
|
256
|
+
isPendingDeletion: node.isPendingDeletion === true || node.pendingDeletion === true,
|
|
257
|
+
deletedAt: railwayConnectionLabel(node.deletedAt) || null,
|
|
211
258
|
sizeGb,
|
|
212
259
|
usedGb
|
|
213
260
|
};
|
|
214
261
|
}
|
|
215
262
|
function isActiveRailwayVolumeInstance(instance) {
|
|
216
263
|
const state = String(instance.state ?? "READY").toUpperCase();
|
|
217
|
-
return state !== "DELETING" && state !== "DELETED";
|
|
264
|
+
return !instance.isPendingDeletion && !(typeof instance.deletedAt === "string" && instance.deletedAt.trim()) && state !== "DELETING" && state !== "DELETED";
|
|
218
265
|
}
|
|
219
266
|
function normalizeVolumeInstances(value) {
|
|
220
267
|
const direct = Array.isArray(value) ? value : null;
|
|
@@ -277,6 +324,13 @@ function collectRailwayVolumes(value, seen = /* @__PURE__ */ new Set()) {
|
|
|
277
324
|
}
|
|
278
325
|
return [...byId.values()];
|
|
279
326
|
}
|
|
327
|
+
function railwayApiTimeoutMs(env, explicitTimeoutMs) {
|
|
328
|
+
if (typeof explicitTimeoutMs === "number" && Number.isFinite(explicitTimeoutMs) && explicitTimeoutMs > 0) {
|
|
329
|
+
return explicitTimeoutMs;
|
|
330
|
+
}
|
|
331
|
+
const configured = Number.parseInt(String(env.TREESEED_RAILWAY_API_TIMEOUT_MS ?? "").trim(), 10);
|
|
332
|
+
return Number.isFinite(configured) && configured > 0 ? configured : 12e4;
|
|
333
|
+
}
|
|
280
334
|
async function railwayGraphqlRequest({
|
|
281
335
|
query,
|
|
282
336
|
variables,
|
|
@@ -284,19 +338,20 @@ async function railwayGraphqlRequest({
|
|
|
284
338
|
apiToken,
|
|
285
339
|
apiUrl,
|
|
286
340
|
fetchImpl = fetch,
|
|
287
|
-
timeoutMs
|
|
288
|
-
retries =
|
|
341
|
+
timeoutMs,
|
|
342
|
+
retries = 4
|
|
289
343
|
}) {
|
|
290
344
|
const token = apiToken || resolveRailwayApiToken(env);
|
|
291
345
|
if (!token) {
|
|
292
|
-
throw new Error("Configure
|
|
346
|
+
throw new Error("Configure TREESEED_RAILWAY_API_TOKEN before invoking Railway APIs.");
|
|
293
347
|
}
|
|
348
|
+
const requestTimeoutMs = railwayApiTimeoutMs(env, timeoutMs);
|
|
294
349
|
let attempt = 0;
|
|
295
350
|
for (; ; ) {
|
|
296
351
|
const controller = new AbortController();
|
|
297
352
|
let timer = null;
|
|
298
353
|
try {
|
|
299
|
-
const response = fetchImpl === fetch ? await railwayGraphqlHttpsRequest(apiUrl || resolveRailwayApiUrl(env), token, { query, variables },
|
|
354
|
+
const response = fetchImpl === fetch ? await railwayGraphqlHttpsRequest(apiUrl || resolveRailwayApiUrl(env), token, { query, variables }, requestTimeoutMs) : await Promise.race([
|
|
300
355
|
fetchImpl(apiUrl || resolveRailwayApiUrl(env), {
|
|
301
356
|
method: "POST",
|
|
302
357
|
headers: {
|
|
@@ -314,8 +369,8 @@ async function railwayGraphqlRequest({
|
|
|
314
369
|
new Promise((_, reject) => {
|
|
315
370
|
timer = setTimeout(() => {
|
|
316
371
|
controller.abort();
|
|
317
|
-
reject(markRailwayTransientError(new Error(`Railway API request timed out after ${
|
|
318
|
-
},
|
|
372
|
+
reject(markRailwayTransientError(new Error(`Railway API request timed out after ${requestTimeoutMs}ms.`)));
|
|
373
|
+
}, requestTimeoutMs);
|
|
319
374
|
})
|
|
320
375
|
]);
|
|
321
376
|
const payload = response.payload;
|
|
@@ -349,7 +404,24 @@ async function railwayGraphqlRequest({
|
|
|
349
404
|
async function railwayGraphqlHttpsRequest(url, token, body, timeoutMs) {
|
|
350
405
|
const rawBody = JSON.stringify(body);
|
|
351
406
|
return new Promise((resolve, reject) => {
|
|
352
|
-
|
|
407
|
+
let settled = false;
|
|
408
|
+
let req = null;
|
|
409
|
+
const hardTimeout = setTimeout(() => {
|
|
410
|
+
if (settled) return;
|
|
411
|
+
settled = true;
|
|
412
|
+
const error = markRailwayTransientError(new Error(`Railway API request timed out after ${timeoutMs}ms.`));
|
|
413
|
+
if (req) {
|
|
414
|
+
req.destroy(error);
|
|
415
|
+
}
|
|
416
|
+
reject(error);
|
|
417
|
+
}, timeoutMs);
|
|
418
|
+
const finish = (callback) => {
|
|
419
|
+
if (settled) return;
|
|
420
|
+
settled = true;
|
|
421
|
+
clearTimeout(hardTimeout);
|
|
422
|
+
callback();
|
|
423
|
+
};
|
|
424
|
+
req = httpsRequest(url, {
|
|
353
425
|
method: "POST",
|
|
354
426
|
headers: {
|
|
355
427
|
authorization: `Bearer ${token}`,
|
|
@@ -371,18 +443,21 @@ async function railwayGraphqlHttpsRequest(url, token, body, timeoutMs) {
|
|
|
371
443
|
}
|
|
372
444
|
const status = res.statusCode ?? 0;
|
|
373
445
|
const retryAfterHeader = res.headers["retry-after"];
|
|
374
|
-
resolve({
|
|
446
|
+
finish(() => resolve({
|
|
375
447
|
ok: status >= 200 && status < 300,
|
|
376
448
|
status,
|
|
377
449
|
payload,
|
|
378
450
|
retryAfter: Array.isArray(retryAfterHeader) ? retryAfterHeader[0] ?? null : retryAfterHeader ?? null
|
|
379
|
-
});
|
|
451
|
+
}));
|
|
380
452
|
});
|
|
381
453
|
});
|
|
382
454
|
req.on("timeout", () => {
|
|
383
|
-
|
|
455
|
+
const error = markRailwayTransientError(new Error(`Railway API request timed out after ${timeoutMs}ms.`));
|
|
456
|
+
if (!settled) {
|
|
457
|
+
req?.destroy(error);
|
|
458
|
+
}
|
|
384
459
|
});
|
|
385
|
-
req.on("error", reject);
|
|
460
|
+
req.on("error", (error) => finish(() => reject(error)));
|
|
386
461
|
req.write(rawBody);
|
|
387
462
|
req.end();
|
|
388
463
|
});
|
|
@@ -637,25 +712,130 @@ query TreeseedRailwayProjectEnvironments($projectId: String!) {
|
|
|
637
712
|
});
|
|
638
713
|
return normalizeConnectionNodes(payload.data?.project ? payload.data.project.environments : null, normalizeEnvironment);
|
|
639
714
|
}
|
|
715
|
+
async function listRailwayEnvironmentServices({
|
|
716
|
+
environmentId,
|
|
717
|
+
env = process.env,
|
|
718
|
+
fetchImpl = fetch
|
|
719
|
+
}) {
|
|
720
|
+
const payload = await railwayGraphqlRequest({
|
|
721
|
+
query: `
|
|
722
|
+
query TreeseedRailwayEnvironmentServices($environmentId: String!) {
|
|
723
|
+
environment(id: $environmentId) {
|
|
724
|
+
id
|
|
725
|
+
name
|
|
726
|
+
serviceInstances(first: 100) {
|
|
727
|
+
edges {
|
|
728
|
+
node {
|
|
729
|
+
id
|
|
730
|
+
serviceId
|
|
731
|
+
serviceName
|
|
732
|
+
environmentId
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
}
|
|
738
|
+
`.trim(),
|
|
739
|
+
variables: { environmentId },
|
|
740
|
+
env,
|
|
741
|
+
fetchImpl
|
|
742
|
+
});
|
|
743
|
+
return normalizeConnectionNodes(
|
|
744
|
+
payload.data?.environment ? payload.data.environment.serviceInstances : null,
|
|
745
|
+
normalizeServiceInstanceService
|
|
746
|
+
);
|
|
747
|
+
}
|
|
640
748
|
async function ensureRailwayService({
|
|
641
749
|
projectId,
|
|
642
750
|
serviceName,
|
|
643
751
|
serviceId,
|
|
752
|
+
environmentId,
|
|
753
|
+
imageRef,
|
|
754
|
+
sourceRepo,
|
|
755
|
+
sourceBranch,
|
|
644
756
|
env = process.env,
|
|
645
757
|
fetchImpl = fetch
|
|
646
758
|
}) {
|
|
647
759
|
const services = await listRailwayServices({ projectId, env, fetchImpl });
|
|
648
760
|
const desiredServiceName = railwayConnectionLabel(serviceName);
|
|
649
761
|
const desiredServiceId = railwayConnectionLabel(serviceId);
|
|
650
|
-
|
|
762
|
+
let existing = services.find(
|
|
651
763
|
(service2) => desiredServiceId && service2.id === desiredServiceId || desiredServiceName && service2.name === desiredServiceName
|
|
652
764
|
) ?? null;
|
|
765
|
+
if (!existing && environmentId) {
|
|
766
|
+
const environmentServices = await listRailwayEnvironmentServices({ environmentId, env, fetchImpl }).catch(() => []);
|
|
767
|
+
existing = environmentServices.find(
|
|
768
|
+
(service2) => desiredServiceId && service2.id === desiredServiceId || desiredServiceName && service2.name === desiredServiceName
|
|
769
|
+
) ?? null;
|
|
770
|
+
}
|
|
653
771
|
if (existing) {
|
|
772
|
+
const desiredImageRef = railwayConnectionLabel(imageRef);
|
|
773
|
+
const desiredSourceRepo = railwayConnectionLabel(sourceRepo);
|
|
774
|
+
if (desiredSourceRepo) {
|
|
775
|
+
try {
|
|
776
|
+
await updateRailwayServiceGitSource({
|
|
777
|
+
serviceId: existing.id,
|
|
778
|
+
sourceRepo: desiredSourceRepo,
|
|
779
|
+
sourceBranch,
|
|
780
|
+
env,
|
|
781
|
+
fetchImpl
|
|
782
|
+
});
|
|
783
|
+
} catch (error) {
|
|
784
|
+
if (!looksLikeRailwayImageSourceUpdateUnsupported(error)) {
|
|
785
|
+
throw error;
|
|
786
|
+
}
|
|
787
|
+
throw new Error(
|
|
788
|
+
`Railway Git source update for existing service ${existing.name} (${existing.id}) is unsupported; refusing to delete and recreate an existing service. Repair the service in place or use a provider-supported source update.`
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
return { service: existing, created: false };
|
|
792
|
+
}
|
|
793
|
+
if (desiredImageRef) {
|
|
794
|
+
try {
|
|
795
|
+
await updateRailwayServiceImageSource({
|
|
796
|
+
serviceId: existing.id,
|
|
797
|
+
imageRef: desiredImageRef,
|
|
798
|
+
env,
|
|
799
|
+
fetchImpl
|
|
800
|
+
});
|
|
801
|
+
} catch (error) {
|
|
802
|
+
if (!looksLikeRailwayImageSourceUpdateUnsupported(error)) {
|
|
803
|
+
throw error;
|
|
804
|
+
}
|
|
805
|
+
throw new Error(
|
|
806
|
+
`Railway image source update for existing service ${existing.name} (${existing.id}) is unsupported; refusing to delete and recreate an existing service. Repair the service in place or use a provider-supported image source update.`
|
|
807
|
+
);
|
|
808
|
+
}
|
|
809
|
+
}
|
|
654
810
|
return { service: existing, created: false };
|
|
655
811
|
}
|
|
656
812
|
if (!desiredServiceName) {
|
|
657
813
|
throw new Error("Railway service creation requires a service name.");
|
|
658
814
|
}
|
|
815
|
+
const service = await createRailwayImageService({
|
|
816
|
+
projectId,
|
|
817
|
+
environmentId,
|
|
818
|
+
serviceName: desiredServiceName,
|
|
819
|
+
imageRef,
|
|
820
|
+
sourceRepo,
|
|
821
|
+
sourceBranch,
|
|
822
|
+
env,
|
|
823
|
+
fetchImpl
|
|
824
|
+
});
|
|
825
|
+
return { service, created: true };
|
|
826
|
+
}
|
|
827
|
+
async function createRailwayImageService({
|
|
828
|
+
projectId,
|
|
829
|
+
serviceName,
|
|
830
|
+
environmentId,
|
|
831
|
+
imageRef,
|
|
832
|
+
sourceRepo,
|
|
833
|
+
sourceBranch,
|
|
834
|
+
env = process.env,
|
|
835
|
+
fetchImpl = fetch
|
|
836
|
+
}) {
|
|
837
|
+
const desiredSourceRepo = railwayConnectionLabel(sourceRepo);
|
|
838
|
+
const desiredImageRef = railwayConnectionLabel(imageRef);
|
|
659
839
|
const created = await railwayGraphqlRequest({
|
|
660
840
|
query: `
|
|
661
841
|
mutation TreeseedRailwayServiceCreate($input: ServiceCreateInput!) {
|
|
@@ -668,7 +848,18 @@ mutation TreeseedRailwayServiceCreate($input: ServiceCreateInput!) {
|
|
|
668
848
|
variables: {
|
|
669
849
|
input: {
|
|
670
850
|
projectId,
|
|
671
|
-
name:
|
|
851
|
+
name: serviceName,
|
|
852
|
+
...railwayConnectionLabel(environmentId) ? { environmentId: railwayConnectionLabel(environmentId) } : {},
|
|
853
|
+
...desiredSourceRepo ? {
|
|
854
|
+
source: {
|
|
855
|
+
repo: desiredSourceRepo
|
|
856
|
+
},
|
|
857
|
+
...railwayConnectionLabel(sourceBranch) ? { branch: railwayConnectionLabel(sourceBranch) } : {}
|
|
858
|
+
} : desiredImageRef ? {
|
|
859
|
+
source: {
|
|
860
|
+
image: desiredImageRef
|
|
861
|
+
}
|
|
862
|
+
} : {}
|
|
672
863
|
}
|
|
673
864
|
},
|
|
674
865
|
env,
|
|
@@ -676,9 +867,219 @@ mutation TreeseedRailwayServiceCreate($input: ServiceCreateInput!) {
|
|
|
676
867
|
});
|
|
677
868
|
const service = created.data?.serviceCreate ? normalizeService(created.data.serviceCreate) : null;
|
|
678
869
|
if (!service) {
|
|
679
|
-
throw new Error(`Railway service create did not return a usable service for ${
|
|
870
|
+
throw new Error(`Railway service create did not return a usable service for ${serviceName}.`);
|
|
680
871
|
}
|
|
681
|
-
return
|
|
872
|
+
return service;
|
|
873
|
+
}
|
|
874
|
+
function looksLikeRailwayImageSourceUpdateUnsupported(error) {
|
|
875
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
876
|
+
return /Problem processing request|source|image|ServiceUpdateInput/iu.test(message);
|
|
877
|
+
}
|
|
878
|
+
async function updateRailwayServiceImageSource({
|
|
879
|
+
serviceId,
|
|
880
|
+
imageRef,
|
|
881
|
+
env = process.env,
|
|
882
|
+
fetchImpl = fetch
|
|
883
|
+
}) {
|
|
884
|
+
const desiredImage = railwayConnectionLabel(imageRef);
|
|
885
|
+
if (!serviceId || !desiredImage) {
|
|
886
|
+
throw new Error("Railway service image source update requires a service id and image reference.");
|
|
887
|
+
}
|
|
888
|
+
const payload = await railwayGraphqlRequest({
|
|
889
|
+
query: `
|
|
890
|
+
mutation TreeseedRailwayServiceImageSourceUpdate($id: String!, $input: ServiceConnectInput!) {
|
|
891
|
+
serviceConnect(id: $id, input: $input) {
|
|
892
|
+
id
|
|
893
|
+
name
|
|
894
|
+
}
|
|
895
|
+
}
|
|
896
|
+
`.trim(),
|
|
897
|
+
variables: {
|
|
898
|
+
id: serviceId,
|
|
899
|
+
input: {
|
|
900
|
+
image: desiredImage
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
env,
|
|
904
|
+
fetchImpl
|
|
905
|
+
});
|
|
906
|
+
const service = payload.data?.serviceConnect ? normalizeService(payload.data.serviceConnect) : null;
|
|
907
|
+
if (!service) {
|
|
908
|
+
throw new Error(`Railway service image source update did not return a usable service for ${serviceId}.`);
|
|
909
|
+
}
|
|
910
|
+
return service;
|
|
911
|
+
}
|
|
912
|
+
async function updateRailwayServiceGitSource({
|
|
913
|
+
serviceId,
|
|
914
|
+
sourceRepo,
|
|
915
|
+
sourceBranch,
|
|
916
|
+
env = process.env,
|
|
917
|
+
fetchImpl = fetch
|
|
918
|
+
}) {
|
|
919
|
+
const desiredRepo = railwayConnectionLabel(sourceRepo);
|
|
920
|
+
if (!serviceId || !desiredRepo) {
|
|
921
|
+
throw new Error("Railway service Git source update requires a service id and repository slug.");
|
|
922
|
+
}
|
|
923
|
+
const connect = async (repo) => {
|
|
924
|
+
const payload2 = await railwayGraphqlRequest({
|
|
925
|
+
query: `
|
|
926
|
+
mutation TreeseedRailwayServiceGitSourceUpdate($id: String!, $input: ServiceConnectInput!) {
|
|
927
|
+
serviceConnect(id: $id, input: $input) {
|
|
928
|
+
id
|
|
929
|
+
name
|
|
930
|
+
}
|
|
931
|
+
}
|
|
932
|
+
`.trim(),
|
|
933
|
+
variables: {
|
|
934
|
+
id: serviceId,
|
|
935
|
+
input: {
|
|
936
|
+
repo,
|
|
937
|
+
...railwayConnectionLabel(sourceBranch) ? { branch: railwayConnectionLabel(sourceBranch) } : {}
|
|
938
|
+
}
|
|
939
|
+
},
|
|
940
|
+
env,
|
|
941
|
+
fetchImpl
|
|
942
|
+
});
|
|
943
|
+
return payload2;
|
|
944
|
+
};
|
|
945
|
+
let payload;
|
|
946
|
+
try {
|
|
947
|
+
payload = await connect(desiredRepo);
|
|
948
|
+
} catch (error) {
|
|
949
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
950
|
+
if (!/User does not have access to the repo/iu.test(message) || /^https?:\/\//iu.test(desiredRepo)) {
|
|
951
|
+
throw error;
|
|
952
|
+
}
|
|
953
|
+
payload = await connect(`https://github.com/${desiredRepo}`);
|
|
954
|
+
}
|
|
955
|
+
const service = payload.data?.serviceConnect ? normalizeService(payload.data.serviceConnect) : null;
|
|
956
|
+
if (!service) {
|
|
957
|
+
throw new Error(`Railway service Git source update did not return a usable service for ${serviceId}.`);
|
|
958
|
+
}
|
|
959
|
+
return service;
|
|
960
|
+
}
|
|
961
|
+
async function ensureRailwayGeneratedServiceDomain({
|
|
962
|
+
projectId,
|
|
963
|
+
environmentId,
|
|
964
|
+
serviceId,
|
|
965
|
+
targetPort,
|
|
966
|
+
env = process.env,
|
|
967
|
+
fetchImpl = fetch
|
|
968
|
+
}) {
|
|
969
|
+
const domains = await listRailwayServiceDomains({ projectId, environmentId, serviceId, env, fetchImpl });
|
|
970
|
+
const existing = domains.find((domain2) => domain2.kind === "service") ?? domains.find((domain2) => domain2.domain.endsWith(".railway.app")) ?? null;
|
|
971
|
+
if (existing) {
|
|
972
|
+
return { domain: existing, created: false };
|
|
973
|
+
}
|
|
974
|
+
const query = `
|
|
975
|
+
mutation TreeseedRailwayServiceDomainCreate($input: ServiceDomainCreateInput!) {
|
|
976
|
+
serviceDomainCreate(input: $input) {
|
|
977
|
+
id
|
|
978
|
+
domain
|
|
979
|
+
serviceId
|
|
980
|
+
environmentId
|
|
981
|
+
targetPort
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
`.trim();
|
|
985
|
+
const inputWithProject = {
|
|
986
|
+
projectId,
|
|
987
|
+
environmentId,
|
|
988
|
+
serviceId,
|
|
989
|
+
...Number.isFinite(Number(targetPort)) ? { targetPort: Number(targetPort) } : {}
|
|
990
|
+
};
|
|
991
|
+
const createPayload = async (input) => railwayGraphqlRequest({
|
|
992
|
+
query,
|
|
993
|
+
variables: {
|
|
994
|
+
input: {
|
|
995
|
+
...input
|
|
996
|
+
}
|
|
997
|
+
},
|
|
998
|
+
env,
|
|
999
|
+
fetchImpl
|
|
1000
|
+
});
|
|
1001
|
+
let payload;
|
|
1002
|
+
try {
|
|
1003
|
+
payload = await createPayload(inputWithProject);
|
|
1004
|
+
} catch (error) {
|
|
1005
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1006
|
+
if (!/Problem processing request|projectId|not defined by type/iu.test(message)) {
|
|
1007
|
+
throw error;
|
|
1008
|
+
}
|
|
1009
|
+
payload = await createPayload({
|
|
1010
|
+
environmentId,
|
|
1011
|
+
serviceId,
|
|
1012
|
+
...Number.isFinite(Number(targetPort)) ? { targetPort: Number(targetPort) } : {}
|
|
1013
|
+
});
|
|
1014
|
+
}
|
|
1015
|
+
const domain = normalizeRailwayDomain(payload.data?.serviceDomainCreate);
|
|
1016
|
+
if (!domain) {
|
|
1017
|
+
throw new Error("Railway service domain create did not return a usable domain.");
|
|
1018
|
+
}
|
|
1019
|
+
return { domain, created: true };
|
|
1020
|
+
}
|
|
1021
|
+
async function listRailwayServiceDomains({
|
|
1022
|
+
projectId,
|
|
1023
|
+
environmentId,
|
|
1024
|
+
serviceId,
|
|
1025
|
+
env = process.env,
|
|
1026
|
+
fetchImpl = fetch
|
|
1027
|
+
}) {
|
|
1028
|
+
const payload = await railwayGraphqlRequest({
|
|
1029
|
+
query: `
|
|
1030
|
+
query TreeseedRailwayServiceDomains($projectId: String!, $environmentId: String!, $serviceId: String!) {
|
|
1031
|
+
domains(projectId: $projectId, environmentId: $environmentId, serviceId: $serviceId) {
|
|
1032
|
+
serviceDomains {
|
|
1033
|
+
id
|
|
1034
|
+
domain
|
|
1035
|
+
serviceId
|
|
1036
|
+
environmentId
|
|
1037
|
+
targetPort
|
|
1038
|
+
}
|
|
1039
|
+
customDomains {
|
|
1040
|
+
id
|
|
1041
|
+
domain
|
|
1042
|
+
serviceId
|
|
1043
|
+
environmentId
|
|
1044
|
+
targetPort
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
`.trim(),
|
|
1049
|
+
variables: { projectId, environmentId, serviceId },
|
|
1050
|
+
env,
|
|
1051
|
+
fetchImpl
|
|
1052
|
+
});
|
|
1053
|
+
const domains = payload.data?.domains && typeof payload.data.domains === "object" ? payload.data.domains : {};
|
|
1054
|
+
return [
|
|
1055
|
+
...normalizeRailwayDomainList(domains.serviceDomains, "service"),
|
|
1056
|
+
...normalizeRailwayDomainList(domains.customDomains, "custom")
|
|
1057
|
+
];
|
|
1058
|
+
}
|
|
1059
|
+
async function deployRailwayServiceInstance({
|
|
1060
|
+
serviceId,
|
|
1061
|
+
environmentId,
|
|
1062
|
+
env = process.env,
|
|
1063
|
+
fetchImpl = fetch
|
|
1064
|
+
}) {
|
|
1065
|
+
const payload = await railwayGraphqlRequest({
|
|
1066
|
+
query: `
|
|
1067
|
+
mutation TreeseedRailwayServiceInstanceDeploy($serviceId: String!, $environmentId: String!) {
|
|
1068
|
+
serviceInstanceDeployV2(serviceId: $serviceId, environmentId: $environmentId)
|
|
1069
|
+
}
|
|
1070
|
+
`.trim(),
|
|
1071
|
+
variables: { serviceId, environmentId },
|
|
1072
|
+
env,
|
|
1073
|
+
fetchImpl
|
|
1074
|
+
});
|
|
1075
|
+
const value = payload.data?.serviceInstanceDeployV2;
|
|
1076
|
+
if (typeof value === "string" && value.trim()) {
|
|
1077
|
+
return { deploymentId: value.trim() };
|
|
1078
|
+
}
|
|
1079
|
+
if (value && typeof value === "object") {
|
|
1080
|
+
return { deploymentId: railwayConnectionLabel(value.id) || null };
|
|
1081
|
+
}
|
|
1082
|
+
return { deploymentId: null };
|
|
682
1083
|
}
|
|
683
1084
|
async function updateRailwayServiceName({
|
|
684
1085
|
serviceId,
|
|
@@ -717,43 +1118,221 @@ async function ensureRailwayPostgresService({
|
|
|
717
1118
|
environmentId,
|
|
718
1119
|
serviceName,
|
|
719
1120
|
env = process.env,
|
|
720
|
-
fetchImpl = fetch
|
|
1121
|
+
fetchImpl = fetch,
|
|
1122
|
+
maxAttempts = 40
|
|
721
1123
|
}) {
|
|
722
1124
|
const desiredServiceName = railwayConnectionLabel(serviceName);
|
|
723
1125
|
if (!desiredServiceName) {
|
|
724
1126
|
throw new Error("Railway Postgres service creation requires a service name.");
|
|
725
1127
|
}
|
|
726
1128
|
const services = await listRailwayServices({ projectId, env, fetchImpl });
|
|
727
|
-
const existing = services.find((
|
|
1129
|
+
const existing = services.find((service) => service.name === desiredServiceName || service.id === desiredServiceName) ?? null;
|
|
728
1130
|
if (existing) {
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
1131
|
+
const proof = await inspectRailwayPostgresService({ projectId, environmentId, serviceId: existing.id, env, fetchImpl });
|
|
1132
|
+
if (proof.ok) {
|
|
1133
|
+
return { service: existing, created: false, proof };
|
|
1134
|
+
}
|
|
1135
|
+
throw new Error(
|
|
1136
|
+
`Railway Postgres service ${existing.name} (${existing.id}) failed proof; refusing to delete and recreate an existing service. Repair the existing database service in place and rerun reconciliation.`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
const template = await getRailwayTemplateByCode({ code: "postgres", env, fetchImpl });
|
|
1140
|
+
await deployRailwayTemplate({
|
|
1141
|
+
templateId: template.id,
|
|
1142
|
+
serializedConfig: template.serializedConfig,
|
|
1143
|
+
projectId,
|
|
1144
|
+
environmentId,
|
|
1145
|
+
env,
|
|
1146
|
+
fetchImpl
|
|
1147
|
+
});
|
|
1148
|
+
const settled = await waitForRailwayPostgresTemplateService({
|
|
1149
|
+
projectId,
|
|
1150
|
+
environmentId,
|
|
1151
|
+
desiredServiceName,
|
|
1152
|
+
env,
|
|
1153
|
+
fetchImpl,
|
|
1154
|
+
maxAttempts
|
|
1155
|
+
});
|
|
1156
|
+
return { service: settled.service, created: true, proof: settled.proof };
|
|
1157
|
+
}
|
|
1158
|
+
async function getRailwayTemplateByCode({
|
|
1159
|
+
code,
|
|
1160
|
+
env = process.env,
|
|
1161
|
+
fetchImpl = fetch
|
|
1162
|
+
}) {
|
|
1163
|
+
const payload = await railwayGraphqlRequest({
|
|
732
1164
|
query: `
|
|
733
|
-
|
|
734
|
-
|
|
1165
|
+
query TreeseedRailwayTemplate($code: String!) {
|
|
1166
|
+
template(code: $code) {
|
|
735
1167
|
id
|
|
1168
|
+
code
|
|
736
1169
|
name
|
|
1170
|
+
serializedConfig
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
`.trim(),
|
|
1174
|
+
variables: { code },
|
|
1175
|
+
env,
|
|
1176
|
+
fetchImpl,
|
|
1177
|
+
timeoutMs: 15e3,
|
|
1178
|
+
retries: 1
|
|
1179
|
+
});
|
|
1180
|
+
const template = payload.data?.template;
|
|
1181
|
+
const id = railwayConnectionLabel(template?.id);
|
|
1182
|
+
if (!id || !template || typeof template !== "object") {
|
|
1183
|
+
throw new Error(`Railway Postgres template "${code}" was not found through the Railway API.`);
|
|
1184
|
+
}
|
|
1185
|
+
return {
|
|
1186
|
+
id,
|
|
1187
|
+
code: railwayConnectionLabel(template.code) || null,
|
|
1188
|
+
name: railwayConnectionLabel(template.name) || null,
|
|
1189
|
+
serializedConfig: normalizeTemplateSerializedConfig(template.serializedConfig)
|
|
1190
|
+
};
|
|
1191
|
+
}
|
|
1192
|
+
function normalizeTemplateSerializedConfig(value) {
|
|
1193
|
+
if (typeof value === "string") {
|
|
1194
|
+
try {
|
|
1195
|
+
const parsed = JSON.parse(value);
|
|
1196
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
1197
|
+
} catch {
|
|
1198
|
+
return {};
|
|
1199
|
+
}
|
|
1200
|
+
}
|
|
1201
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1202
|
+
}
|
|
1203
|
+
async function deployRailwayTemplate({
|
|
1204
|
+
templateId,
|
|
1205
|
+
serializedConfig,
|
|
1206
|
+
projectId,
|
|
1207
|
+
environmentId,
|
|
1208
|
+
env = process.env,
|
|
1209
|
+
fetchImpl = fetch
|
|
1210
|
+
}) {
|
|
1211
|
+
const workspace = await resolveRailwayWorkspaceContext({ env, fetchImpl });
|
|
1212
|
+
const payload = await railwayGraphqlRequest({
|
|
1213
|
+
query: `
|
|
1214
|
+
mutation TreeseedRailwayTemplateDeploy($input: TemplateDeployV2Input!) {
|
|
1215
|
+
templateDeployV2(input: $input) {
|
|
1216
|
+
projectId
|
|
1217
|
+
workflowId
|
|
737
1218
|
}
|
|
738
1219
|
}
|
|
739
1220
|
`.trim(),
|
|
740
1221
|
variables: {
|
|
741
1222
|
input: {
|
|
1223
|
+
templateId,
|
|
1224
|
+
serializedConfig,
|
|
742
1225
|
projectId,
|
|
743
1226
|
environmentId,
|
|
744
|
-
|
|
745
|
-
templateId: RAILWAY_POSTGRES_TEMPLATE_ID,
|
|
746
|
-
templateServiceId: RAILWAY_POSTGRES_TEMPLATE_SERVICE_ID
|
|
1227
|
+
workspaceId: workspace.id
|
|
747
1228
|
}
|
|
748
1229
|
},
|
|
749
1230
|
env,
|
|
750
|
-
fetchImpl
|
|
1231
|
+
fetchImpl,
|
|
1232
|
+
timeoutMs: 2e4,
|
|
1233
|
+
retries: 1
|
|
751
1234
|
});
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
throw new Error(`Railway Postgres service create did not return a usable service for ${desiredServiceName}.`);
|
|
1235
|
+
if (!payload.data?.templateDeployV2?.projectId) {
|
|
1236
|
+
throw new Error("Railway Postgres template deployment did not return a project id.");
|
|
755
1237
|
}
|
|
756
|
-
return
|
|
1238
|
+
return payload.data.templateDeployV2;
|
|
1239
|
+
}
|
|
1240
|
+
async function waitForRailwayPostgresTemplateService({
|
|
1241
|
+
projectId,
|
|
1242
|
+
environmentId,
|
|
1243
|
+
desiredServiceName,
|
|
1244
|
+
env = process.env,
|
|
1245
|
+
fetchImpl = fetch,
|
|
1246
|
+
maxAttempts = 40
|
|
1247
|
+
}) {
|
|
1248
|
+
let lastProof = null;
|
|
1249
|
+
for (let attempt = 0; attempt < maxAttempts; attempt += 1) {
|
|
1250
|
+
const services = await listRailwayServices({ projectId, env, fetchImpl });
|
|
1251
|
+
for (const service of services) {
|
|
1252
|
+
const proof = await inspectRailwayPostgresService({
|
|
1253
|
+
projectId,
|
|
1254
|
+
environmentId,
|
|
1255
|
+
serviceId: service.id,
|
|
1256
|
+
env,
|
|
1257
|
+
fetchImpl
|
|
1258
|
+
});
|
|
1259
|
+
if (proof.ok) {
|
|
1260
|
+
const renamed = service.name === desiredServiceName ? service : await updateRailwayServiceName({ serviceId: service.id, name: desiredServiceName, env, fetchImpl });
|
|
1261
|
+
return { service: renamed, proof };
|
|
1262
|
+
}
|
|
1263
|
+
lastProof = proof;
|
|
1264
|
+
}
|
|
1265
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
1266
|
+
}
|
|
1267
|
+
throw new Error(`Railway Postgres template deployment did not produce a managed PostgreSQL service named ${desiredServiceName}. Last proof: ${lastProof?.message ?? "no candidate service observed"}`);
|
|
1268
|
+
}
|
|
1269
|
+
async function inspectRailwayPostgresService({
|
|
1270
|
+
projectId,
|
|
1271
|
+
environmentId,
|
|
1272
|
+
serviceId,
|
|
1273
|
+
env = process.env,
|
|
1274
|
+
fetchImpl = fetch
|
|
1275
|
+
}) {
|
|
1276
|
+
const variables = await listRailwayVariables({ projectId, environmentId, serviceId, env, fetchImpl }).catch(() => ({}));
|
|
1277
|
+
const hasConnectionVars = typeof variables.DATABASE_URL === "string" && typeof variables.PGHOST === "string" && typeof variables.PGUSER === "string" && typeof variables.PGPASSWORD === "string" && typeof variables.PGDATABASE === "string";
|
|
1278
|
+
const volumes = await listRailwayVolumes({ projectId, env, fetchImpl }).catch(() => []);
|
|
1279
|
+
const volume = volumes.find((candidate) => candidate.instances.some(
|
|
1280
|
+
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === "/var/lib/postgresql/data"
|
|
1281
|
+
)) ?? null;
|
|
1282
|
+
const deployment = await inspectRailwayServiceDeploymentHealth({ serviceId, environmentId, env, fetchImpl }).catch((error) => ({
|
|
1283
|
+
ok: false,
|
|
1284
|
+
status: null,
|
|
1285
|
+
message: error instanceof Error ? error.message : String(error ?? "Unable to inspect PostgreSQL deployment health.")
|
|
1286
|
+
}));
|
|
1287
|
+
return {
|
|
1288
|
+
ok: hasConnectionVars && Boolean(volume) && deployment.ok,
|
|
1289
|
+
variableKeys: Object.keys(variables).sort(),
|
|
1290
|
+
volumeId: volume?.id ?? null,
|
|
1291
|
+
deploymentStatus: deployment.status,
|
|
1292
|
+
message: hasConnectionVars ? volume ? deployment.ok ? "Railway managed PostgreSQL markers are present and the deployment is healthy." : `Railway managed PostgreSQL markers are present, but deployment health is not ready. ${deployment.message}` : "PostgreSQL connection variables exist, but the managed data volume is missing." : "PostgreSQL connection variables are missing."
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1295
|
+
async function inspectRailwayServiceDeploymentHealth({
|
|
1296
|
+
serviceId,
|
|
1297
|
+
environmentId,
|
|
1298
|
+
env = process.env,
|
|
1299
|
+
fetchImpl = fetch
|
|
1300
|
+
}) {
|
|
1301
|
+
const payload = await railwayGraphqlRequest({
|
|
1302
|
+
query: `
|
|
1303
|
+
query TreeseedRailwayServiceDeploymentHealth($serviceId: String!, $environmentId: String!) {
|
|
1304
|
+
serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {
|
|
1305
|
+
latestDeployment {
|
|
1306
|
+
status
|
|
1307
|
+
deploymentStopped
|
|
1308
|
+
meta
|
|
1309
|
+
instances {
|
|
1310
|
+
status
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
}
|
|
1315
|
+
`.trim(),
|
|
1316
|
+
variables: { serviceId, environmentId },
|
|
1317
|
+
env,
|
|
1318
|
+
fetchImpl
|
|
1319
|
+
});
|
|
1320
|
+
const deployment = payload.data?.serviceInstance?.latestDeployment;
|
|
1321
|
+
const status = railwayConnectionLabel(deployment?.status)?.toUpperCase() ?? null;
|
|
1322
|
+
const instanceStatuses = Array.isArray(deployment?.instances) ? deployment.instances.map((instance) => railwayConnectionLabel(instance?.status)?.toUpperCase()).filter(Boolean) : [];
|
|
1323
|
+
const stopped = deployment?.deploymentStopped === true;
|
|
1324
|
+
const ok = status === "SUCCESS" && !stopped && (instanceStatuses.length === 0 || instanceStatuses.some((candidate) => candidate === "RUNNING"));
|
|
1325
|
+
return {
|
|
1326
|
+
ok,
|
|
1327
|
+
status,
|
|
1328
|
+
branch: railwayConnectionLabel(deployment?.meta?.branch) || null,
|
|
1329
|
+
repo: railwayConnectionLabel(deployment?.meta?.repo) || null,
|
|
1330
|
+
rootDirectory: railwayConnectionLabel(deployment?.meta?.rootDirectory) || null,
|
|
1331
|
+
commitHash: railwayConnectionLabel(deployment?.meta?.commitHash) || null,
|
|
1332
|
+
requiredMountPath: railwayConnectionLabel(deployment?.meta?.serviceManifest?.deploy?.requiredMountPath) || null,
|
|
1333
|
+
volumeMounts: Array.isArray(deployment?.meta?.volumeMounts) ? deployment.meta.volumeMounts.map((entry) => railwayConnectionLabel(entry)).filter(Boolean) : [],
|
|
1334
|
+
message: ok ? "Deployment is healthy." : `Latest deployment status is ${status ?? "unknown"}${stopped ? " and stopped" : ""}${instanceStatuses.length ? `; instances=${instanceStatuses.join(",")}` : ""}.`
|
|
1335
|
+
};
|
|
757
1336
|
}
|
|
758
1337
|
async function listRailwayServices({
|
|
759
1338
|
projectId,
|
|
@@ -791,6 +1370,8 @@ async function getRailwayServiceInstance({
|
|
|
791
1370
|
const legacySummary = {
|
|
792
1371
|
id: null,
|
|
793
1372
|
buildCommand: null,
|
|
1373
|
+
dockerfilePath: null,
|
|
1374
|
+
railwayConfigFile: null,
|
|
794
1375
|
startCommand: null,
|
|
795
1376
|
cronSchedule: null,
|
|
796
1377
|
rootDirectory: null,
|
|
@@ -809,6 +1390,8 @@ query TreeseedRailwayServiceInstance($serviceId: String!, $environmentId: String
|
|
|
809
1390
|
serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {
|
|
810
1391
|
id
|
|
811
1392
|
buildCommand
|
|
1393
|
+
dockerfilePath
|
|
1394
|
+
railwayConfigFile
|
|
812
1395
|
startCommand
|
|
813
1396
|
cronSchedule
|
|
814
1397
|
rootDirectory
|
|
@@ -826,6 +1409,8 @@ query TreeseedRailwayServiceInstance($serviceId: String!, $environmentId: String
|
|
|
826
1409
|
return {
|
|
827
1410
|
id: railwayConnectionLabel(instance?.id) || null,
|
|
828
1411
|
buildCommand: railwayConnectionLabel(instance?.buildCommand) || null,
|
|
1412
|
+
dockerfilePath: railwayConnectionLabel(instance?.dockerfilePath) || null,
|
|
1413
|
+
railwayConfigFile: railwayConnectionLabel(instance?.railwayConfigFile) || null,
|
|
829
1414
|
startCommand: railwayConnectionLabel(instance?.startCommand) || null,
|
|
830
1415
|
cronSchedule: railwayConnectionLabel(instance?.cronSchedule) || null,
|
|
831
1416
|
rootDirectory: railwayConnectionLabel(instance?.rootDirectory) || null,
|
|
@@ -846,6 +1431,8 @@ query TreeseedRailwayServiceInstanceLegacy($serviceId: String!, $environmentId:
|
|
|
846
1431
|
serviceInstance(serviceId: $serviceId, environmentId: $environmentId) {
|
|
847
1432
|
id
|
|
848
1433
|
buildCommand
|
|
1434
|
+
dockerfilePath
|
|
1435
|
+
railwayConfigFile
|
|
849
1436
|
startCommand
|
|
850
1437
|
cronSchedule
|
|
851
1438
|
rootDirectory
|
|
@@ -861,6 +1448,8 @@ query TreeseedRailwayServiceInstanceLegacy($serviceId: String!, $environmentId:
|
|
|
861
1448
|
...legacySummary,
|
|
862
1449
|
id: railwayConnectionLabel(instance?.id) || null,
|
|
863
1450
|
buildCommand: railwayConnectionLabel(instance?.buildCommand) || null,
|
|
1451
|
+
dockerfilePath: railwayConnectionLabel(instance?.dockerfilePath) || null,
|
|
1452
|
+
railwayConfigFile: railwayConnectionLabel(instance?.railwayConfigFile) || null,
|
|
864
1453
|
startCommand: railwayConnectionLabel(instance?.startCommand) || null,
|
|
865
1454
|
cronSchedule: railwayConnectionLabel(instance?.cronSchedule) || null,
|
|
866
1455
|
rootDirectory: railwayConnectionLabel(instance?.rootDirectory) || null
|
|
@@ -876,6 +1465,8 @@ async function ensureRailwayServiceInstanceConfiguration({
|
|
|
876
1465
|
serviceId,
|
|
877
1466
|
environmentId,
|
|
878
1467
|
buildCommand,
|
|
1468
|
+
dockerfilePath,
|
|
1469
|
+
railwayConfigFile,
|
|
879
1470
|
startCommand,
|
|
880
1471
|
cronSchedule,
|
|
881
1472
|
rootDirectory,
|
|
@@ -884,6 +1475,7 @@ async function ensureRailwayServiceInstanceConfiguration({
|
|
|
884
1475
|
healthcheckIntervalSeconds,
|
|
885
1476
|
restartPolicy,
|
|
886
1477
|
runtimeMode,
|
|
1478
|
+
deploymentRegion,
|
|
887
1479
|
env = process.env,
|
|
888
1480
|
fetchImpl = fetch,
|
|
889
1481
|
settleAttempts = 60,
|
|
@@ -899,8 +1491,12 @@ async function ensureRailwayServiceInstanceConfiguration({
|
|
|
899
1491
|
if (!current.id) {
|
|
900
1492
|
return { instance: current, updated: false };
|
|
901
1493
|
}
|
|
1494
|
+
const desiredRuntimeMode = railwayConnectionLabel(runtimeMode) === "service" ? "replicated" : railwayConnectionLabel(runtimeMode) || null;
|
|
1495
|
+
const desiredDeploymentRegion = railwayConnectionLabel(deploymentRegion) || null;
|
|
902
1496
|
const desired = {
|
|
903
1497
|
buildCommand: railwayConnectionLabel(buildCommand) || null,
|
|
1498
|
+
dockerfilePath: railwayConnectionLabel(dockerfilePath) || null,
|
|
1499
|
+
railwayConfigFile: railwayConnectionLabel(railwayConfigFile) || null,
|
|
904
1500
|
startCommand: railwayConnectionLabel(startCommand) || null,
|
|
905
1501
|
cronSchedule: railwayConnectionLabel(cronSchedule) || null,
|
|
906
1502
|
rootDirectory: railwayConnectionLabel(rootDirectory) || null,
|
|
@@ -908,10 +1504,11 @@ async function ensureRailwayServiceInstanceConfiguration({
|
|
|
908
1504
|
healthcheckTimeoutSeconds: normalizeRailwayNumber(healthcheckTimeoutSeconds),
|
|
909
1505
|
healthcheckIntervalSeconds: normalizeRailwayNumber(healthcheckIntervalSeconds),
|
|
910
1506
|
restartPolicy: railwayConnectionLabel(restartPolicy) || null,
|
|
911
|
-
runtimeMode:
|
|
912
|
-
|
|
1507
|
+
runtimeMode: desiredRuntimeMode,
|
|
1508
|
+
deploymentRegion: desiredDeploymentRegion,
|
|
1509
|
+
sleepApplication: desiredRuntimeMode === "serverless" ? true : desiredRuntimeMode === "replicated" ? false : null
|
|
913
1510
|
};
|
|
914
|
-
const needsRuntimeConfig = desired.healthcheckPath !== null || desired.healthcheckTimeoutSeconds !== null || desired.runtimeMode !== null;
|
|
1511
|
+
const needsRuntimeConfig = desired.healthcheckPath !== null || desired.healthcheckTimeoutSeconds !== null || desired.runtimeMode !== null || desired.deploymentRegion !== null;
|
|
915
1512
|
if (needsRuntimeConfig && current.runtimeConfigSupported !== true) {
|
|
916
1513
|
throw new Error("Railway service instance runtime settings are unsupported by the current Railway API schema.");
|
|
917
1514
|
}
|
|
@@ -921,7 +1518,7 @@ async function ensureRailwayServiceInstanceConfiguration({
|
|
|
921
1518
|
if (desired.restartPolicy !== null) {
|
|
922
1519
|
throw new Error("Railway service instance restart policies are unsupported by the current Railway API schema.");
|
|
923
1520
|
}
|
|
924
|
-
const drifted = desired.buildCommand !== null && desired.buildCommand !== current.buildCommand || desired.startCommand !== null && desired.startCommand !== current.startCommand || desired.cronSchedule !== null && desired.cronSchedule !== current.cronSchedule || desired.rootDirectory !== null && desired.rootDirectory !== current.rootDirectory || desired.healthcheckPath !== null && desired.healthcheckPath !== current.healthcheckPath || desired.healthcheckTimeoutSeconds !== null && desired.healthcheckTimeoutSeconds !== current.healthcheckTimeoutSeconds || desired.runtimeMode !== null && desired.runtimeMode !== current.runtimeMode;
|
|
1521
|
+
const drifted = desired.buildCommand !== null && desired.buildCommand !== current.buildCommand || desired.dockerfilePath !== null && desired.dockerfilePath !== current.dockerfilePath || desired.railwayConfigFile !== null && desired.railwayConfigFile !== current.railwayConfigFile || desired.startCommand !== null && desired.startCommand !== current.startCommand || desired.cronSchedule !== null && desired.cronSchedule !== current.cronSchedule || desired.rootDirectory !== null && desired.rootDirectory !== current.rootDirectory || desired.healthcheckPath !== null && desired.healthcheckPath !== current.healthcheckPath || desired.healthcheckTimeoutSeconds !== null && desired.healthcheckTimeoutSeconds !== current.healthcheckTimeoutSeconds || desired.runtimeMode !== null && desired.runtimeMode !== current.runtimeMode || desired.deploymentRegion !== null;
|
|
925
1522
|
if (!drifted) {
|
|
926
1523
|
return { instance: current, updated: false };
|
|
927
1524
|
}
|
|
@@ -942,12 +1539,19 @@ mutation TreeseedRailwayServiceInstanceUpdateLegacy($serviceId: String!, $enviro
|
|
|
942
1539
|
environmentId,
|
|
943
1540
|
input: {
|
|
944
1541
|
...desired.buildCommand !== null ? { buildCommand: desired.buildCommand } : {},
|
|
1542
|
+
...desired.dockerfilePath !== null ? { dockerfilePath: desired.dockerfilePath } : {},
|
|
1543
|
+
...desired.railwayConfigFile !== null ? { railwayConfigFile: desired.railwayConfigFile } : {},
|
|
945
1544
|
...desired.startCommand !== null ? { startCommand: desired.startCommand } : {},
|
|
946
1545
|
...desired.cronSchedule !== null ? { cronSchedule: desired.cronSchedule } : {},
|
|
947
1546
|
...desired.rootDirectory !== null ? { rootDirectory: desired.rootDirectory } : {},
|
|
948
1547
|
...desired.healthcheckPath !== null ? { healthcheckPath: desired.healthcheckPath } : {},
|
|
949
1548
|
...desired.healthcheckTimeoutSeconds !== null ? { healthcheckTimeout: desired.healthcheckTimeoutSeconds } : {},
|
|
950
|
-
...desired.sleepApplication !== null ? { sleepApplication: desired.sleepApplication } : {}
|
|
1549
|
+
...desired.sleepApplication !== null ? { sleepApplication: desired.sleepApplication } : {},
|
|
1550
|
+
...desired.deploymentRegion !== null ? {
|
|
1551
|
+
multiRegionConfig: {
|
|
1552
|
+
[desired.deploymentRegion]: { numReplicas: 1 }
|
|
1553
|
+
}
|
|
1554
|
+
} : {}
|
|
951
1555
|
}
|
|
952
1556
|
},
|
|
953
1557
|
env,
|
|
@@ -1041,6 +1645,21 @@ mutation TreeseedRailwayVariableCollectionUpsert($input: VariableCollectionUpser
|
|
|
1041
1645
|
replace: false,
|
|
1042
1646
|
skipDeploys: true
|
|
1043
1647
|
};
|
|
1648
|
+
const upsertOne = (key, value) => railwayGraphqlRequest({
|
|
1649
|
+
query,
|
|
1650
|
+
variables: {
|
|
1651
|
+
input: {
|
|
1652
|
+
projectId,
|
|
1653
|
+
environmentId,
|
|
1654
|
+
serviceId: serviceId || null,
|
|
1655
|
+
variables: { [key]: value },
|
|
1656
|
+
replace: false,
|
|
1657
|
+
skipDeploys: true
|
|
1658
|
+
}
|
|
1659
|
+
},
|
|
1660
|
+
env,
|
|
1661
|
+
fetchImpl
|
|
1662
|
+
});
|
|
1044
1663
|
try {
|
|
1045
1664
|
await railwayGraphqlRequest({
|
|
1046
1665
|
query,
|
|
@@ -1054,23 +1673,29 @@ mutation TreeseedRailwayVariableCollectionUpsert($input: VariableCollectionUpser
|
|
|
1054
1673
|
throw error;
|
|
1055
1674
|
}
|
|
1056
1675
|
for (const [key, value] of Object.entries(variables)) {
|
|
1057
|
-
await
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1676
|
+
await upsertOne(key, value);
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
const observed = await listRailwayVariables({ projectId, environmentId, serviceId, env, fetchImpl }).catch(() => ({}));
|
|
1680
|
+
const missing = Object.keys(variables).filter((key) => observed[key] == null);
|
|
1681
|
+
for (const key of missing) {
|
|
1682
|
+
await upsertOne(key, variables[key]);
|
|
1683
|
+
}
|
|
1684
|
+
let retried = missing.length > 0 ? await listRailwayVariables({ projectId, environmentId, serviceId, env, fetchImpl }).catch(() => ({})) : observed;
|
|
1685
|
+
let stillMissing = Object.keys(variables).filter((key) => retried[key] == null);
|
|
1686
|
+
for (let attempt = 0; stillMissing.length > 0 && attempt < 12; attempt += 1) {
|
|
1687
|
+
await new Promise((resolve) => setTimeout(resolve, 2500));
|
|
1688
|
+
retried = await listRailwayVariables({ projectId, environmentId, serviceId, env, fetchImpl }).catch(() => ({}));
|
|
1689
|
+
stillMissing = Object.keys(variables).filter((key) => retried[key] == null);
|
|
1690
|
+
if (stillMissing.length > 0 && attempt === 5) {
|
|
1691
|
+
for (const key of stillMissing) {
|
|
1692
|
+
await upsertOne(key, variables[key]);
|
|
1693
|
+
}
|
|
1072
1694
|
}
|
|
1073
1695
|
}
|
|
1696
|
+
if (stillMissing.length > 0) {
|
|
1697
|
+
throw new Error(`Railway variable upsert did not persist: ${stillMissing.join(", ")}.`);
|
|
1698
|
+
}
|
|
1074
1699
|
}
|
|
1075
1700
|
async function listRailwayVolumes({
|
|
1076
1701
|
projectId,
|
|
@@ -1095,6 +1720,8 @@ query TreeseedRailwayVolumeList($projectId: String!) {
|
|
|
1095
1720
|
environmentId
|
|
1096
1721
|
mountPath
|
|
1097
1722
|
state
|
|
1723
|
+
isPendingDeletion
|
|
1724
|
+
deletedAt
|
|
1098
1725
|
}
|
|
1099
1726
|
}
|
|
1100
1727
|
}
|
|
@@ -1129,15 +1756,17 @@ mutation TreeseedRailwayVolumeCreate($input: VolumeCreateInput!) {
|
|
|
1129
1756
|
projectId
|
|
1130
1757
|
volumeInstances {
|
|
1131
1758
|
edges {
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1759
|
+
node {
|
|
1760
|
+
id
|
|
1761
|
+
serviceId
|
|
1762
|
+
environmentId
|
|
1763
|
+
mountPath
|
|
1764
|
+
state
|
|
1765
|
+
isPendingDeletion
|
|
1766
|
+
deletedAt
|
|
1767
|
+
}
|
|
1138
1768
|
}
|
|
1139
1769
|
}
|
|
1140
|
-
}
|
|
1141
1770
|
}
|
|
1142
1771
|
}
|
|
1143
1772
|
`.trim();
|
|
@@ -1193,15 +1822,17 @@ mutation TreeseedRailwayVolumeUpdate($volumeId: String!, $input: VolumeUpdateInp
|
|
|
1193
1822
|
projectId
|
|
1194
1823
|
volumeInstances {
|
|
1195
1824
|
edges {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1825
|
+
node {
|
|
1826
|
+
id
|
|
1827
|
+
serviceId
|
|
1828
|
+
environmentId
|
|
1829
|
+
mountPath
|
|
1830
|
+
state
|
|
1831
|
+
isPendingDeletion
|
|
1832
|
+
deletedAt
|
|
1833
|
+
}
|
|
1202
1834
|
}
|
|
1203
1835
|
}
|
|
1204
|
-
}
|
|
1205
1836
|
}
|
|
1206
1837
|
}
|
|
1207
1838
|
`.trim();
|
|
@@ -1233,7 +1864,7 @@ mutation TreeseedRailwayVolumeInstanceUpdate($volumeId: String!, $input: VolumeI
|
|
|
1233
1864
|
variables: {
|
|
1234
1865
|
volumeId,
|
|
1235
1866
|
input: {
|
|
1236
|
-
...serviceId ? { serviceId } : {},
|
|
1867
|
+
...serviceId !== void 0 ? { serviceId } : {},
|
|
1237
1868
|
mountPath
|
|
1238
1869
|
}
|
|
1239
1870
|
},
|
|
@@ -1248,7 +1879,9 @@ async function ensureRailwayServiceVolume({
|
|
|
1248
1879
|
name,
|
|
1249
1880
|
mountPath,
|
|
1250
1881
|
env = process.env,
|
|
1251
|
-
fetchImpl = fetch
|
|
1882
|
+
fetchImpl = fetch,
|
|
1883
|
+
settleAttempts = 24,
|
|
1884
|
+
settleDelayMs = 5e3
|
|
1252
1885
|
}) {
|
|
1253
1886
|
if (!mountPath.startsWith("/")) {
|
|
1254
1887
|
throw new Error(`Railway volume mount path must be absolute: ${mountPath}`);
|
|
@@ -1258,8 +1891,25 @@ async function ensureRailwayServiceVolume({
|
|
|
1258
1891
|
...candidate,
|
|
1259
1892
|
instances: candidate.instances.filter(isActiveRailwayVolumeInstance)
|
|
1260
1893
|
})).filter((candidate) => candidate.instances.length > 0);
|
|
1261
|
-
|
|
1894
|
+
const exactVolume = activeVolumes.find(
|
|
1895
|
+
(candidate) => candidate.name === name && candidate.instances.some(
|
|
1896
|
+
(instance2) => instance2.serviceId === serviceId && instance2.environmentId === environmentId && instance2.mountPath === mountPath
|
|
1897
|
+
)
|
|
1898
|
+
) ?? null;
|
|
1899
|
+
if (exactVolume) {
|
|
1900
|
+
return {
|
|
1901
|
+
volume: exactVolume,
|
|
1902
|
+
instance: exactVolume.instances.find(
|
|
1903
|
+
(instance2) => instance2.serviceId === serviceId && instance2.environmentId === environmentId && instance2.mountPath === mountPath
|
|
1904
|
+
) ?? null,
|
|
1905
|
+
created: false,
|
|
1906
|
+
updated: false
|
|
1907
|
+
};
|
|
1908
|
+
}
|
|
1909
|
+
let volume = findRailwayVolumeForService(volumes, serviceId, environmentId) ?? findRailwayVolumeForService(volumes, serviceId) ?? activeVolumes.find(
|
|
1262
1910
|
(candidate) => candidate.name === name && candidate.instances.some((instance2) => instance2.environmentId === environmentId)
|
|
1911
|
+
) ?? activeVolumes.find(
|
|
1912
|
+
(candidate) => candidate.name === name && (candidate.instances.length === 0 || candidate.instances.some((instance2) => instance2.environmentId === environmentId))
|
|
1263
1913
|
) ?? null;
|
|
1264
1914
|
let created = false;
|
|
1265
1915
|
let updated = false;
|
|
@@ -1282,7 +1932,13 @@ async function ensureRailwayServiceVolume({
|
|
|
1282
1932
|
for (let attempt = 0; attempt < 8; attempt += 1) {
|
|
1283
1933
|
await new Promise((resolve) => setTimeout(resolve, 1500));
|
|
1284
1934
|
const refreshed = await listRailwayVolumes({ projectId, env, fetchImpl });
|
|
1285
|
-
const
|
|
1935
|
+
const activeRefreshed = refreshed.map((candidate) => ({
|
|
1936
|
+
...candidate,
|
|
1937
|
+
instances: candidate.instances.filter(isActiveRailwayVolumeInstance)
|
|
1938
|
+
})).filter((candidate) => candidate.instances.length > 0);
|
|
1939
|
+
const existing = findRailwayVolumeForService(refreshed, serviceId, environmentId) ?? findRailwayVolumeForService(refreshed, serviceId) ?? activeRefreshed.find(
|
|
1940
|
+
(candidate) => candidate.name === name && (candidate.instances.length === 0 || candidate.instances.some((instance2) => instance2.environmentId === environmentId))
|
|
1941
|
+
) ?? findSoleActiveRailwayVolumeForEnvironment(refreshed, environmentId);
|
|
1286
1942
|
if (existing) {
|
|
1287
1943
|
return existing;
|
|
1288
1944
|
}
|
|
@@ -1295,7 +1951,9 @@ async function ensureRailwayServiceVolume({
|
|
|
1295
1951
|
if (!volume) {
|
|
1296
1952
|
volume = await createReplacementVolume();
|
|
1297
1953
|
}
|
|
1298
|
-
const desiredNameInUse = volumes.some(
|
|
1954
|
+
const desiredNameInUse = volumes.some(
|
|
1955
|
+
(candidate) => candidate.id !== volume?.id && candidate.name === name && candidate.instances.some(isActiveRailwayVolumeInstance)
|
|
1956
|
+
);
|
|
1299
1957
|
if (volume.name && volume.name !== name && !desiredNameInUse) {
|
|
1300
1958
|
try {
|
|
1301
1959
|
volume = await updateRailwayVolumeName({ volumeId: volume.id, name, env, fetchImpl }) ?? { ...volume, name };
|
|
@@ -1313,10 +1971,33 @@ async function ensureRailwayServiceVolume({
|
|
|
1313
1971
|
await updateRailwayVolumeInstanceMountPath({ volumeId: volume.id, serviceId, mountPath, env, fetchImpl });
|
|
1314
1972
|
volume = await listRailwayVolumes({ projectId, env, fetchImpl }).then((refreshed) => refreshed.find((candidate) => candidate.id === volume?.id) ?? volume);
|
|
1315
1973
|
} catch (error) {
|
|
1316
|
-
if (
|
|
1974
|
+
if (looksLikeRailwayVolumeCreateRace(error)) {
|
|
1975
|
+
volume = await listRailwayVolumes({ projectId, env, fetchImpl }).then(
|
|
1976
|
+
(refreshed) => findRailwayVolumeForService(refreshed, serviceId, environmentId) ?? volume
|
|
1977
|
+
);
|
|
1978
|
+
} else if (!looksLikeRailwayMissingResource(error)) {
|
|
1317
1979
|
throw error;
|
|
1980
|
+
} else {
|
|
1981
|
+
volume = await createReplacementVolume();
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? null;
|
|
1985
|
+
updated = true;
|
|
1986
|
+
}
|
|
1987
|
+
if (!instance) {
|
|
1988
|
+
try {
|
|
1989
|
+
await updateRailwayVolumeInstanceMountPath({ volumeId: volume.id, serviceId, mountPath, env, fetchImpl });
|
|
1990
|
+
volume = await listRailwayVolumes({ projectId, env, fetchImpl }).then((refreshed) => refreshed.find((candidate) => candidate.id === volume?.id) ?? volume);
|
|
1991
|
+
} catch (error) {
|
|
1992
|
+
if (looksLikeRailwayVolumeCreateRace(error)) {
|
|
1993
|
+
volume = await listRailwayVolumes({ projectId, env, fetchImpl }).then(
|
|
1994
|
+
(refreshed) => findRailwayVolumeForService(refreshed, serviceId, environmentId) ?? volume
|
|
1995
|
+
);
|
|
1996
|
+
} else if (!looksLikeRailwayMissingResource(error)) {
|
|
1997
|
+
throw error;
|
|
1998
|
+
} else {
|
|
1999
|
+
volume = await createReplacementVolume();
|
|
1318
2000
|
}
|
|
1319
|
-
volume = await createReplacementVolume();
|
|
1320
2001
|
}
|
|
1321
2002
|
instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? null;
|
|
1322
2003
|
updated = true;
|
|
@@ -1337,18 +2018,77 @@ async function ensureRailwayServiceVolume({
|
|
|
1337
2018
|
}
|
|
1338
2019
|
updated = true;
|
|
1339
2020
|
}
|
|
2021
|
+
const settled = await waitForRailwayVolumeMount({
|
|
2022
|
+
projectId,
|
|
2023
|
+
volume,
|
|
2024
|
+
serviceId,
|
|
2025
|
+
environmentId,
|
|
2026
|
+
mountPath,
|
|
2027
|
+
env,
|
|
2028
|
+
fetchImpl,
|
|
2029
|
+
settleAttempts,
|
|
2030
|
+
settleDelayMs
|
|
2031
|
+
});
|
|
2032
|
+
if (settled) {
|
|
2033
|
+
volume = settled.volume;
|
|
2034
|
+
instance = settled.instance;
|
|
2035
|
+
}
|
|
2036
|
+
if (!instance || instance.serviceId !== serviceId || instance.environmentId !== environmentId || instance.mountPath !== mountPath) {
|
|
2037
|
+
throw new Error(`Railway API volume reconciliation did not observe ${name} mounted on service ${serviceId} at ${mountPath}.`);
|
|
2038
|
+
}
|
|
1340
2039
|
return { volume, instance, created, updated };
|
|
1341
2040
|
}
|
|
2041
|
+
async function waitForRailwayVolumeMount({
|
|
2042
|
+
projectId,
|
|
2043
|
+
volume,
|
|
2044
|
+
serviceId,
|
|
2045
|
+
environmentId,
|
|
2046
|
+
mountPath,
|
|
2047
|
+
env,
|
|
2048
|
+
fetchImpl,
|
|
2049
|
+
settleAttempts,
|
|
2050
|
+
settleDelayMs
|
|
2051
|
+
}) {
|
|
2052
|
+
const mountedInstance = (candidate) => candidate.instances.find(
|
|
2053
|
+
(entry) => entry.serviceId === serviceId && entry.environmentId === environmentId && entry.mountPath === mountPath && isActiveRailwayVolumeInstance(entry)
|
|
2054
|
+
) ?? null;
|
|
2055
|
+
let instance = mountedInstance(volume);
|
|
2056
|
+
if (instance) {
|
|
2057
|
+
return { volume, instance };
|
|
2058
|
+
}
|
|
2059
|
+
for (let attempt = 0; attempt < settleAttempts; attempt += 1) {
|
|
2060
|
+
await new Promise((resolve) => setTimeout(resolve, settleDelayMs));
|
|
2061
|
+
const refreshed = await listRailwayVolumes({ projectId, env, fetchImpl });
|
|
2062
|
+
const refreshedVolume = refreshed.find((candidate) => candidate.id === volume.id) ?? findRailwayVolumeForService(refreshed, serviceId, environmentId) ?? null;
|
|
2063
|
+
if (!refreshedVolume) {
|
|
2064
|
+
continue;
|
|
2065
|
+
}
|
|
2066
|
+
instance = mountedInstance(refreshedVolume);
|
|
2067
|
+
if (instance) {
|
|
2068
|
+
return { volume: refreshedVolume, instance };
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
return null;
|
|
2072
|
+
}
|
|
1342
2073
|
function findRailwayVolumeForService(volumes, serviceId, environmentId) {
|
|
1343
2074
|
return volumes.find(
|
|
1344
2075
|
(candidate) => candidate.instances.some(
|
|
1345
|
-
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && isActiveRailwayVolumeInstance(instance)
|
|
2076
|
+
(instance) => instance.serviceId === serviceId && (!environmentId || instance.environmentId === environmentId) && isActiveRailwayVolumeInstance(instance)
|
|
1346
2077
|
)
|
|
1347
2078
|
) ?? null;
|
|
1348
2079
|
}
|
|
2080
|
+
function findSoleActiveRailwayVolumeForEnvironment(volumes, environmentId) {
|
|
2081
|
+
const active = volumes.map((candidate) => ({
|
|
2082
|
+
...candidate,
|
|
2083
|
+
instances: candidate.instances.filter(
|
|
2084
|
+
(instance) => instance.environmentId === environmentId && isActiveRailwayVolumeInstance(instance)
|
|
2085
|
+
)
|
|
2086
|
+
})).filter((candidate) => candidate.instances.length > 0);
|
|
2087
|
+
return active.length === 1 ? active[0] : null;
|
|
2088
|
+
}
|
|
1349
2089
|
function looksLikeRailwayVolumeCreateRace(error) {
|
|
1350
2090
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1351
|
-
return /would have \d+ volumes attached|can only have one volume|not authorized/iu.test(message);
|
|
2091
|
+
return /already has a volume attached|would have \d+ volumes attached|can only have one volume|volume named .* already exists|already exists in this project|not authorized/iu.test(message);
|
|
1352
2092
|
}
|
|
1353
2093
|
async function listRailwayCustomDomains({
|
|
1354
2094
|
projectId,
|
|
@@ -1463,6 +2203,10 @@ function looksLikeRailwayMissingResource(error) {
|
|
|
1463
2203
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1464
2204
|
return /not found|does not exist|could not find|unknown|invalid .*id/iu.test(message);
|
|
1465
2205
|
}
|
|
2206
|
+
function looksLikeRailwayOperationInProgress(error) {
|
|
2207
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2208
|
+
return /operation is already in progress/iu.test(message);
|
|
2209
|
+
}
|
|
1466
2210
|
async function railwayDeleteMutation({
|
|
1467
2211
|
query,
|
|
1468
2212
|
variables,
|
|
@@ -1470,20 +2214,32 @@ async function railwayDeleteMutation({
|
|
|
1470
2214
|
fetchImpl,
|
|
1471
2215
|
missingResult
|
|
1472
2216
|
}) {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
2217
|
+
let lastError = null;
|
|
2218
|
+
for (let attempt = 0; attempt < 6; attempt += 1) {
|
|
2219
|
+
try {
|
|
2220
|
+
const payload = await railwayGraphqlRequest({
|
|
2221
|
+
query,
|
|
2222
|
+
variables,
|
|
2223
|
+
env,
|
|
2224
|
+
fetchImpl
|
|
2225
|
+
});
|
|
2226
|
+
const mutationResult = Object.values(payload.data ?? {})[0];
|
|
2227
|
+
if (mutationResult === false || mutationResult == null) {
|
|
2228
|
+
throw new Error("Railway delete mutation returned no successful deletion result.");
|
|
2229
|
+
}
|
|
2230
|
+
return { status: "deleted" };
|
|
2231
|
+
} catch (error) {
|
|
2232
|
+
if (looksLikeRailwayMissingResource(error)) {
|
|
2233
|
+
return missingResult;
|
|
2234
|
+
}
|
|
2235
|
+
if (!looksLikeRailwayOperationInProgress(error) || attempt >= 5) {
|
|
2236
|
+
throw error;
|
|
2237
|
+
}
|
|
2238
|
+
lastError = error;
|
|
2239
|
+
await new Promise((resolve) => setTimeout(resolve, 2500 * (attempt + 1)));
|
|
1484
2240
|
}
|
|
1485
|
-
throw error;
|
|
1486
2241
|
}
|
|
2242
|
+
throw lastError instanceof Error ? lastError : new Error(String(lastError ?? "Railway delete mutation did not complete."));
|
|
1487
2243
|
}
|
|
1488
2244
|
async function deleteRailwayCustomDomain({
|
|
1489
2245
|
domainId,
|
|
@@ -1506,6 +2262,27 @@ mutation TreeseedRailwayCustomDomainDelete($id: String!) {
|
|
|
1506
2262
|
missingResult: { status: "missing", id: domainId }
|
|
1507
2263
|
});
|
|
1508
2264
|
}
|
|
2265
|
+
async function deleteRailwayService({
|
|
2266
|
+
serviceId,
|
|
2267
|
+
env = process.env,
|
|
2268
|
+
fetchImpl = fetch
|
|
2269
|
+
}) {
|
|
2270
|
+
if (!railwayConnectionLabel(serviceId)) {
|
|
2271
|
+
return { status: "missing", id: serviceId };
|
|
2272
|
+
}
|
|
2273
|
+
const mutation = configuredEnvValue(env, "TREESEED_RAILWAY_SERVICE_DELETE_MUTATION") || `
|
|
2274
|
+
mutation TreeseedRailwayServiceDelete($id: String!) {
|
|
2275
|
+
serviceDelete(id: $id)
|
|
2276
|
+
}
|
|
2277
|
+
`.trim();
|
|
2278
|
+
return railwayDeleteMutation({
|
|
2279
|
+
query: mutation,
|
|
2280
|
+
variables: { id: serviceId },
|
|
2281
|
+
env,
|
|
2282
|
+
fetchImpl,
|
|
2283
|
+
missingResult: { status: "missing", id: serviceId }
|
|
2284
|
+
});
|
|
2285
|
+
}
|
|
1509
2286
|
async function deleteRailwayVolume({
|
|
1510
2287
|
volumeId,
|
|
1511
2288
|
env = process.env,
|
|
@@ -1514,18 +2291,55 @@ async function deleteRailwayVolume({
|
|
|
1514
2291
|
if (!railwayConnectionLabel(volumeId)) {
|
|
1515
2292
|
return { status: "missing", id: volumeId };
|
|
1516
2293
|
}
|
|
1517
|
-
const
|
|
2294
|
+
const configuredMutation = configuredEnvValue(env, "TREESEED_RAILWAY_VOLUME_DELETE_MUTATION");
|
|
2295
|
+
if (configuredMutation) {
|
|
2296
|
+
return railwayDeleteMutation({
|
|
2297
|
+
query: configuredMutation,
|
|
2298
|
+
variables: { volumeId, id: volumeId },
|
|
2299
|
+
env,
|
|
2300
|
+
fetchImpl,
|
|
2301
|
+
missingResult: { status: "missing", id: volumeId }
|
|
2302
|
+
});
|
|
2303
|
+
}
|
|
2304
|
+
const primaryMutation = `
|
|
1518
2305
|
mutation TreeseedRailwayVolumeDelete($volumeId: String!) {
|
|
1519
2306
|
volumeDelete(volumeId: $volumeId)
|
|
1520
2307
|
}
|
|
1521
2308
|
`.trim();
|
|
1522
|
-
|
|
1523
|
-
|
|
2309
|
+
const fallbackMutation = `
|
|
2310
|
+
mutation TreeseedRailwayVolumeDeleteById($id: String!) {
|
|
2311
|
+
volumeDelete(volumeId: $id)
|
|
2312
|
+
}
|
|
2313
|
+
`.trim();
|
|
2314
|
+
const primary = await railwayDeleteMutation({
|
|
2315
|
+
query: primaryMutation,
|
|
1524
2316
|
variables: { volumeId },
|
|
1525
2317
|
env,
|
|
1526
2318
|
fetchImpl,
|
|
1527
2319
|
missingResult: { status: "missing", id: volumeId }
|
|
2320
|
+
}).catch((error) => {
|
|
2321
|
+
if (looksLikeRailwayVolumeDeleteShapeUnsupported(error)) {
|
|
2322
|
+
return null;
|
|
2323
|
+
}
|
|
2324
|
+
throw error;
|
|
1528
2325
|
});
|
|
2326
|
+
const fallback = await railwayDeleteMutation({
|
|
2327
|
+
query: fallbackMutation,
|
|
2328
|
+
variables: { id: volumeId },
|
|
2329
|
+
env,
|
|
2330
|
+
fetchImpl,
|
|
2331
|
+
missingResult: { status: "missing", id: volumeId }
|
|
2332
|
+
}).catch((error) => {
|
|
2333
|
+
if (looksLikeRailwayVolumeDeleteShapeUnsupported(error) && primary) {
|
|
2334
|
+
return primary;
|
|
2335
|
+
}
|
|
2336
|
+
throw error;
|
|
2337
|
+
});
|
|
2338
|
+
return fallback ?? primary ?? { status: "deleted" };
|
|
2339
|
+
}
|
|
2340
|
+
function looksLikeRailwayVolumeDeleteShapeUnsupported(error) {
|
|
2341
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
2342
|
+
return /Unknown argument|Cannot query field|Unknown field|Field .* is not defined|volumeDelete.*argument|Problem processing request/iu.test(message);
|
|
1529
2343
|
}
|
|
1530
2344
|
async function deleteRailwayEnvironment({
|
|
1531
2345
|
environmentId,
|
|
@@ -1573,9 +2387,12 @@ export {
|
|
|
1573
2387
|
deleteRailwayCustomDomain,
|
|
1574
2388
|
deleteRailwayEnvironment,
|
|
1575
2389
|
deleteRailwayProject,
|
|
2390
|
+
deleteRailwayService,
|
|
1576
2391
|
deleteRailwayVolume,
|
|
2392
|
+
deployRailwayServiceInstance,
|
|
1577
2393
|
ensureRailwayCustomDomain,
|
|
1578
2394
|
ensureRailwayEnvironment,
|
|
2395
|
+
ensureRailwayGeneratedServiceDomain,
|
|
1579
2396
|
ensureRailwayPostgresService,
|
|
1580
2397
|
ensureRailwayProject,
|
|
1581
2398
|
ensureRailwayService,
|
|
@@ -1584,10 +2401,13 @@ export {
|
|
|
1584
2401
|
getRailwayAuthProfile,
|
|
1585
2402
|
getRailwayProject,
|
|
1586
2403
|
getRailwayServiceInstance,
|
|
2404
|
+
inspectRailwayServiceDeploymentHealth,
|
|
1587
2405
|
isUsableRailwayToken,
|
|
1588
2406
|
listRailwayCustomDomains,
|
|
2407
|
+
listRailwayEnvironmentServices,
|
|
1589
2408
|
listRailwayEnvironments,
|
|
1590
2409
|
listRailwayProjects,
|
|
2410
|
+
listRailwayServiceDomains,
|
|
1591
2411
|
listRailwayServices,
|
|
1592
2412
|
listRailwayVariables,
|
|
1593
2413
|
listRailwayVolumes,
|
|
@@ -1597,6 +2417,8 @@ export {
|
|
|
1597
2417
|
resolveRailwayApiUrl,
|
|
1598
2418
|
resolveRailwayWorkspace,
|
|
1599
2419
|
resolveRailwayWorkspaceContext,
|
|
2420
|
+
updateRailwayServiceGitSource,
|
|
2421
|
+
updateRailwayServiceImageSource,
|
|
1600
2422
|
updateRailwayServiceName,
|
|
1601
2423
|
upsertRailwayVariables
|
|
1602
2424
|
};
|