@treeseed/sdk 0.6.43 → 0.6.45
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/dist/operations/services/project-platform.d.ts +32 -0
- package/dist/operations/services/project-platform.js +7 -2
- package/dist/operations/services/railway-deploy.d.ts +48 -0
- package/dist/operations/services/railway-deploy.js +242 -1
- package/dist/workflow/operations.js +25 -7
- package/dist/workflow.d.ts +2 -0
- package/package.json +1 -1
|
@@ -137,6 +137,14 @@ export declare function deployProjectPlatform(options: ProjectPlatformActionOpti
|
|
|
137
137
|
skipped: boolean;
|
|
138
138
|
reason: string;
|
|
139
139
|
};
|
|
140
|
+
railwayResources: {
|
|
141
|
+
ok: boolean;
|
|
142
|
+
checks: any[];
|
|
143
|
+
} | {
|
|
144
|
+
ok: boolean;
|
|
145
|
+
skipped: boolean;
|
|
146
|
+
reason: string;
|
|
147
|
+
};
|
|
140
148
|
pages: {
|
|
141
149
|
ok: boolean;
|
|
142
150
|
status: number;
|
|
@@ -315,6 +323,14 @@ export declare function monitorProjectPlatform(options: ProjectPlatformActionOpt
|
|
|
315
323
|
skipped: boolean;
|
|
316
324
|
reason: string;
|
|
317
325
|
};
|
|
326
|
+
railwayResources: {
|
|
327
|
+
ok: boolean;
|
|
328
|
+
checks: any[];
|
|
329
|
+
} | {
|
|
330
|
+
ok: boolean;
|
|
331
|
+
skipped: boolean;
|
|
332
|
+
reason: string;
|
|
333
|
+
};
|
|
318
334
|
pages: {
|
|
319
335
|
ok: boolean;
|
|
320
336
|
status: number;
|
|
@@ -498,6 +514,14 @@ export declare function runProjectPlatformAction(action: ProjectPlatformAction,
|
|
|
498
514
|
skipped: boolean;
|
|
499
515
|
reason: string;
|
|
500
516
|
};
|
|
517
|
+
railwayResources: {
|
|
518
|
+
ok: boolean;
|
|
519
|
+
checks: any[];
|
|
520
|
+
} | {
|
|
521
|
+
ok: boolean;
|
|
522
|
+
skipped: boolean;
|
|
523
|
+
reason: string;
|
|
524
|
+
};
|
|
501
525
|
pages: {
|
|
502
526
|
ok: boolean;
|
|
503
527
|
status: number;
|
|
@@ -637,6 +661,14 @@ export declare function runProjectPlatformAction(action: ProjectPlatformAction,
|
|
|
637
661
|
skipped: boolean;
|
|
638
662
|
reason: string;
|
|
639
663
|
};
|
|
664
|
+
railwayResources: {
|
|
665
|
+
ok: boolean;
|
|
666
|
+
checks: any[];
|
|
667
|
+
} | {
|
|
668
|
+
ok: boolean;
|
|
669
|
+
skipped: boolean;
|
|
670
|
+
reason: string;
|
|
671
|
+
};
|
|
640
672
|
pages: {
|
|
641
673
|
ok: boolean;
|
|
642
674
|
status: number;
|
|
@@ -35,6 +35,7 @@ import {
|
|
|
35
35
|
ensureRailwayScheduledJobs,
|
|
36
36
|
validateRailwayDeployPrerequisites,
|
|
37
37
|
validateRailwayServiceConfiguration,
|
|
38
|
+
verifyRailwayManagedResources,
|
|
38
39
|
verifyRailwayScheduledJobs
|
|
39
40
|
} from "./railway-deploy.js";
|
|
40
41
|
import { loadCliDeployConfig, packageScriptPath } from "./runtime-tools.js";
|
|
@@ -1209,6 +1210,7 @@ async function publishProjectContent(options) {
|
|
|
1209
1210
|
}
|
|
1210
1211
|
async function monitorProjectPlatform(options) {
|
|
1211
1212
|
const reporter = resolveReporter(options.tenantRoot, options.reporter);
|
|
1213
|
+
const env = { ...process.env, ...options.env ?? {} };
|
|
1212
1214
|
const target = createPersistentDeployTarget(options.scope === "local" ? "staging" : options.scope);
|
|
1213
1215
|
const siteConfig = loadCliDeployConfig(options.tenantRoot);
|
|
1214
1216
|
const selectedSystems = new Set(resolveProjectPlatformBootstrapSystems(options, siteConfig));
|
|
@@ -1228,12 +1230,14 @@ async function monitorProjectPlatform(options) {
|
|
|
1228
1230
|
r2: options.dryRun ? { ok: true, skipped: true, reason: "dry_run" } : probeR2(options.tenantRoot, siteConfig, state, target),
|
|
1229
1231
|
queue: options.dryRun ? Promise.resolve({ ok: true, skipped: true, reason: "dry_run" }) : probeQueue(siteConfig, state),
|
|
1230
1232
|
scaleProbe: probeScaleConfiguration(siteConfig, state),
|
|
1233
|
+
railwayResources: options.scope === "local" || !apiSelected && !agentsSelected ? Promise.resolve({ ok: true, skipped: true, reason: options.scope === "local" ? "local_scope" : "railway_not_selected" }) : verifyRailwayManagedResources(options.tenantRoot, options.scope, { env, settleDeployments: true }),
|
|
1231
1234
|
readiness: state.readiness
|
|
1232
1235
|
};
|
|
1233
1236
|
const resolvedChecks = {
|
|
1234
1237
|
...checks,
|
|
1235
1238
|
r2: await checks.r2,
|
|
1236
|
-
queue: await checks.queue
|
|
1239
|
+
queue: await checks.queue,
|
|
1240
|
+
railwayResources: await checks.railwayResources
|
|
1237
1241
|
};
|
|
1238
1242
|
const ok = [
|
|
1239
1243
|
resolvedChecks.pages,
|
|
@@ -1243,7 +1247,8 @@ async function monitorProjectPlatform(options) {
|
|
|
1243
1247
|
resolvedChecks.agentHealth,
|
|
1244
1248
|
resolvedChecks.r2,
|
|
1245
1249
|
resolvedChecks.queue,
|
|
1246
|
-
resolvedChecks.scaleProbe
|
|
1250
|
+
resolvedChecks.scaleProbe,
|
|
1251
|
+
resolvedChecks.railwayResources
|
|
1247
1252
|
].every((check) => check?.ok === true || check?.skipped === true);
|
|
1248
1253
|
if (!ok) {
|
|
1249
1254
|
const failedChecks = Object.entries(resolvedChecks).filter(([, check]) => check && typeof check === "object" && check.ok !== true && check.skipped !== true).map(([name, check]) => `${name}: ${JSON.stringify(check)}`);
|
|
@@ -2,6 +2,7 @@ import { type TreeseedBootstrapTaskPrefix, type TreeseedBootstrapWriter } from '
|
|
|
2
2
|
export declare function deriveRailwayWorkerRunnerServiceName(projectSlug: any, index?: number): string;
|
|
3
3
|
export declare function deriveRailwayWorkerRunnerVolumeName(serviceName: any, environmentName?: string): string;
|
|
4
4
|
export declare function railwayServiceRuntimeStartCommand(service: any): any;
|
|
5
|
+
export declare function collectRailwayDeploymentStatusChecks(statusPayload: any, scope: any, services: any): any;
|
|
5
6
|
export declare function isUsableRailwayToken(value: any): boolean;
|
|
6
7
|
export declare function resolveRailwayAuthToken(env?: NodeJS.ProcessEnv): string;
|
|
7
8
|
export declare function buildRailwayCommandEnv(env?: NodeJS.ProcessEnv): {
|
|
@@ -12,6 +13,43 @@ export declare function runRailway(args: any, { cwd, capture, allowFailure, inpu
|
|
|
12
13
|
capture?: boolean | undefined;
|
|
13
14
|
allowFailure?: boolean | undefined;
|
|
14
15
|
}): import("child_process").SpawnSyncReturns<string>;
|
|
16
|
+
export declare function waitForRailwayManagedDeploymentsSettled(tenantRoot: any, scope: any, { services, env, timeoutMs, pollMs, }?: {
|
|
17
|
+
services?: ({
|
|
18
|
+
key: string;
|
|
19
|
+
scope: string;
|
|
20
|
+
projectId: string | null;
|
|
21
|
+
projectName: string;
|
|
22
|
+
serviceId: string | null;
|
|
23
|
+
serviceName: string;
|
|
24
|
+
rootDir: string;
|
|
25
|
+
publicBaseUrl: string | null;
|
|
26
|
+
railwayEnvironment: string;
|
|
27
|
+
buildCommand: string | null;
|
|
28
|
+
startCommand: string | null;
|
|
29
|
+
healthcheckPath: any;
|
|
30
|
+
healthcheckTimeoutSeconds: any;
|
|
31
|
+
healthcheckIntervalSeconds: any;
|
|
32
|
+
restartPolicy: any;
|
|
33
|
+
runtimeMode: any;
|
|
34
|
+
schedule: string[];
|
|
35
|
+
hostingKind: string;
|
|
36
|
+
runnerPool: {
|
|
37
|
+
bootstrapIndex: number;
|
|
38
|
+
volumeMountPath: string;
|
|
39
|
+
} | null;
|
|
40
|
+
} | null)[] | undefined;
|
|
41
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
42
|
+
timeoutMs?: number | undefined;
|
|
43
|
+
pollMs?: number | undefined;
|
|
44
|
+
}): Promise<{
|
|
45
|
+
ok: boolean;
|
|
46
|
+
checks: any;
|
|
47
|
+
message?: undefined;
|
|
48
|
+
} | {
|
|
49
|
+
ok: boolean;
|
|
50
|
+
checks: any;
|
|
51
|
+
message: string;
|
|
52
|
+
}>;
|
|
15
53
|
export declare function setRailwaySecretVariable({ cwd, service, environment, key, value, env, capture, allowFailure }: {
|
|
16
54
|
cwd: any;
|
|
17
55
|
service: any;
|
|
@@ -281,6 +319,16 @@ export declare function verifyRailwayScheduledJobs(tenantRoot: any, scope: any,
|
|
|
281
319
|
unsupported?: undefined;
|
|
282
320
|
message?: undefined;
|
|
283
321
|
}>;
|
|
322
|
+
export declare function verifyRailwayManagedResources(tenantRoot: any, scope: any, { fetchImpl, apiToken, apiUrl, env, settleDeployments, settleTimeoutMs, settlePollMs, }?: {
|
|
323
|
+
fetchImpl?: typeof fetch | undefined;
|
|
324
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
325
|
+
settleDeployments?: boolean | undefined;
|
|
326
|
+
settleTimeoutMs?: number | undefined;
|
|
327
|
+
settlePollMs?: number | undefined;
|
|
328
|
+
}): Promise<{
|
|
329
|
+
ok: boolean;
|
|
330
|
+
checks: any[];
|
|
331
|
+
}>;
|
|
284
332
|
export declare function planRailwayServiceDeploy(service: any, { env }?: {
|
|
285
333
|
env?: NodeJS.ProcessEnv | undefined;
|
|
286
334
|
}): {
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
listRailwayEnvironments,
|
|
16
16
|
listRailwayProjects,
|
|
17
17
|
listRailwayServices,
|
|
18
|
+
listRailwayVolumes,
|
|
18
19
|
normalizeRailwayEnvironmentName,
|
|
19
20
|
resolveRailwayApiToken,
|
|
20
21
|
resolveRailwayApiUrl,
|
|
@@ -94,6 +95,74 @@ function parseRailwayJsonOutput(output) {
|
|
|
94
95
|
}
|
|
95
96
|
return null;
|
|
96
97
|
}
|
|
98
|
+
function railwayEdgeNodes(value) {
|
|
99
|
+
return Array.isArray(value?.edges) ? value.edges.map((entry) => entry?.node).filter(Boolean) : [];
|
|
100
|
+
}
|
|
101
|
+
function railwayStatusEnvironmentNodes(payload) {
|
|
102
|
+
if (!payload || typeof payload !== "object") {
|
|
103
|
+
return [];
|
|
104
|
+
}
|
|
105
|
+
if (Array.isArray(payload.environments)) {
|
|
106
|
+
return payload.environments;
|
|
107
|
+
}
|
|
108
|
+
return railwayEdgeNodes(payload.environments);
|
|
109
|
+
}
|
|
110
|
+
function railwayStatusDeploymentSettled(status) {
|
|
111
|
+
const normalized = String(status ?? "").trim().toUpperCase();
|
|
112
|
+
return normalized === "SUCCESS" || normalized === "SLEEPING";
|
|
113
|
+
}
|
|
114
|
+
function collectRailwayDeploymentStatusChecks(statusPayload, scope, services) {
|
|
115
|
+
const expectedEnvironment = resolveRailwayEnvironmentForScope(scope);
|
|
116
|
+
const environments = railwayStatusEnvironmentNodes(statusPayload);
|
|
117
|
+
const environment = environments.find(
|
|
118
|
+
(candidate) => normalizeRailwayEnvironmentName(candidate?.name) === expectedEnvironment
|
|
119
|
+
) ?? null;
|
|
120
|
+
if (!environment) {
|
|
121
|
+
return services.map((service) => ({
|
|
122
|
+
type: "deployment-status",
|
|
123
|
+
service: service.key,
|
|
124
|
+
serviceName: service.serviceName,
|
|
125
|
+
environment: expectedEnvironment,
|
|
126
|
+
ok: false,
|
|
127
|
+
status: "missing_environment",
|
|
128
|
+
message: `Railway status did not include the ${expectedEnvironment} environment.`
|
|
129
|
+
}));
|
|
130
|
+
}
|
|
131
|
+
const instances = railwayEdgeNodes(environment.serviceInstances);
|
|
132
|
+
return services.map((service) => {
|
|
133
|
+
const instance = instances.find((candidate) => candidate?.serviceName === service.serviceName) ?? null;
|
|
134
|
+
if (!instance) {
|
|
135
|
+
return {
|
|
136
|
+
type: "deployment-status",
|
|
137
|
+
service: service.key,
|
|
138
|
+
serviceName: service.serviceName,
|
|
139
|
+
environment: expectedEnvironment,
|
|
140
|
+
ok: false,
|
|
141
|
+
status: "missing_service_instance",
|
|
142
|
+
message: `Railway status did not include service ${service.serviceName} in ${expectedEnvironment}.`
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
const deployment = instance.latestDeployment ?? null;
|
|
146
|
+
const status = String(deployment?.status ?? "").trim().toUpperCase();
|
|
147
|
+
const instanceStatuses = Array.isArray(deployment?.instances) ? deployment.instances.map((entry) => String(entry?.status ?? "").trim()).filter(Boolean) : [];
|
|
148
|
+
const ok = railwayStatusDeploymentSettled(status);
|
|
149
|
+
return {
|
|
150
|
+
type: "deployment-status",
|
|
151
|
+
service: service.key,
|
|
152
|
+
serviceName: service.serviceName,
|
|
153
|
+
environment: normalizeRailwayEnvironmentName(environment.name),
|
|
154
|
+
ok,
|
|
155
|
+
status: status || "missing_deployment",
|
|
156
|
+
observed: {
|
|
157
|
+
status: status || null,
|
|
158
|
+
deploymentStopped: deployment?.deploymentStopped ?? null,
|
|
159
|
+
instanceStatuses,
|
|
160
|
+
volumeMounts: Array.isArray(deployment?.meta?.volumeMounts) ? deployment.meta.volumeMounts : []
|
|
161
|
+
},
|
|
162
|
+
message: ok ? void 0 : `Railway deployment for ${service.serviceName} is not settled yet; observed ${status || "missing deployment status"}.`
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
}
|
|
97
166
|
function normalizeRailwayCliVolume(value, { serviceId, environmentId, fallbackName, fallbackMountPath }) {
|
|
98
167
|
if (!value || typeof value !== "object") {
|
|
99
168
|
return null;
|
|
@@ -320,6 +389,50 @@ function runRailway(args, { cwd, capture = false, allowFailure = false, input, e
|
|
|
320
389
|
}
|
|
321
390
|
return result;
|
|
322
391
|
}
|
|
392
|
+
async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
|
|
393
|
+
services = configuredRailwayServices(tenantRoot, scope),
|
|
394
|
+
env = process.env,
|
|
395
|
+
timeoutMs = 18e4,
|
|
396
|
+
pollMs = 15e3
|
|
397
|
+
} = {}) {
|
|
398
|
+
const deadline = Date.now() + timeoutMs;
|
|
399
|
+
let checks = [];
|
|
400
|
+
let lastError = null;
|
|
401
|
+
for (; ; ) {
|
|
402
|
+
try {
|
|
403
|
+
const result = runRailway(["status", "--json"], {
|
|
404
|
+
cwd: tenantRoot,
|
|
405
|
+
capture: true,
|
|
406
|
+
env
|
|
407
|
+
});
|
|
408
|
+
const payload = parseRailwayJsonOutput(result.stdout);
|
|
409
|
+
checks = collectRailwayDeploymentStatusChecks(payload, scope, services);
|
|
410
|
+
lastError = null;
|
|
411
|
+
if (checks.every((entry) => entry.ok === true)) {
|
|
412
|
+
return { ok: true, checks };
|
|
413
|
+
}
|
|
414
|
+
} catch (error) {
|
|
415
|
+
lastError = error;
|
|
416
|
+
checks = services.map((service) => ({
|
|
417
|
+
type: "deployment-status",
|
|
418
|
+
service: service.key,
|
|
419
|
+
serviceName: service.serviceName,
|
|
420
|
+
environment: resolveRailwayEnvironmentForScope(scope),
|
|
421
|
+
ok: false,
|
|
422
|
+
status: "status_error",
|
|
423
|
+
message: error instanceof Error ? error.message : String(error)
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
if (Date.now() >= deadline) {
|
|
427
|
+
return {
|
|
428
|
+
ok: false,
|
|
429
|
+
checks,
|
|
430
|
+
message: lastError instanceof Error ? lastError.message : "Railway deployments did not settle before the monitor timeout."
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
await sleep(pollMs);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
323
436
|
function setRailwaySecretVariable({ cwd, service, environment, key, value, env = process.env, capture = false, allowFailure = false }) {
|
|
324
437
|
const effectiveEnv = buildRailwayCommandEnv({
|
|
325
438
|
...process.env,
|
|
@@ -841,6 +954,131 @@ async function verifyRailwayScheduledJobs(tenantRoot, scope, { fetchImpl = fetch
|
|
|
841
954
|
checks
|
|
842
955
|
};
|
|
843
956
|
}
|
|
957
|
+
async function verifyRailwayManagedResources(tenantRoot, scope, {
|
|
958
|
+
fetchImpl = fetch,
|
|
959
|
+
apiToken,
|
|
960
|
+
apiUrl,
|
|
961
|
+
env = process.env,
|
|
962
|
+
settleDeployments = false,
|
|
963
|
+
settleTimeoutMs = 18e4,
|
|
964
|
+
settlePollMs = 15e3
|
|
965
|
+
} = {}) {
|
|
966
|
+
const effectiveApiToken = apiToken || resolveRailwayAuthToken(env);
|
|
967
|
+
const effectiveApiUrl = apiUrl || resolveRailwayApiUrl(env);
|
|
968
|
+
const effectiveEnv = { ...env, RAILWAY_API_TOKEN: effectiveApiToken, TREESEED_RAILWAY_API_URL: effectiveApiUrl };
|
|
969
|
+
const services = configuredRailwayServices(tenantRoot, scope);
|
|
970
|
+
const checks = [];
|
|
971
|
+
for (const service of services) {
|
|
972
|
+
const target = await resolveRailwayScheduleTarget({
|
|
973
|
+
projectId: service.projectId,
|
|
974
|
+
projectName: service.projectName,
|
|
975
|
+
serviceId: service.serviceId,
|
|
976
|
+
serviceName: service.serviceName,
|
|
977
|
+
environment: normalizeRailwayEnvironmentName(service.railwayEnvironment),
|
|
978
|
+
environmentId: envValue("TREESEED_RAILWAY_ENVIRONMENT_ID") || null
|
|
979
|
+
}, {
|
|
980
|
+
env: effectiveEnv,
|
|
981
|
+
fetchImpl,
|
|
982
|
+
ensure: false
|
|
983
|
+
});
|
|
984
|
+
if (!target.project || !target.environment || !target.service) {
|
|
985
|
+
checks.push({
|
|
986
|
+
type: "service",
|
|
987
|
+
service: service.key,
|
|
988
|
+
serviceName: service.serviceName,
|
|
989
|
+
projectName: service.projectName,
|
|
990
|
+
environment: service.railwayEnvironment,
|
|
991
|
+
ok: false,
|
|
992
|
+
status: "missing",
|
|
993
|
+
message: `Railway service ${service.serviceName} is missing in ${service.railwayEnvironment}.`
|
|
994
|
+
});
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
const instance = await getRailwayServiceInstance({
|
|
998
|
+
serviceId: target.service.id,
|
|
999
|
+
environmentId: target.environment.id,
|
|
1000
|
+
env: effectiveEnv,
|
|
1001
|
+
fetchImpl
|
|
1002
|
+
});
|
|
1003
|
+
checks.push({
|
|
1004
|
+
type: "service-instance",
|
|
1005
|
+
service: service.key,
|
|
1006
|
+
serviceName: target.service.name,
|
|
1007
|
+
serviceId: target.service.id,
|
|
1008
|
+
projectId: target.project.id,
|
|
1009
|
+
environment: target.environment.name,
|
|
1010
|
+
environmentId: target.environment.id,
|
|
1011
|
+
instanceId: instance.id,
|
|
1012
|
+
ok: Boolean(instance.id),
|
|
1013
|
+
status: instance.id ? "checked" : "missing",
|
|
1014
|
+
observed: instance.id ? {
|
|
1015
|
+
rootDirectory: instance.rootDirectory,
|
|
1016
|
+
startCommand: instance.startCommand,
|
|
1017
|
+
cronSchedule: instance.cronSchedule,
|
|
1018
|
+
sleepApplication: instance.sleepApplication,
|
|
1019
|
+
runtimeMode: instance.runtimeMode
|
|
1020
|
+
} : null,
|
|
1021
|
+
message: instance.id ? void 0 : `Railway service instance for ${target.service.name} is missing in ${target.environment.name}.`
|
|
1022
|
+
});
|
|
1023
|
+
if (service.key === "workerRunner") {
|
|
1024
|
+
const expectedVolumeName = deriveRailwayWorkerRunnerVolumeName(target.service.name, target.environment.name);
|
|
1025
|
+
const volumes = await listRailwayVolumes({
|
|
1026
|
+
projectId: target.project.id,
|
|
1027
|
+
env: effectiveEnv,
|
|
1028
|
+
fetchImpl
|
|
1029
|
+
});
|
|
1030
|
+
const volume = volumes.find(
|
|
1031
|
+
(candidate) => candidate.name === expectedVolumeName && candidate.instances.some((entry) => entry.serviceId === target.service.id && entry.environmentId === target.environment.id && entry.mountPath === WORKER_RUNNER_VOLUME_MOUNT_PATH)
|
|
1032
|
+
) ?? null;
|
|
1033
|
+
checks.push({
|
|
1034
|
+
type: "worker-runner-volume",
|
|
1035
|
+
service: service.key,
|
|
1036
|
+
serviceName: target.service.name,
|
|
1037
|
+
serviceId: target.service.id,
|
|
1038
|
+
projectId: target.project.id,
|
|
1039
|
+
environment: target.environment.name,
|
|
1040
|
+
environmentId: target.environment.id,
|
|
1041
|
+
volumeName: expectedVolumeName,
|
|
1042
|
+
mountPath: WORKER_RUNNER_VOLUME_MOUNT_PATH,
|
|
1043
|
+
ok: Boolean(volume),
|
|
1044
|
+
status: volume ? "checked" : "missing",
|
|
1045
|
+
observed: volume ? {
|
|
1046
|
+
id: volume.id,
|
|
1047
|
+
name: volume.name,
|
|
1048
|
+
instances: volume.instances
|
|
1049
|
+
} : null,
|
|
1050
|
+
message: volume ? void 0 : `Railway worker-runner volume ${expectedVolumeName} is missing or is not mounted at ${WORKER_RUNNER_VOLUME_MOUNT_PATH}.`
|
|
1051
|
+
});
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
const schedules = await verifyRailwayScheduledJobs(tenantRoot, scope, {
|
|
1055
|
+
fetchImpl,
|
|
1056
|
+
apiToken: effectiveApiToken,
|
|
1057
|
+
apiUrl: effectiveApiUrl,
|
|
1058
|
+
env: effectiveEnv
|
|
1059
|
+
});
|
|
1060
|
+
for (const check of schedules.checks ?? []) {
|
|
1061
|
+
checks.push({
|
|
1062
|
+
type: "schedule",
|
|
1063
|
+
...check
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
if (settleDeployments) {
|
|
1067
|
+
const settled = await waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
|
|
1068
|
+
services,
|
|
1069
|
+
env: effectiveEnv,
|
|
1070
|
+
timeoutMs: settleTimeoutMs,
|
|
1071
|
+
pollMs: settlePollMs
|
|
1072
|
+
});
|
|
1073
|
+
for (const check of settled.checks ?? []) {
|
|
1074
|
+
checks.push(check);
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
return {
|
|
1078
|
+
ok: checks.every((entry) => entry.ok === true || entry.skipped === true),
|
|
1079
|
+
checks
|
|
1080
|
+
};
|
|
1081
|
+
}
|
|
844
1082
|
function shouldAttachRailwayDeployLogs(env = process.env) {
|
|
845
1083
|
return configuredEnvValue(env, "TREESEED_RAILWAY_DEPLOY_ATTACH_LOGS") === "1";
|
|
846
1084
|
}
|
|
@@ -1148,6 +1386,7 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1148
1386
|
}
|
|
1149
1387
|
export {
|
|
1150
1388
|
buildRailwayCommandEnv,
|
|
1389
|
+
collectRailwayDeploymentStatusChecks,
|
|
1151
1390
|
configuredRailwayScheduledJobs,
|
|
1152
1391
|
configuredRailwayServices,
|
|
1153
1392
|
deployRailwayService,
|
|
@@ -1168,5 +1407,7 @@ export {
|
|
|
1168
1407
|
setRailwaySecretVariable,
|
|
1169
1408
|
validateRailwayDeployPrerequisites,
|
|
1170
1409
|
validateRailwayServiceConfiguration,
|
|
1171
|
-
|
|
1410
|
+
verifyRailwayManagedResources,
|
|
1411
|
+
verifyRailwayScheduledJobs,
|
|
1412
|
+
waitForRailwayManagedDeploymentsSettled
|
|
1172
1413
|
};
|
|
@@ -319,7 +319,7 @@ function normalizeSaveVerifyMode(mode) {
|
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
321
|
function shouldUseHostedSaveCi(input) {
|
|
322
|
-
return normalizeCiMode(input.ciMode, "save") === "hosted" || input.verifyMode === "hosted" || input.verifyMode === "both";
|
|
322
|
+
return normalizeCiMode(input.ciMode, "save") === "hosted" || input.verifyMode === "hosted" || input.verifyMode === "both" || input.verifyDeployedResources === true;
|
|
323
323
|
}
|
|
324
324
|
function worktreePayload(root, requestedMode) {
|
|
325
325
|
const metadata = managedWorkflowWorktreeMetadata(root);
|
|
@@ -2875,7 +2875,8 @@ async function workflowSave(helpers, input) {
|
|
|
2875
2875
|
ciMode: effectiveInput.ciMode ?? "auto",
|
|
2876
2876
|
worktreeMode: effectiveInput.worktreeMode ?? "auto",
|
|
2877
2877
|
commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
|
|
2878
|
-
workspaceLinks: effectiveInput.workspaceLinks ?? "auto"
|
|
2878
|
+
workspaceLinks: effectiveInput.workspaceLinks ?? "auto",
|
|
2879
|
+
verifyDeployedResources: effectiveInput.verifyDeployedResources === true
|
|
2879
2880
|
},
|
|
2880
2881
|
[
|
|
2881
2882
|
{
|
|
@@ -2971,6 +2972,13 @@ async function workflowSave(helpers, input) {
|
|
|
2971
2972
|
branch,
|
|
2972
2973
|
headSha: savedRootRepo.commitSha
|
|
2973
2974
|
}] : [],
|
|
2975
|
+
...effectiveInput.verifyDeployedResources === true && scope !== "local" && savedRootRepo.pushed && savedRootRepo.commitSha && branch ? [{
|
|
2976
|
+
name: savedRootRepo.name,
|
|
2977
|
+
repoPath: savedRootRepo.path,
|
|
2978
|
+
workflow: "deploy.yml",
|
|
2979
|
+
branch,
|
|
2980
|
+
headSha: savedRootRepo.commitSha
|
|
2981
|
+
}] : [],
|
|
2974
2982
|
...savedPackageReports.filter((repo) => repo.pushed && repo.commitSha && repo.branch).map((repo) => ({
|
|
2975
2983
|
name: repo.name,
|
|
2976
2984
|
repoPath: repo.path,
|
|
@@ -2978,7 +2986,11 @@ async function workflowSave(helpers, input) {
|
|
|
2978
2986
|
branch: String(repo.branch),
|
|
2979
2987
|
headSha: String(repo.commitSha)
|
|
2980
2988
|
}))
|
|
2981
|
-
], "hosted", {
|
|
2989
|
+
], "hosted", {
|
|
2990
|
+
root,
|
|
2991
|
+
runId: workflowRun.runId,
|
|
2992
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
2993
|
+
}).then((workflowGates) => ({ workflowGates }))) : { workflowGates: [] };
|
|
2982
2994
|
const releaseCandidate = branch === STAGING_BRANCH ? await executeJournalStep(root, workflowRun.runId, "release-candidate", () => {
|
|
2983
2995
|
const releaseSession = resolveTreeseedWorkflowSession(root);
|
|
2984
2996
|
const stagingReleasePlan = buildReleasePlanSnapshot({
|
|
@@ -3289,6 +3301,7 @@ async function workflowStage(helpers, input) {
|
|
|
3289
3301
|
const effectiveInput = autoResumeRun ? autoResumeRun.input : input;
|
|
3290
3302
|
const message = ensureMessage("stage", effectiveInput.message, "a resolution message");
|
|
3291
3303
|
const ciMode = normalizeCiMode(effectiveInput.ciMode, "stage");
|
|
3304
|
+
const waitForStaging = effectiveInput.verifyDeployedResources === true || effectiveInput.waitForStaging !== false;
|
|
3292
3305
|
if (executionMode === "plan") {
|
|
3293
3306
|
const blockers = [];
|
|
3294
3307
|
if (initialSession.branchRole !== "feature") {
|
|
@@ -3371,11 +3384,12 @@ async function workflowStage(helpers, input) {
|
|
|
3371
3384
|
session,
|
|
3372
3385
|
{
|
|
3373
3386
|
message,
|
|
3374
|
-
waitForStaging
|
|
3387
|
+
waitForStaging,
|
|
3375
3388
|
deletePreview: effectiveInput.deletePreview !== false,
|
|
3376
3389
|
deleteBranch: effectiveInput.deleteBranch !== false,
|
|
3377
3390
|
ciMode,
|
|
3378
|
-
worktreeMode: effectiveInput.worktreeMode ?? "auto"
|
|
3391
|
+
worktreeMode: effectiveInput.worktreeMode ?? "auto",
|
|
3392
|
+
verifyDeployedResources: effectiveInput.verifyDeployedResources === true
|
|
3379
3393
|
},
|
|
3380
3394
|
[
|
|
3381
3395
|
{ id: "workspace-unlink", description: "Remove local workspace links", repoName: rootRepo.name, repoPath: rootRepo.path, branch: featureBranch, resumable: true },
|
|
@@ -3491,7 +3505,7 @@ async function workflowStage(helpers, input) {
|
|
|
3491
3505
|
exitCode: 12
|
|
3492
3506
|
});
|
|
3493
3507
|
}
|
|
3494
|
-
const stageWorkflowGateResult =
|
|
3508
|
+
const stageWorkflowGateResult = !waitForStaging ? (skipJournalStep(root, workflowRun.runId, "wait-staging", { status: "skipped", reason: "disabled" }), { status: "skipped", reason: "disabled" }) : await executeJournalStep(root, workflowRun.runId, "wait-staging", () => waitForWorkflowGates("stage", [
|
|
3495
3509
|
{
|
|
3496
3510
|
name: rootRepo.name,
|
|
3497
3511
|
repoPath: rootRepo.path,
|
|
@@ -3506,7 +3520,11 @@ async function workflowStage(helpers, input) {
|
|
|
3506
3520
|
branch: STAGING_BRANCH,
|
|
3507
3521
|
headSha: String(report.commitSha)
|
|
3508
3522
|
}))
|
|
3509
|
-
], ciMode, {
|
|
3523
|
+
], ciMode, {
|
|
3524
|
+
root,
|
|
3525
|
+
runId: workflowRun.runId,
|
|
3526
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
3527
|
+
}).then((workflowGates) => ({
|
|
3510
3528
|
status: "completed",
|
|
3511
3529
|
workflowGates
|
|
3512
3530
|
})));
|
package/dist/workflow.d.ts
CHANGED
|
@@ -111,6 +111,7 @@ export type TreeseedSaveInput = {
|
|
|
111
111
|
worktreeMode?: TreeseedWorkflowWorktreeMode;
|
|
112
112
|
commitMessageMode?: 'auto' | 'cloudflare' | 'generated' | 'fallback';
|
|
113
113
|
workspaceLinks?: 'auto' | 'off';
|
|
114
|
+
verifyDeployedResources?: boolean;
|
|
114
115
|
plan?: boolean;
|
|
115
116
|
dryRun?: boolean;
|
|
116
117
|
};
|
|
@@ -153,6 +154,7 @@ export type TreeseedStageInput = {
|
|
|
153
154
|
ciMode?: TreeseedWorkflowCiMode;
|
|
154
155
|
worktreeMode?: TreeseedWorkflowWorktreeMode;
|
|
155
156
|
workspaceLinks?: 'auto' | 'off';
|
|
157
|
+
verifyDeployedResources?: boolean;
|
|
156
158
|
plan?: boolean;
|
|
157
159
|
dryRun?: boolean;
|
|
158
160
|
};
|