@treeseed/sdk 0.10.28 → 0.11.0
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 +207 -6
- package/dist/capacity-provider.d.ts +3 -1
- package/dist/capacity-provider.js +25 -5
- package/dist/control-plane.d.ts +1 -0
- package/dist/control-plane.js +38 -13
- package/dist/db/market-schema.d.ts +8860 -6172
- package/dist/db/market-schema.js +108 -0
- package/dist/db/node-sqlite.js +7 -2
- 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 +791 -0
- package/dist/hosting/contracts.d.ts +207 -0
- package/dist/hosting/contracts.js +0 -0
- package/dist/hosting/graph.d.ts +192 -0
- package/dist/hosting/graph.js +1106 -0
- package/dist/hosting/index.d.ts +4 -0
- package/dist/hosting/index.js +4 -0
- package/dist/index.d.ts +10 -3
- package/dist/index.js +63 -6
- package/dist/managed-dependencies.js +1 -2
- package/dist/market-client.d.ts +63 -3
- package/dist/market-client.js +83 -11
- package/dist/operations/services/bootstrap-runner.d.ts +3 -1
- package/dist/operations/services/bootstrap-runner.js +22 -2
- package/dist/operations/services/config-runtime.d.ts +10 -5
- package/dist/operations/services/config-runtime.js +209 -66
- package/dist/operations/services/deploy.d.ts +70 -7
- package/dist/operations/services/deploy.js +579 -64
- package/dist/operations/services/deployment-readiness.d.ts +30 -0
- package/dist/operations/services/deployment-readiness.js +175 -0
- package/dist/operations/services/git-workflow.d.ts +2 -1
- package/dist/operations/services/git-workflow.js +9 -3
- package/dist/operations/services/github-actions-verification.d.ts +1 -0
- package/dist/operations/services/github-actions-verification.js +1 -0
- package/dist/operations/services/github-api.js +1 -1
- package/dist/operations/services/github-automation.d.ts +1 -1
- package/dist/operations/services/github-automation.js +4 -3
- package/dist/operations/services/github-credentials.d.ts +13 -0
- package/dist/operations/services/github-credentials.js +58 -0
- package/dist/operations/services/hosted-service-checks.d.ts +63 -0
- package/dist/operations/services/hosted-service-checks.js +327 -0
- package/dist/operations/services/hub-provider-launch.js +3 -3
- package/dist/operations/services/live-hosted-service-checks.d.ts +25 -0
- package/dist/operations/services/live-hosted-service-checks.js +350 -0
- package/dist/operations/services/managed-host-security.js +1 -1
- package/dist/operations/services/operations-runner-smoke.d.ts +30 -0
- package/dist/operations/services/operations-runner-smoke.js +180 -0
- package/dist/operations/services/package-adapters.d.ts +95 -0
- package/dist/operations/services/package-adapters.js +288 -0
- package/dist/operations/services/package-reference-policy.d.ts +1 -0
- package/dist/operations/services/package-reference-policy.js +15 -2
- package/dist/operations/services/project-platform.d.ts +80 -22
- package/dist/operations/services/project-platform.js +49 -8
- package/dist/operations/services/project-web-monitor.js +26 -4
- package/dist/operations/services/railway-api.d.ts +88 -5
- package/dist/operations/services/railway-api.js +626 -35
- package/dist/operations/services/railway-deploy.d.ts +46 -40
- package/dist/operations/services/railway-deploy.js +261 -293
- package/dist/operations/services/release-candidate.d.ts +19 -0
- package/dist/operations/services/release-candidate.js +375 -38
- package/dist/operations/services/repository-save-orchestrator.d.ts +3 -1
- package/dist/operations/services/repository-save-orchestrator.js +279 -66
- package/dist/operations/services/runtime-tools.d.ts +1 -0
- package/dist/operations/services/runtime-tools.js +10 -9
- package/dist/operations/services/verification-cache.d.ts +25 -0
- package/dist/operations/services/verification-cache.js +71 -0
- package/dist/operations/services/workspace-dependency-mode.js +9 -1
- package/dist/operations/services/workspace-save.js +1 -1
- package/dist/operations/services/workspace-tools.js +2 -1
- package/dist/platform/contracts.d.ts +32 -1
- package/dist/platform/deploy-config.js +73 -8
- package/dist/platform/env.yaml +163 -35
- package/dist/platform/environment.d.ts +1 -0
- package/dist/platform/environment.js +74 -5
- package/dist/platform/plugin.d.ts +9 -0
- package/dist/platform-operation-store.js +2 -2
- package/dist/platform-operations.js +1 -1
- package/dist/reconcile/bootstrap-systems.js +2 -2
- package/dist/reconcile/builtin-adapters.js +372 -189
- package/dist/reconcile/contracts.d.ts +9 -5
- package/dist/reconcile/desired-state.d.ts +1 -0
- package/dist/reconcile/desired-state.js +5 -5
- package/dist/reconcile/engine.d.ts +5 -2
- package/dist/reconcile/engine.js +53 -32
- package/dist/reconcile/index.d.ts +2 -0
- package/dist/reconcile/index.js +2 -0
- package/dist/reconcile/live-acceptance.d.ts +79 -0
- package/dist/reconcile/live-acceptance.js +1615 -0
- package/dist/reconcile/platform.d.ts +104 -0
- package/dist/reconcile/platform.js +100 -0
- package/dist/reconcile/state.js +4 -4
- package/dist/reconcile/units.js +2 -2
- package/dist/scripts/deployment-readiness.js +20 -0
- package/dist/scripts/generate-treedx-openapi-types.js +186 -0
- package/dist/scripts/operations-runner-smoke.js +16 -0
- package/dist/scripts/release-verify.js +4 -1
- package/dist/scripts/tenant-workflow-action.js +10 -1
- package/dist/sdk-types.d.ts +169 -4
- package/dist/sdk-types.js +20 -2
- package/dist/sdk.d.ts +35 -24
- package/dist/sdk.js +186 -17
- package/dist/template-launch-requirements.js +9 -0
- 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 +308 -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 +218 -0
- package/dist/treedx-backends.js +632 -0
- package/dist/treedx-client.d.ts +86 -0
- package/dist/treedx-client.js +175 -0
- package/dist/treeseed/template-catalog/catalog.fixture.json +23 -23
- package/dist/workflow/operations.d.ts +119 -13
- package/dist/workflow/operations.js +309 -53
- package/dist/workflow-state.d.ts +13 -0
- package/dist/workflow-state.js +43 -26
- package/dist/workflow-support.d.ts +11 -3
- package/dist/workflow-support.js +67 -3
- package/dist/workflow.d.ts +5 -0
- package/drizzle/market/0004_treedx_market_integration.sql +99 -0
- package/package.json +34 -3
- package/templates/github/deploy-web.workflow.yml +39 -6
|
@@ -3,6 +3,7 @@ import { dirname, relative, resolve } from "node:path";
|
|
|
3
3
|
import { spawnSync } from "node:child_process";
|
|
4
4
|
import { loadCliDeployConfig } from "./runtime-tools.js";
|
|
5
5
|
import { createPersistentDeployTarget, resolveTreeseedResourceIdentity } from "./deploy.js";
|
|
6
|
+
import { discoverTreeseedApplications } from "../../hosting/apps.js";
|
|
6
7
|
import { runPrefixedCommand, sleep } from "./bootstrap-runner.js";
|
|
7
8
|
import { resolveTreeseedToolCommand } from "../../managed-dependencies.js";
|
|
8
9
|
import {
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
ensureRailwayService,
|
|
12
13
|
ensureRailwayServiceInstanceConfiguration,
|
|
13
14
|
ensureRailwayServiceVolume,
|
|
15
|
+
deployRailwayServiceInstance,
|
|
14
16
|
getRailwayServiceInstance,
|
|
15
17
|
listRailwayEnvironments,
|
|
16
18
|
listRailwayProjects,
|
|
@@ -31,37 +33,49 @@ 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"];
|
|
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
|
+
function isTreeseedOperationsRunnerResourceName(value) {
|
|
42
|
+
const normalized = String(value ?? "").trim().toLowerCase();
|
|
43
|
+
if (!normalized) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
if (normalized.startsWith("market-ops")) {
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
return normalized.includes("operations-runner");
|
|
50
|
+
}
|
|
51
|
+
function findStaleTreeseedOperationsRunnerResources(resources, desiredNames) {
|
|
52
|
+
const desired = new Set([...desiredNames].map((value) => String(value ?? "").trim()).filter(Boolean));
|
|
53
|
+
return resources.filter((resource) => {
|
|
54
|
+
const name = String(resource?.name ?? "").trim();
|
|
55
|
+
return name && isTreeseedOperationsRunnerResourceName(name) && !desired.has(name);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
39
58
|
function shouldManageRailwaySchedules(scope, phase = "deploy") {
|
|
40
59
|
const environment = normalizeRailwayEnvironmentName(scope);
|
|
41
60
|
return phase === "deploy" && (environment === "staging" || environment === "production");
|
|
42
61
|
}
|
|
43
62
|
function railwayServiceNameSuffix(serviceKey) {
|
|
44
|
-
return serviceKey === "workdayManager" ? "workday-manager" : serviceKey === "workerRunner" ? "worker-runner" : serviceKey === "
|
|
63
|
+
return serviceKey === "workdayManager" ? "workday-manager" : serviceKey === "workerRunner" ? "worker-runner" : serviceKey === "operationsRunner" ? "operations-runner" : serviceKey;
|
|
45
64
|
}
|
|
46
65
|
function deriveRailwayWorkerRunnerServiceName(projectSlug, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
|
|
47
66
|
const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
|
|
48
67
|
return `${projectSlug}-worker-runner-${String(normalizedIndex).padStart(2, "0")}`;
|
|
49
68
|
}
|
|
50
|
-
function
|
|
69
|
+
function deriveRailwayOperationsRunnerServiceName(baseServiceName, index = WORKER_RUNNER_BOOTSTRAP_INDEX) {
|
|
51
70
|
const normalizedIndex = Math.max(1, Number.parseInt(String(index), 10) || WORKER_RUNNER_BOOTSTRAP_INDEX);
|
|
52
|
-
const base = String(baseServiceName ?? "").trim().replace(/-\d+$/u, "") || "treeseed-
|
|
71
|
+
const base = String(baseServiceName ?? "").trim().replace(/-\d+$/u, "").replace(/-\d{2}$/u, "") || "treeseed-api-operations-runner";
|
|
53
72
|
return `${base}-${String(normalizedIndex).padStart(2, "0")}`;
|
|
54
73
|
}
|
|
55
74
|
function deriveRailwayWorkerRunnerVolumeName(serviceName, environmentName = "") {
|
|
56
|
-
|
|
57
|
-
const environmentSuffix = environment === "production" ? "-prod" : environment ? `-${environment}` : "";
|
|
58
|
-
return `${serviceName}${environmentSuffix}-data`;
|
|
75
|
+
return `${serviceName}-volume`;
|
|
59
76
|
}
|
|
60
|
-
function
|
|
61
|
-
|
|
62
|
-
const environmentSuffix = environment === "production" ? "-prod" : environment ? `-${environment}` : "";
|
|
63
|
-
const index = String(serviceName ?? "").match(/-(\d+)$/u)?.[1] ?? "01";
|
|
64
|
-
return `market-ops-runner-${index}${environmentSuffix}-data`;
|
|
77
|
+
function deriveRailwayOperationsRunnerVolumeName(serviceName, environmentName = "") {
|
|
78
|
+
return `${serviceName}-volume`;
|
|
65
79
|
}
|
|
66
80
|
function railwayServiceRuntimeStartCommand(service) {
|
|
67
81
|
return service.startCommand;
|
|
@@ -87,6 +101,10 @@ function configuredEnvValue(env, name) {
|
|
|
87
101
|
const value = env?.[name];
|
|
88
102
|
return typeof value === "string" && value.trim() ? value.trim() : "";
|
|
89
103
|
}
|
|
104
|
+
function railwayDeployTransport(env) {
|
|
105
|
+
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_TRANSPORT").toLowerCase();
|
|
106
|
+
return configured === "cli-fallback" ? "cli-fallback" : "api";
|
|
107
|
+
}
|
|
90
108
|
async function timedRailwayPhase(timings, name, run, metadata) {
|
|
91
109
|
const startMs = performance.now();
|
|
92
110
|
try {
|
|
@@ -149,6 +167,10 @@ function railwayStatusDeploymentSettled(status) {
|
|
|
149
167
|
const normalized = String(status ?? "").trim().toUpperCase();
|
|
150
168
|
return normalized === "SUCCESS" || normalized === "SLEEPING";
|
|
151
169
|
}
|
|
170
|
+
function railwayStatusDeploymentTerminalFailure(status) {
|
|
171
|
+
const normalized = String(status ?? "").trim().toUpperCase();
|
|
172
|
+
return ["FAILED", "CRASHED", "REMOVED"].includes(normalized);
|
|
173
|
+
}
|
|
152
174
|
function formatRailwayDeploymentStatusSummary(scope, checks) {
|
|
153
175
|
const aliases = {
|
|
154
176
|
api: "api",
|
|
@@ -219,12 +241,14 @@ function collectRailwayDeploymentStatusChecks(statusPayload, scope, services) {
|
|
|
219
241
|
const status = String(deployment?.status ?? "").trim().toUpperCase();
|
|
220
242
|
const instanceStatuses = Array.isArray(deployment?.instances) ? deployment.instances.map((entry) => String(entry?.status ?? "").trim()).filter(Boolean) : [];
|
|
221
243
|
const ok = railwayStatusDeploymentSettled(status);
|
|
244
|
+
const terminalFailure = railwayStatusDeploymentTerminalFailure(status);
|
|
222
245
|
return {
|
|
223
246
|
type: "deployment-status",
|
|
224
247
|
service: service.key,
|
|
225
248
|
serviceName: service.serviceName,
|
|
226
249
|
environment: normalizeRailwayEnvironmentName(environment.name),
|
|
227
250
|
ok,
|
|
251
|
+
terminalFailure,
|
|
228
252
|
status: status || "missing_deployment",
|
|
229
253
|
observed: {
|
|
230
254
|
status: status || null,
|
|
@@ -234,74 +258,10 @@ function collectRailwayDeploymentStatusChecks(statusPayload, scope, services) {
|
|
|
234
258
|
instanceStatuses,
|
|
235
259
|
volumeMounts: Array.isArray(deployment?.meta?.volumeMounts) ? deployment.meta.volumeMounts : []
|
|
236
260
|
},
|
|
237
|
-
message: ok ? void 0 : `Railway deployment for ${service.serviceName} is not settled yet; observed ${status || "missing deployment status"}.`
|
|
261
|
+
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
262
|
};
|
|
239
263
|
});
|
|
240
264
|
}
|
|
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
265
|
function isUsableRailwayToken(value) {
|
|
306
266
|
return typeof value === "string" && value.trim().length >= 8;
|
|
307
267
|
}
|
|
@@ -324,6 +284,8 @@ function buildRailwayDeployCommandEnv(env = process.env) {
|
|
|
324
284
|
const merged = buildRailwayCommandEnv(env);
|
|
325
285
|
if (shouldAttachRailwayDeployLogs(merged)) {
|
|
326
286
|
merged.CI = "true";
|
|
287
|
+
} else {
|
|
288
|
+
merged.CI = void 0;
|
|
327
289
|
}
|
|
328
290
|
return merged;
|
|
329
291
|
}
|
|
@@ -489,7 +451,7 @@ mutation TreeseedScheduleUpdate($id: String!, $name: String!, $schedule: String!
|
|
|
489
451
|
`.trim()
|
|
490
452
|
};
|
|
491
453
|
}
|
|
492
|
-
function runRailway(args, { cwd, capture = false, allowFailure = false, input, env, retryTransient = true, retryAttempts = 3, retryDelayMs = 2e3 } = {}) {
|
|
454
|
+
function runRailway(args, { cwd, capture = false, allowFailure = false, input, env, retryTransient = true, retryAttempts = 3, retryDelayMs = 2e3, timeoutMs = 12e4 } = {}) {
|
|
493
455
|
const effectiveEnv = buildRailwayCommandEnv({ ...process.env, ...env ?? {} });
|
|
494
456
|
const railway = resolveTreeseedToolCommand("railway", { env: effectiveEnv });
|
|
495
457
|
if (!railway) {
|
|
@@ -500,7 +462,8 @@ function runRailway(args, { cwd, capture = false, allowFailure = false, input, e
|
|
|
500
462
|
stdio: input !== void 0 ? ["pipe", capture ? "pipe" : "inherit", capture ? "pipe" : "inherit"] : capture ? "pipe" : "inherit",
|
|
501
463
|
encoding: "utf8",
|
|
502
464
|
env: spawnEnv,
|
|
503
|
-
input
|
|
465
|
+
input,
|
|
466
|
+
timeout: Number.isFinite(timeoutMs) && Number(timeoutMs) > 0 ? Number(timeoutMs) : void 0
|
|
504
467
|
});
|
|
505
468
|
let result = null;
|
|
506
469
|
const maxAttempts = retryTransient && !allowFailure ? Math.max(1, Number(retryAttempts) || 1) : 1;
|
|
@@ -511,6 +474,14 @@ function runRailway(args, { cwd, capture = false, allowFailure = false, input, e
|
|
|
511
474
|
}
|
|
512
475
|
sleepSync((Number(retryDelayMs) || 0) * attempt);
|
|
513
476
|
}
|
|
477
|
+
if (result?.error) {
|
|
478
|
+
const errorMessage = result.error instanceof Error ? result.error.message : String(result.error);
|
|
479
|
+
const timeoutMessage = /timed out|ETIMEDOUT/iu.test(errorMessage) ? `railway ${args.join(" ")} timed out after ${Math.round(Number(timeoutMs) / 1e3)}s` : errorMessage;
|
|
480
|
+
if (!allowFailure) {
|
|
481
|
+
throw new Error(timeoutMessage);
|
|
482
|
+
}
|
|
483
|
+
result.stderr = result.stderr || timeoutMessage;
|
|
484
|
+
}
|
|
514
485
|
if (result?.status !== 0 && !allowFailure) {
|
|
515
486
|
throw new Error(result.stderr?.trim() || result.stdout?.trim() || `railway ${args.join(" ")} failed`);
|
|
516
487
|
}
|
|
@@ -602,6 +573,26 @@ async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
|
|
|
602
573
|
}
|
|
603
574
|
};
|
|
604
575
|
}
|
|
576
|
+
if (checks.some((entry) => entry.terminalFailure === true)) {
|
|
577
|
+
return {
|
|
578
|
+
ok: false,
|
|
579
|
+
checks: checks.map((check) => ({
|
|
580
|
+
...check,
|
|
581
|
+
settle: {
|
|
582
|
+
durationMs: elapsedMs(startMs),
|
|
583
|
+
pollCount,
|
|
584
|
+
finalStatus: check.status,
|
|
585
|
+
terminalFailure: check.terminalFailure === true
|
|
586
|
+
}
|
|
587
|
+
})),
|
|
588
|
+
settle: {
|
|
589
|
+
durationMs: elapsedMs(startMs),
|
|
590
|
+
pollCount,
|
|
591
|
+
status: "failed"
|
|
592
|
+
},
|
|
593
|
+
message: "Railway deployment reached a terminal failed state."
|
|
594
|
+
};
|
|
595
|
+
}
|
|
605
596
|
if (Date.now() >= deadline) {
|
|
606
597
|
return {
|
|
607
598
|
ok: false,
|
|
@@ -923,8 +914,7 @@ function ensureRailwayProjectContext(service, { env = process.env, allowFailure
|
|
|
923
914
|
}
|
|
924
915
|
return null;
|
|
925
916
|
}
|
|
926
|
-
function
|
|
927
|
-
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
917
|
+
function configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, application = null) {
|
|
928
918
|
const normalizedScope = normalizeScope(scope);
|
|
929
919
|
let identity;
|
|
930
920
|
try {
|
|
@@ -944,7 +934,7 @@ function configuredRailwayServices(tenantRoot, scope) {
|
|
|
944
934
|
if (!service || service.enabled === false || (service.provider ?? "railway") !== "railway") {
|
|
945
935
|
return [];
|
|
946
936
|
}
|
|
947
|
-
const defaultRootDir = ["api", "
|
|
937
|
+
const defaultRootDir = ["api", "operationsRunner"].includes(serviceKey) ? "." : "packages/core";
|
|
948
938
|
const serviceRoot = resolve(tenantRoot, service.railway?.rootDir ?? service.rootDir ?? defaultRootDir);
|
|
949
939
|
const railwayEnvironment = resolveRailwayEnvironmentForScope(
|
|
950
940
|
normalizedScope,
|
|
@@ -953,28 +943,29 @@ function configuredRailwayServices(tenantRoot, scope) {
|
|
|
953
943
|
const publicBaseUrl = service.environments?.[normalizedScope]?.baseUrl ?? service.publicBaseUrl ?? null;
|
|
954
944
|
const configuredServiceName = service.railway?.serviceName ?? (serviceKey === "workerRunner" ? deriveRailwayWorkerRunnerServiceName(identity.deploymentKey) : `${identity.deploymentKey}-${railwayServiceNameSuffix(serviceKey)}`);
|
|
955
945
|
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 ??
|
|
946
|
+
const runnerPool = serviceKey === "operationsRunner" ? {
|
|
947
|
+
bootstrapCount: Math.max(1, Number.parseInt(String(configuredRunnerPool?.bootstrapCount ?? OPERATIONS_RUNNER_BOOTSTRAP_COUNT), 10) || OPERATIONS_RUNNER_BOOTSTRAP_COUNT),
|
|
948
|
+
maxRunners: Math.max(1, Number.parseInt(String(configuredRunnerPool?.maxRunners ?? configuredRunnerPool?.bootstrapCount ?? OPERATIONS_RUNNER_BOOTSTRAP_COUNT), 10) || OPERATIONS_RUNNER_BOOTSTRAP_COUNT),
|
|
959
949
|
volumeMountPath: service.railway?.volumeMountPath ?? configuredRunnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH
|
|
960
950
|
} : serviceKey === "workerRunner" ? {
|
|
961
951
|
bootstrapIndex: WORKER_RUNNER_BOOTSTRAP_INDEX,
|
|
962
952
|
volumeMountPath: WORKER_RUNNER_VOLUME_MOUNT_PATH
|
|
963
953
|
} : null;
|
|
964
|
-
const instanceCount = serviceKey === "
|
|
954
|
+
const instanceCount = serviceKey === "operationsRunner" ? runnerPool.bootstrapCount : 1;
|
|
965
955
|
return Array.from({ length: instanceCount }, (_, offset) => {
|
|
966
956
|
const runnerIndex = offset + 1;
|
|
967
|
-
const serviceName = serviceKey === "
|
|
957
|
+
const serviceName = serviceKey === "operationsRunner" ? deriveRailwayOperationsRunnerServiceName(configuredServiceName, runnerIndex) : configuredServiceName;
|
|
968
958
|
return {
|
|
969
959
|
key: serviceKey,
|
|
970
|
-
instanceKey: serviceKey === "
|
|
971
|
-
runnerIndex: serviceKey === "
|
|
960
|
+
instanceKey: serviceKey === "operationsRunner" ? `${serviceKey}:${runnerIndex}` : serviceKey,
|
|
961
|
+
runnerIndex: serviceKey === "operationsRunner" ? runnerIndex : null,
|
|
962
|
+
serviceConfig: service,
|
|
972
963
|
scope: normalizedScope,
|
|
973
964
|
projectId: service.railway?.projectId ?? null,
|
|
974
965
|
projectName: service.railway?.projectName ?? identity.deploymentKey,
|
|
975
966
|
serviceId: service.railway?.serviceId ?? null,
|
|
976
967
|
serviceName,
|
|
977
|
-
runnerId: serviceKey === "
|
|
968
|
+
runnerId: serviceKey === "operationsRunner" ? serviceName : null,
|
|
978
969
|
rootDir: serviceRoot,
|
|
979
970
|
publicBaseUrl,
|
|
980
971
|
railwayEnvironment,
|
|
@@ -985,14 +976,30 @@ function configuredRailwayServices(tenantRoot, scope) {
|
|
|
985
976
|
healthcheckIntervalSeconds: service.railway?.healthcheckIntervalSeconds ?? null,
|
|
986
977
|
restartPolicy: service.railway?.restartPolicy ?? null,
|
|
987
978
|
runtimeMode: service.railway?.runtimeMode ?? null,
|
|
988
|
-
volumeMountPath: serviceKey === "
|
|
979
|
+
volumeMountPath: serviceKey === "operationsRunner" ? runnerPool.volumeMountPath : service.railway?.volumeMountPath ?? null,
|
|
989
980
|
schedule: normalizeScheduleExpressions(service.railway?.schedule),
|
|
990
981
|
hostingKind,
|
|
991
|
-
runnerPool
|
|
982
|
+
runnerPool,
|
|
983
|
+
application
|
|
992
984
|
};
|
|
993
985
|
});
|
|
994
986
|
}).filter(Boolean);
|
|
995
987
|
}
|
|
988
|
+
function configuredRailwayServices(tenantRoot, scope) {
|
|
989
|
+
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
990
|
+
const direct = configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig);
|
|
991
|
+
const nested = discoverTreeseedApplications(tenantRoot).filter((application) => application.root !== resolve(tenantRoot)).flatMap((application) => configuredRailwayServicesForConfig(
|
|
992
|
+
application.root,
|
|
993
|
+
scope,
|
|
994
|
+
application.config,
|
|
995
|
+
{
|
|
996
|
+
id: application.id,
|
|
997
|
+
root: application.root,
|
|
998
|
+
relativeRoot: application.relativeRoot
|
|
999
|
+
}
|
|
1000
|
+
));
|
|
1001
|
+
return [...direct, ...nested];
|
|
1002
|
+
}
|
|
996
1003
|
function configuredRailwayScheduledJobs(tenantRoot, scope, { phase = "deploy" } = {}) {
|
|
997
1004
|
if (!shouldManageRailwaySchedules(scope, phase)) {
|
|
998
1005
|
return [];
|
|
@@ -1427,6 +1434,39 @@ function shouldUseVerboseRailwayDeploy(env = process.env) {
|
|
|
1427
1434
|
}
|
|
1428
1435
|
return shouldAttachRailwayDeployLogs(env);
|
|
1429
1436
|
}
|
|
1437
|
+
function railwayDeployCommandTimeoutMs(env = process.env) {
|
|
1438
|
+
const configured = Number.parseInt(configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_COMMAND_TIMEOUT_MS"), 10);
|
|
1439
|
+
return Number.isFinite(configured) && configured > 0 ? configured : 3e5;
|
|
1440
|
+
}
|
|
1441
|
+
function railwayPhaseTimeoutMs(env = process.env, phase = "default") {
|
|
1442
|
+
const configured = Number.parseInt(configuredEnvValue(env, `TREESEED_RAILWAY_${String(phase).toUpperCase().replace(/[^A-Z0-9]+/gu, "_")}_TIMEOUT_MS`), 10);
|
|
1443
|
+
if (Number.isFinite(configured) && configured > 0) {
|
|
1444
|
+
return configured;
|
|
1445
|
+
}
|
|
1446
|
+
const defaultConfigured = Number.parseInt(configuredEnvValue(env, "TREESEED_RAILWAY_PHASE_TIMEOUT_MS"), 10);
|
|
1447
|
+
if (Number.isFinite(defaultConfigured) && defaultConfigured > 0) {
|
|
1448
|
+
return defaultConfigured;
|
|
1449
|
+
}
|
|
1450
|
+
if (phase === "sync_runtime_config") {
|
|
1451
|
+
return 6e5;
|
|
1452
|
+
}
|
|
1453
|
+
return phase === "deploy" ? 3e5 : 18e4;
|
|
1454
|
+
}
|
|
1455
|
+
async function withRailwayPhaseTimeout(run, timeoutMs, message) {
|
|
1456
|
+
let timer = null;
|
|
1457
|
+
try {
|
|
1458
|
+
return await Promise.race([
|
|
1459
|
+
Promise.resolve().then(run),
|
|
1460
|
+
new Promise((_, reject) => {
|
|
1461
|
+
timer = setTimeout(() => reject(new Error(message)), timeoutMs);
|
|
1462
|
+
})
|
|
1463
|
+
]);
|
|
1464
|
+
} finally {
|
|
1465
|
+
if (timer) {
|
|
1466
|
+
clearTimeout(timer);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1430
1470
|
function shouldIncludeRailwayIgnoredFiles(env = process.env) {
|
|
1431
1471
|
const configured = configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_INCLUDE_IGNORED");
|
|
1432
1472
|
return configured === "1" || configured === "true";
|
|
@@ -1699,15 +1739,23 @@ async function resolveRailwayDeployProjectContext(service, { env = process.env }
|
|
|
1699
1739
|
projectName: project.name ?? service.projectName
|
|
1700
1740
|
};
|
|
1701
1741
|
}
|
|
1702
|
-
async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env } = {}) {
|
|
1742
|
+
async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env, writePhase = null } = {}) {
|
|
1743
|
+
const writeSyncPhase = (stage, message) => {
|
|
1744
|
+
if (typeof writePhase === "function") {
|
|
1745
|
+
writePhase(`sync-runtime-config:${stage}`, message);
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1703
1748
|
const wantsInstanceConfig = service.buildCommand || service.startCommand || service.rootDir || service.healthcheckPath || service.healthcheckTimeoutSeconds !== null || service.healthcheckTimeoutSeconds !== void 0 || service.healthcheckIntervalSeconds !== null || service.healthcheckIntervalSeconds !== void 0 || service.restartPolicy || service.runtimeMode;
|
|
1704
1749
|
const wantsRunnerVolume = service.key === "workerRunner" || Boolean(service.volumeMountPath);
|
|
1705
1750
|
if (!wantsInstanceConfig && !wantsRunnerVolume) {
|
|
1751
|
+
writeSyncPhase("skip", "No runtime configuration changes requested.");
|
|
1706
1752
|
return null;
|
|
1707
1753
|
}
|
|
1754
|
+
writeSyncPhase("workspace", "Resolving Railway workspace.");
|
|
1708
1755
|
const workspace = await resolveRailwayWorkspaceContext({ env });
|
|
1709
1756
|
let project = null;
|
|
1710
1757
|
if (service.projectId) {
|
|
1758
|
+
writeSyncPhase("project", `Resolving Railway project ${service.projectName ?? service.projectId}.`);
|
|
1711
1759
|
project = await ensureRailwayProject({
|
|
1712
1760
|
projectId: service.projectId,
|
|
1713
1761
|
projectName: service.projectName,
|
|
@@ -1716,9 +1764,11 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1716
1764
|
workspace: workspace.id
|
|
1717
1765
|
}).then((result) => result.project);
|
|
1718
1766
|
} else {
|
|
1767
|
+
writeSyncPhase("project", `Looking up Railway project ${service.projectName}.`);
|
|
1719
1768
|
const projects = await listRailwayProjects({ env, workspaceId: workspace.id });
|
|
1720
1769
|
project = projects.find((entry) => entry.name === service.projectName) ?? null;
|
|
1721
1770
|
if (!project) {
|
|
1771
|
+
writeSyncPhase("project", `Creating Railway project ${service.projectName}.`);
|
|
1722
1772
|
project = await ensureRailwayProject({
|
|
1723
1773
|
projectName: service.projectName,
|
|
1724
1774
|
defaultEnvironmentName: service.railwayEnvironment,
|
|
@@ -1730,6 +1780,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1730
1780
|
const environmentName = normalizeRailwayEnvironmentName(service.railwayEnvironment);
|
|
1731
1781
|
let environment = project.environments.find((entry) => entry.name === environmentName || entry.id === environmentName) ?? null;
|
|
1732
1782
|
if (!environment) {
|
|
1783
|
+
writeSyncPhase("environment", `Creating Railway environment ${environmentName}.`);
|
|
1733
1784
|
environment = await ensureRailwayEnvironment({
|
|
1734
1785
|
projectId: project.id,
|
|
1735
1786
|
environmentName,
|
|
@@ -1738,6 +1789,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1738
1789
|
}
|
|
1739
1790
|
let railwayService = project.services.find((entry) => entry.id === service.serviceId || entry.name === service.serviceName) ?? null;
|
|
1740
1791
|
if (!railwayService) {
|
|
1792
|
+
writeSyncPhase("service", `Creating Railway service ${service.serviceName ?? service.key}.`);
|
|
1741
1793
|
railwayService = await ensureRailwayService({
|
|
1742
1794
|
projectId: project.id,
|
|
1743
1795
|
serviceId: service.serviceId,
|
|
@@ -1745,56 +1797,63 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1745
1797
|
env
|
|
1746
1798
|
}).then((result) => result.service);
|
|
1747
1799
|
}
|
|
1800
|
+
if (wantsInstanceConfig) {
|
|
1801
|
+
writeSyncPhase("instance", "Ensuring Railway service instance configuration.");
|
|
1802
|
+
}
|
|
1748
1803
|
const runtimeConfiguration = wantsInstanceConfig ? await ensureRailwayServiceInstanceConfiguration({
|
|
1749
1804
|
serviceId: railwayService.id,
|
|
1750
1805
|
environmentId: environment.id,
|
|
1751
1806
|
buildCommand: service.buildCommand,
|
|
1752
1807
|
startCommand: railwayServiceRuntimeStartCommand(service),
|
|
1753
1808
|
cronSchedule: service.schedule?.[0] ?? null,
|
|
1754
|
-
rootDirectory:
|
|
1809
|
+
rootDirectory: ".",
|
|
1755
1810
|
healthcheckPath: service.healthcheckPath,
|
|
1756
1811
|
healthcheckTimeoutSeconds: service.healthcheckTimeoutSeconds,
|
|
1757
1812
|
healthcheckIntervalSeconds: service.healthcheckIntervalSeconds,
|
|
1758
1813
|
restartPolicy: service.restartPolicy,
|
|
1759
1814
|
runtimeMode: service.runtimeMode,
|
|
1815
|
+
deploymentRegion: wantsRunnerVolume ? configuredEnvValue(env, "TREESEED_RAILWAY_STATEFUL_REGION") || "us-west2" : null,
|
|
1760
1816
|
env
|
|
1761
1817
|
}) : null;
|
|
1818
|
+
writeSyncPhase("variables", "Upserting Railway runtime variables.");
|
|
1762
1819
|
await upsertRailwayVariables({
|
|
1763
1820
|
projectId: project.id,
|
|
1764
1821
|
environmentId: environment.id,
|
|
1765
1822
|
serviceId: railwayService.id,
|
|
1766
1823
|
variables: {
|
|
1767
1824
|
TREESEED_SKIP_PACKAGE_PREPARE: "1",
|
|
1768
|
-
...service.key === "
|
|
1825
|
+
...service.key === "operationsRunner" ? {
|
|
1769
1826
|
NIXPACKS_APT_PKGS: "git",
|
|
1770
1827
|
NIXPACKS_PKGS: "git",
|
|
1771
1828
|
TREESEED_PLATFORM_RUNNER_ID: service.runnerId ?? railwayService.name,
|
|
1772
1829
|
TREESEED_PLATFORM_RUNNER_DATA_DIR: service.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH,
|
|
1773
1830
|
TREESEED_PLATFORM_RUNNER_ENVIRONMENT: normalizeScope(service.scope) === "prod" ? "production" : normalizeScope(service.scope),
|
|
1774
|
-
|
|
1831
|
+
TREESEED_MANAGER_ID: normalizeScope(service.scope),
|
|
1832
|
+
...configuredEnvValue(env, "RAILWAY_API_TOKEN") ? { RAILWAY_API_TOKEN: configuredEnvValue(env, "RAILWAY_API_TOKEN") } : {},
|
|
1833
|
+
...configuredEnvValue(env, "TREESEED_RAILWAY_WORKSPACE") ? { TREESEED_RAILWAY_WORKSPACE: configuredEnvValue(env, "TREESEED_RAILWAY_WORKSPACE") } : {},
|
|
1775
1834
|
...configuredEnvValue(env, "TREESEED_PLATFORM_RUNNER_SECRET") ? { TREESEED_PLATFORM_RUNNER_SECRET: configuredEnvValue(env, "TREESEED_PLATFORM_RUNNER_SECRET") } : {},
|
|
1776
|
-
...configuredEnvValue(env, "
|
|
1777
|
-
|
|
1835
|
+
...configuredEnvValue(env, "TREESEED_API_BASE_URL") || configuredEnvValue(env, "TREESEED_URL") ? {
|
|
1836
|
+
TREESEED_API_BASE_URL: configuredEnvValue(env, "TREESEED_API_BASE_URL") || configuredEnvValue(env, "TREESEED_URL")
|
|
1778
1837
|
} : {}
|
|
1779
1838
|
} : {}
|
|
1780
1839
|
},
|
|
1781
1840
|
env
|
|
1782
1841
|
});
|
|
1783
1842
|
const volumeMountPath = service.volumeMountPath ?? service.runnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH;
|
|
1784
|
-
|
|
1785
|
-
|
|
1843
|
+
if (wantsRunnerVolume) {
|
|
1844
|
+
writeSyncPhase("volume", `Ensuring Railway volume mounted at ${volumeMountPath}.`);
|
|
1845
|
+
}
|
|
1846
|
+
const volumeConfiguration = wantsRunnerVolume ? await ensureRailwayServiceVolume({
|
|
1786
1847
|
projectId: project.id,
|
|
1787
1848
|
environmentId: environment.id,
|
|
1788
|
-
environmentName: environment.name,
|
|
1789
1849
|
serviceId: railwayService.id,
|
|
1790
|
-
|
|
1791
|
-
name: service.key === "marketOperationsRunner" ? deriveRailwayMarketOperationsRunnerVolumeName(railwayService.name, environment.name) : deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
|
|
1850
|
+
name: service.key === "operationsRunner" ? deriveRailwayOperationsRunnerVolumeName(railwayService.name, environment.name) : deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
|
|
1792
1851
|
mountPath: volumeMountPath,
|
|
1793
|
-
preferCli: service.key === "marketOperationsRunner",
|
|
1794
1852
|
env
|
|
1795
1853
|
}) : null;
|
|
1796
1854
|
if (wantsRunnerVolume) {
|
|
1797
1855
|
if (service.key === "workerRunner") {
|
|
1856
|
+
writeSyncPhase("volume-vars", "Upserting Railway worker volume variables.");
|
|
1798
1857
|
await upsertRailwayVariables({
|
|
1799
1858
|
projectId: project.id,
|
|
1800
1859
|
environmentId: environment.id,
|
|
@@ -1810,6 +1869,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1810
1869
|
});
|
|
1811
1870
|
}
|
|
1812
1871
|
}
|
|
1872
|
+
writeSyncPhase("done", "Runtime configuration is synchronized.");
|
|
1813
1873
|
return {
|
|
1814
1874
|
projectId: project.id,
|
|
1815
1875
|
projectName: project.name ?? service.projectName ?? null,
|
|
@@ -1828,163 +1888,6 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
|
|
|
1828
1888
|
} : null
|
|
1829
1889
|
};
|
|
1830
1890
|
}
|
|
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
1891
|
async function deployRailwayService(tenantRoot, service, {
|
|
1989
1892
|
dryRun = false,
|
|
1990
1893
|
write,
|
|
@@ -2000,7 +1903,13 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2000
1903
|
command: [plan2.command, ...plan2.args].join(" "),
|
|
2001
1904
|
cwd: plan2.cwd,
|
|
2002
1905
|
publicBaseUrl: service.publicBaseUrl,
|
|
2003
|
-
timings
|
|
1906
|
+
timings,
|
|
1907
|
+
transport: {
|
|
1908
|
+
railway: {
|
|
1909
|
+
reconcile: "api",
|
|
1910
|
+
deploy: railwayDeployTransport(env)
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
2004
1913
|
};
|
|
2005
1914
|
}
|
|
2006
1915
|
const deployService = await timedRailwayPhase(timings, "railway:resolve-context", () => resolveRailwayDeployProjectContext(service, { env }), {
|
|
@@ -2008,19 +1917,23 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2008
1917
|
});
|
|
2009
1918
|
const commandEnv = buildRailwayCommandEnv({ ...process.env, ...env });
|
|
2010
1919
|
let railwayDeployEnv = buildRailwayDeployCommandEnv(commandEnv);
|
|
2011
|
-
const
|
|
2012
|
-
if (!railway) {
|
|
2013
|
-
throw new Error("Railway CLI is unavailable.");
|
|
2014
|
-
}
|
|
1920
|
+
const deployTransport = railwayDeployTransport(commandEnv);
|
|
2015
1921
|
const taskPrefix = prefix ?? {
|
|
2016
1922
|
scope: normalizeScope(deployService.scope ?? deployService.railwayEnvironment ?? "railway"),
|
|
2017
1923
|
system: deployService.key === "api" ? "api" : "agents",
|
|
2018
1924
|
task: `${deployService.key}-railway-deploy`,
|
|
2019
1925
|
stage: "deploy"
|
|
2020
1926
|
};
|
|
2021
|
-
const
|
|
2022
|
-
|
|
2023
|
-
}
|
|
1927
|
+
const writePhase = (stage, message) => {
|
|
1928
|
+
write ? write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][${stage}] ${message}`, "stdout") : null;
|
|
1929
|
+
};
|
|
1930
|
+
writePhase("resolve-context", `Resolved Railway service ${deployService.serviceName ?? deployService.serviceId ?? deployService.key}.`);
|
|
1931
|
+
writePhase("sync-runtime-config", "Syncing Railway runtime configuration.");
|
|
1932
|
+
const runtimeConfiguration = await timedRailwayPhase(timings, "railway:sync-runtime-config", () => withRailwayPhaseTimeout(
|
|
1933
|
+
() => syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, { env: commandEnv, writePhase }),
|
|
1934
|
+
railwayPhaseTimeoutMs(commandEnv, "sync_runtime_config"),
|
|
1935
|
+
`Railway runtime configuration sync timed out for ${deployService.serviceName ?? deployService.key}.`
|
|
1936
|
+
), { service: deployService.key });
|
|
2024
1937
|
const cliDeployService = {
|
|
2025
1938
|
...deployService,
|
|
2026
1939
|
projectId: runtimeConfiguration?.projectId ?? deployService.projectId,
|
|
@@ -2030,16 +1943,71 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2030
1943
|
serviceName: runtimeConfiguration?.serviceName ?? deployService.serviceName,
|
|
2031
1944
|
railwayEnvironment: runtimeConfiguration?.environmentName ?? runtimeConfiguration?.environmentId ?? deployService.railwayEnvironment
|
|
2032
1945
|
};
|
|
2033
|
-
|
|
1946
|
+
writePhase("device-login-vars", "Syncing Railway device-login variables.");
|
|
1947
|
+
await timedRailwayPhase(timings, "railway:device-login-vars", () => withRailwayPhaseTimeout(
|
|
1948
|
+
() => syncRailwayApiDeviceLoginVariables(cliDeployService, commandEnv, write, taskPrefix),
|
|
1949
|
+
railwayPhaseTimeoutMs(commandEnv, "device_login_vars"),
|
|
1950
|
+
`Railway device-login variable sync timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
|
|
1951
|
+
), {
|
|
2034
1952
|
service: cliDeployService.key
|
|
2035
1953
|
});
|
|
1954
|
+
if (deployService.buildCommand && shouldRunRailwayPredeployBuild(commandEnv)) {
|
|
1955
|
+
const buildResult = await timedRailwayPhase(timings, "railway:predeploy-build", () => runPrefixedCommand("bash", ["-lc", deployService.buildCommand], {
|
|
1956
|
+
cwd: deployService.rootDir,
|
|
1957
|
+
env: commandEnv,
|
|
1958
|
+
write,
|
|
1959
|
+
prefix: { ...taskPrefix, stage: "build" }
|
|
1960
|
+
}), { service: deployService.key });
|
|
1961
|
+
if (buildResult.status !== 0) {
|
|
1962
|
+
throw new Error(`Railway ${deployService.key} build command failed.`);
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
if (deployTransport !== "cli-fallback") {
|
|
1966
|
+
writePhase("deploy", `Deploying Railway service ${cliDeployService.serviceName ?? cliDeployService.serviceId ?? cliDeployService.key} through the Railway API.`);
|
|
1967
|
+
const apiDeploy = await timedRailwayPhase(timings, "railway:api-deploy", () => withRailwayPhaseTimeout(
|
|
1968
|
+
() => deployRailwayServiceInstance({
|
|
1969
|
+
serviceId: cliDeployService.serviceId,
|
|
1970
|
+
environmentId: cliDeployService.environmentId,
|
|
1971
|
+
env: commandEnv
|
|
1972
|
+
}),
|
|
1973
|
+
railwayPhaseTimeoutMs(commandEnv, "deploy"),
|
|
1974
|
+
`Railway API deploy phase timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
|
|
1975
|
+
), { service: cliDeployService.key });
|
|
1976
|
+
return {
|
|
1977
|
+
service: deployService.key,
|
|
1978
|
+
status: "deployed",
|
|
1979
|
+
command: "railway-api serviceInstanceDeployV2",
|
|
1980
|
+
cwd: deployService.rootDir,
|
|
1981
|
+
publicBaseUrl: deployService.publicBaseUrl,
|
|
1982
|
+
timings,
|
|
1983
|
+
deploymentId: apiDeploy.deploymentId,
|
|
1984
|
+
transport: {
|
|
1985
|
+
railway: {
|
|
1986
|
+
reconcile: "api",
|
|
1987
|
+
deploy: "api"
|
|
1988
|
+
}
|
|
1989
|
+
},
|
|
1990
|
+
runtimeConfiguration: runtimeConfiguration ? {
|
|
1991
|
+
updated: runtimeConfiguration.updated,
|
|
1992
|
+
healthcheckPath: runtimeConfiguration.instance?.healthcheckPath ?? null,
|
|
1993
|
+
healthcheckTimeoutSeconds: runtimeConfiguration.instance?.healthcheckTimeoutSeconds ?? null,
|
|
1994
|
+
runtimeMode: runtimeConfiguration.instance?.runtimeMode ?? null,
|
|
1995
|
+
volume: runtimeConfiguration.volume ?? null
|
|
1996
|
+
} : null
|
|
1997
|
+
};
|
|
1998
|
+
}
|
|
1999
|
+
const railway = resolveTreeseedToolCommand("railway", { env: commandEnv });
|
|
2000
|
+
if (!railway) {
|
|
2001
|
+
throw new Error("Railway CLI deploy fallback requested, but Railway CLI is unavailable.");
|
|
2002
|
+
}
|
|
2036
2003
|
railwayDeployEnv = buildRailwayCliContextEnv(railwayDeployEnv, cliDeployService);
|
|
2037
2004
|
const hasCommandApiToken = Boolean(configuredEnvValue(commandEnv, "RAILWAY_API_TOKEN"));
|
|
2038
2005
|
let usesProjectToken = Boolean(configuredEnvValue(railwayDeployEnv, "RAILWAY_TOKEN"));
|
|
2039
2006
|
if (usesProjectToken) {
|
|
2040
2007
|
railwayDeployEnv = { ...railwayDeployEnv, RAILWAY_API_TOKEN: void 0 };
|
|
2041
2008
|
}
|
|
2042
|
-
|
|
2009
|
+
writePhase("project-token", usesProjectToken || hasCommandApiToken ? "Using configured Railway authentication." : "Creating Railway project token.");
|
|
2010
|
+
await timedRailwayPhase(timings, "railway:project-token", () => withRailwayPhaseTimeout(async () => {
|
|
2043
2011
|
if (usesProjectToken || hasCommandApiToken) {
|
|
2044
2012
|
return null;
|
|
2045
2013
|
}
|
|
@@ -2051,25 +2019,15 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2051
2019
|
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
2020
|
}
|
|
2053
2021
|
return null;
|
|
2054
|
-
}, { service: cliDeployService.key });
|
|
2022
|
+
}, railwayPhaseTimeoutMs(commandEnv, "project_token"), `Railway project-token phase timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`), { service: cliDeployService.key });
|
|
2055
2023
|
const linkPlan = planRailwayServiceLink(cliDeployService, { env: commandEnv });
|
|
2056
2024
|
const plan = planRailwayServiceDeploy(cliDeployService, { env, projectTokenMode: usesProjectToken });
|
|
2057
|
-
if (deployService.buildCommand && shouldRunRailwayPredeployBuild(commandEnv)) {
|
|
2058
|
-
const buildResult = await timedRailwayPhase(timings, "railway:predeploy-build", () => runPrefixedCommand("bash", ["-lc", deployService.buildCommand], {
|
|
2059
|
-
cwd: deployService.rootDir,
|
|
2060
|
-
env: commandEnv,
|
|
2061
|
-
write,
|
|
2062
|
-
prefix: { ...taskPrefix, stage: "build" }
|
|
2063
|
-
}), { service: deployService.key });
|
|
2064
|
-
if (buildResult.status !== 0) {
|
|
2065
|
-
throw new Error(`Railway ${deployService.key} build command failed.`);
|
|
2066
|
-
}
|
|
2067
|
-
}
|
|
2068
2025
|
const hasRailwayApiToken = Boolean(configuredEnvValue(commandEnv, "RAILWAY_API_TOKEN"));
|
|
2069
2026
|
const cliConfig = configuredEnvValue(commandEnv, "CI") === "true" ? writeRailwayCliProjectConfig(cliDeployService, { env: railwayDeployEnv, cwd: plan.cwd }) : null;
|
|
2070
2027
|
const effectiveLinkPlan = hasRailwayApiToken ? linkPlan : usesProjectToken ? planRailwayProjectEnvironmentLink(cliDeployService) : linkPlan;
|
|
2071
2028
|
const railwayLinkEnv = hasRailwayApiToken ? buildRailwayLinkCommandEnv(commandEnv, cliDeployService) : railwayDeployEnv;
|
|
2072
|
-
|
|
2029
|
+
writePhase("link", `Linking Railway project context for ${cliDeployService.serviceName ?? cliDeployService.serviceId ?? cliDeployService.key}.`);
|
|
2030
|
+
await timedRailwayPhase(timings, "railway:link", () => withRailwayPhaseTimeout(async () => {
|
|
2073
2031
|
if (cliConfig) {
|
|
2074
2032
|
write ? write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][link] Wrote Railway CLI project context for ${cliConfig.projectPath}.`, "stdout") : null;
|
|
2075
2033
|
return;
|
|
@@ -2083,15 +2041,19 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2083
2041
|
if (linkResult.status !== 0) {
|
|
2084
2042
|
throw new Error(linkResult.stderr?.trim() || linkResult.stdout?.trim() || `railway ${effectiveLinkPlan.args.join(" ")} failed with exit code ${linkResult.status ?? "unknown"} in ${effectiveLinkPlan.cwd}`);
|
|
2085
2043
|
}
|
|
2086
|
-
}, { service: cliDeployService.key });
|
|
2087
|
-
await timedRailwayPhase(timings, "railway:deploy", async () => {
|
|
2044
|
+
}, railwayPhaseTimeoutMs(commandEnv, "link"), `Railway link phase timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`), { service: cliDeployService.key });
|
|
2045
|
+
await timedRailwayPhase(timings, "railway:deploy", () => withRailwayPhaseTimeout(async () => {
|
|
2088
2046
|
let lastFailure = null;
|
|
2089
2047
|
for (let attempt = 1; attempt <= 5; attempt += 1) {
|
|
2048
|
+
if (write && attempt === 1) {
|
|
2049
|
+
write(`[${taskPrefix.scope}][${taskPrefix.system}][${taskPrefix.task}][deploy] $ railway ${plan.args.join(" ")}`, "stdout");
|
|
2050
|
+
}
|
|
2090
2051
|
const result = await runPrefixedCommand(railway.command, [...railway.argsPrefix, ...plan.args], {
|
|
2091
2052
|
cwd: plan.cwd,
|
|
2092
2053
|
env: railwayDeployEnv,
|
|
2093
2054
|
write,
|
|
2094
|
-
prefix: taskPrefix
|
|
2055
|
+
prefix: taskPrefix,
|
|
2056
|
+
timeoutMs: railwayDeployCommandTimeoutMs(commandEnv)
|
|
2095
2057
|
});
|
|
2096
2058
|
if (result.status === 0) {
|
|
2097
2059
|
lastFailure = null;
|
|
@@ -2109,7 +2071,7 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2109
2071
|
if (lastFailure) {
|
|
2110
2072
|
throw new Error(lastFailure.stderr?.trim() || lastFailure.stdout?.trim() || `railway ${plan.args.join(" ")} failed`);
|
|
2111
2073
|
}
|
|
2112
|
-
}, { service: cliDeployService.key });
|
|
2074
|
+
}, railwayPhaseTimeoutMs(commandEnv, "deploy"), `Railway deploy phase timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`), { service: cliDeployService.key });
|
|
2113
2075
|
return {
|
|
2114
2076
|
service: deployService.key,
|
|
2115
2077
|
status: "deployed",
|
|
@@ -2117,6 +2079,12 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
2117
2079
|
cwd: plan.cwd,
|
|
2118
2080
|
publicBaseUrl: deployService.publicBaseUrl,
|
|
2119
2081
|
timings,
|
|
2082
|
+
transport: {
|
|
2083
|
+
railway: {
|
|
2084
|
+
reconcile: "api",
|
|
2085
|
+
deploy: "cli-fallback"
|
|
2086
|
+
}
|
|
2087
|
+
},
|
|
2120
2088
|
runtimeConfiguration: runtimeConfiguration ? {
|
|
2121
2089
|
updated: runtimeConfiguration.updated,
|
|
2122
2090
|
healthcheckPath: runtimeConfiguration.instance?.healthcheckPath ?? null,
|
|
@@ -2134,8 +2102,8 @@ export {
|
|
|
2134
2102
|
configuredRailwayScheduledJobs,
|
|
2135
2103
|
configuredRailwayServices,
|
|
2136
2104
|
deployRailwayService,
|
|
2137
|
-
|
|
2138
|
-
|
|
2105
|
+
deriveRailwayOperationsRunnerServiceName,
|
|
2106
|
+
deriveRailwayOperationsRunnerVolumeName,
|
|
2139
2107
|
deriveRailwayWorkerRunnerServiceName,
|
|
2140
2108
|
deriveRailwayWorkerRunnerVolumeName,
|
|
2141
2109
|
ensureRailwayDatabaseServiceExists,
|
|
@@ -2144,10 +2112,10 @@ export {
|
|
|
2144
2112
|
ensureRailwayProjectExists,
|
|
2145
2113
|
ensureRailwayScheduledJobs,
|
|
2146
2114
|
ensureRailwayServiceExists,
|
|
2147
|
-
|
|
2115
|
+
findStaleTreeseedOperationsRunnerResources,
|
|
2148
2116
|
isRailwayTransientFailure,
|
|
2117
|
+
isTreeseedOperationsRunnerResourceName,
|
|
2149
2118
|
isUsableRailwayToken,
|
|
2150
|
-
listRailwayServiceVolumesWithCli,
|
|
2151
2119
|
parseRailwayJsonOutput,
|
|
2152
2120
|
planRailwayServiceDeploy,
|
|
2153
2121
|
planRailwayServiceLink,
|