@treeseed/sdk 0.10.18 → 0.10.20
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/railway-api.d.ts +3 -1
- package/dist/operations/services/railway-api.js +19 -19
- package/dist/operations/services/railway-deploy.d.ts +9 -0
- package/dist/operations/services/railway-deploy.js +120 -5
- package/dist/reconcile/builtin-adapters.js +75 -6
- package/dist/workflow/operations.js +36 -2
- package/package.json +1 -1
|
@@ -199,7 +199,7 @@ export declare function getRailwayServiceInstance({ serviceId, environmentId, en
|
|
|
199
199
|
sleepApplication: null;
|
|
200
200
|
runtimeConfigSupported: false;
|
|
201
201
|
}>;
|
|
202
|
-
export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, environmentId, buildCommand, startCommand, cronSchedule, rootDirectory, healthcheckPath, healthcheckTimeoutSeconds, healthcheckIntervalSeconds, restartPolicy, runtimeMode, env, fetchImpl, }: {
|
|
202
|
+
export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, environmentId, buildCommand, startCommand, cronSchedule, rootDirectory, healthcheckPath, healthcheckTimeoutSeconds, healthcheckIntervalSeconds, restartPolicy, runtimeMode, env, fetchImpl, settleAttempts, settleDelayMs, }: {
|
|
203
203
|
serviceId: string;
|
|
204
204
|
environmentId: string;
|
|
205
205
|
buildCommand?: string | null;
|
|
@@ -213,6 +213,8 @@ export declare function ensureRailwayServiceInstanceConfiguration({ serviceId, e
|
|
|
213
213
|
runtimeMode?: string | null;
|
|
214
214
|
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
215
215
|
fetchImpl?: typeof fetch;
|
|
216
|
+
settleAttempts?: number;
|
|
217
|
+
settleDelayMs?: number;
|
|
216
218
|
}): Promise<{
|
|
217
219
|
instance: {
|
|
218
220
|
id: string | null;
|
|
@@ -838,7 +838,9 @@ async function ensureRailwayServiceInstanceConfiguration({
|
|
|
838
838
|
restartPolicy,
|
|
839
839
|
runtimeMode,
|
|
840
840
|
env = process.env,
|
|
841
|
-
fetchImpl = fetch
|
|
841
|
+
fetchImpl = fetch,
|
|
842
|
+
settleAttempts = 24,
|
|
843
|
+
settleDelayMs = 5e3
|
|
842
844
|
}) {
|
|
843
845
|
let current = await getRailwayServiceInstance({ serviceId, environmentId, env, fetchImpl });
|
|
844
846
|
if (!current.id) {
|
|
@@ -911,34 +913,32 @@ mutation TreeseedRailwayServiceInstanceUpdateLegacy($serviceId: String!, $enviro
|
|
|
911
913
|
}
|
|
912
914
|
throw error;
|
|
913
915
|
}
|
|
914
|
-
let instance =
|
|
915
|
-
|
|
916
|
-
environmentId,
|
|
917
|
-
env,
|
|
918
|
-
fetchImpl
|
|
919
|
-
});
|
|
920
|
-
for (let attempt = 0; attempt < 5 && serviceInstanceDrifted(instance, desired); attempt += 1) {
|
|
921
|
-
await new Promise((resolve) => setTimeout(resolve, 1e3));
|
|
916
|
+
let instance = current;
|
|
917
|
+
for (let attempt = 0; attempt <= settleAttempts; attempt += 1) {
|
|
922
918
|
instance = await getRailwayServiceInstance({
|
|
923
919
|
serviceId,
|
|
924
920
|
environmentId,
|
|
925
921
|
env,
|
|
926
922
|
fetchImpl
|
|
927
923
|
});
|
|
924
|
+
if (!serviceInstanceDrifted(instance, desired) || attempt >= settleAttempts) {
|
|
925
|
+
break;
|
|
926
|
+
}
|
|
927
|
+
await new Promise((resolve) => setTimeout(resolve, settleDelayMs));
|
|
928
928
|
}
|
|
929
929
|
return {
|
|
930
930
|
instance: {
|
|
931
931
|
id: instance.id || current.id,
|
|
932
|
-
buildCommand: instance.buildCommand
|
|
933
|
-
startCommand: instance.startCommand
|
|
934
|
-
cronSchedule: instance.cronSchedule
|
|
935
|
-
rootDirectory: instance.rootDirectory
|
|
936
|
-
healthcheckPath: instance.healthcheckPath
|
|
937
|
-
healthcheckTimeoutSeconds: instance.healthcheckTimeoutSeconds
|
|
938
|
-
healthcheckIntervalSeconds: instance.healthcheckIntervalSeconds
|
|
939
|
-
restartPolicy: instance.restartPolicy
|
|
940
|
-
runtimeMode: instance.runtimeMode
|
|
941
|
-
sleepApplication: instance.sleepApplication
|
|
932
|
+
buildCommand: instance.buildCommand,
|
|
933
|
+
startCommand: instance.startCommand,
|
|
934
|
+
cronSchedule: instance.cronSchedule,
|
|
935
|
+
rootDirectory: instance.rootDirectory,
|
|
936
|
+
healthcheckPath: instance.healthcheckPath,
|
|
937
|
+
healthcheckTimeoutSeconds: instance.healthcheckTimeoutSeconds,
|
|
938
|
+
healthcheckIntervalSeconds: instance.healthcheckIntervalSeconds,
|
|
939
|
+
restartPolicy: instance.restartPolicy,
|
|
940
|
+
runtimeMode: instance.runtimeMode,
|
|
941
|
+
sleepApplication: instance.sleepApplication,
|
|
942
942
|
runtimeConfigSupported: instance.runtimeConfigSupported
|
|
943
943
|
},
|
|
944
944
|
updated: true
|
|
@@ -4,7 +4,16 @@ export declare function deriveRailwayMarketOperationsRunnerServiceName(baseServi
|
|
|
4
4
|
export declare function deriveRailwayWorkerRunnerVolumeName(serviceName: any, environmentName?: string): string;
|
|
5
5
|
export declare function deriveRailwayMarketOperationsRunnerVolumeName(serviceName: any, environmentName?: string): string;
|
|
6
6
|
export declare function railwayServiceRuntimeStartCommand(service: any): any;
|
|
7
|
+
export declare function parseRailwayJsonOutput(output: any): any;
|
|
7
8
|
export declare function collectRailwayDeploymentStatusChecks(statusPayload: any, scope: any, services: any): any;
|
|
9
|
+
export declare function listRailwayServiceVolumesWithCli({ cwd, serviceId, environmentId, name, mountPath, env, }: {
|
|
10
|
+
cwd: any;
|
|
11
|
+
serviceId: any;
|
|
12
|
+
environmentId: any;
|
|
13
|
+
name: any;
|
|
14
|
+
mountPath: any;
|
|
15
|
+
env?: NodeJS.ProcessEnv | undefined;
|
|
16
|
+
}): any;
|
|
8
17
|
export declare function isUsableRailwayToken(value: any): boolean;
|
|
9
18
|
export declare function resolveRailwayAuthToken(env?: NodeJS.ProcessEnv): string;
|
|
10
19
|
export declare function buildRailwayCommandEnv(env?: NodeJS.ProcessEnv): {
|
|
@@ -225,6 +225,30 @@ function normalizeRailwayCliVolumeList(value, options) {
|
|
|
225
225
|
}
|
|
226
226
|
return value.volumes.map((entry) => normalizeRailwayCliVolume(entry, options)).filter(Boolean);
|
|
227
227
|
}
|
|
228
|
+
function listRailwayServiceVolumesWithCli({
|
|
229
|
+
cwd,
|
|
230
|
+
serviceId,
|
|
231
|
+
environmentId,
|
|
232
|
+
name,
|
|
233
|
+
mountPath,
|
|
234
|
+
env = process.env
|
|
235
|
+
}) {
|
|
236
|
+
const listResult = runRailway(["volume", "--service", serviceId, "--environment", environmentId, "list", "--json"], {
|
|
237
|
+
cwd,
|
|
238
|
+
capture: true,
|
|
239
|
+
allowFailure: true,
|
|
240
|
+
env
|
|
241
|
+
});
|
|
242
|
+
if ((listResult.status ?? 1) !== 0) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
return normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
|
|
246
|
+
serviceId,
|
|
247
|
+
environmentId,
|
|
248
|
+
fallbackName: name,
|
|
249
|
+
fallbackMountPath: mountPath
|
|
250
|
+
});
|
|
251
|
+
}
|
|
228
252
|
function isUsableRailwayToken(value) {
|
|
229
253
|
return typeof value === "string" && value.trim().length >= 8;
|
|
230
254
|
}
|
|
@@ -589,9 +613,7 @@ function setRailwaySecretVariable({ cwd, service, environment, key, value, env =
|
|
|
589
613
|
}
|
|
590
614
|
function ensureRailwayProjectExists(service, { env = process.env } = {}) {
|
|
591
615
|
const projectName = typeof service?.projectName === "string" ? service.projectName.trim() : "";
|
|
592
|
-
|
|
593
|
-
throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a projectName.`);
|
|
594
|
-
}
|
|
616
|
+
const projectId = typeof service?.projectId === "string" ? service.projectId.trim() : "";
|
|
595
617
|
const listed = runRailway(["list", "--json"], {
|
|
596
618
|
cwd: service.rootDir,
|
|
597
619
|
capture: true,
|
|
@@ -599,11 +621,17 @@ function ensureRailwayProjectExists(service, { env = process.env } = {}) {
|
|
|
599
621
|
env
|
|
600
622
|
});
|
|
601
623
|
if (listed.status === 0) {
|
|
602
|
-
const match = normalizeRailwayProjectList(listed.stdout ?? "").find((entry) => entry.name === projectName || entry.id === projectName);
|
|
624
|
+
const match = normalizeRailwayProjectList(listed.stdout ?? "").find((entry) => entry.name === projectName || entry.id === projectName || entry.id === projectId);
|
|
603
625
|
if (match) {
|
|
604
626
|
return match;
|
|
605
627
|
}
|
|
606
628
|
}
|
|
629
|
+
if (!projectName) {
|
|
630
|
+
if (projectId) {
|
|
631
|
+
return { id: projectId, name: "" };
|
|
632
|
+
}
|
|
633
|
+
throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a projectName.`);
|
|
634
|
+
}
|
|
607
635
|
const args = ["init", "--name", projectName, "--json"];
|
|
608
636
|
const workspace = resolveRailwayWorkspace(env);
|
|
609
637
|
if (workspace) {
|
|
@@ -1734,6 +1762,16 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1734
1762
|
capture: true,
|
|
1735
1763
|
env
|
|
1736
1764
|
};
|
|
1765
|
+
ensureRailwayProjectContext({
|
|
1766
|
+
key: serviceName,
|
|
1767
|
+
projectId,
|
|
1768
|
+
serviceName,
|
|
1769
|
+
rootDir: tenantRoot,
|
|
1770
|
+
railwayEnvironment: environmentName
|
|
1771
|
+
}, {
|
|
1772
|
+
env,
|
|
1773
|
+
capture: true
|
|
1774
|
+
});
|
|
1737
1775
|
const volumeArgs = ["volume", "--service", serviceId, "--environment", environmentId];
|
|
1738
1776
|
const listResult = runRailway([...volumeArgs, "list", "--json"], cliOptions);
|
|
1739
1777
|
const existingVolumes = normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
|
|
@@ -1758,7 +1796,43 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1758
1796
|
}
|
|
1759
1797
|
created = true;
|
|
1760
1798
|
}
|
|
1761
|
-
|
|
1799
|
+
let instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
|
|
1800
|
+
if (!instance || instance.mountPath !== mountPath) {
|
|
1801
|
+
const attachResult = runRailway([...volumeArgs, "attach", "--volume", volume.id, "--yes", "--json"], {
|
|
1802
|
+
...cliOptions,
|
|
1803
|
+
allowFailure: true
|
|
1804
|
+
});
|
|
1805
|
+
if ((attachResult.status ?? 1) !== 0) {
|
|
1806
|
+
const attachMessage = attachResult.stderr?.trim() || attachResult.stdout?.trim() || "";
|
|
1807
|
+
if (!/already mounted/iu.test(attachMessage)) {
|
|
1808
|
+
throw new Error(attachMessage || `Railway volume attach failed for ${serviceName} in ${environmentName}.`);
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
const attachedVolume = (attachResult.status ?? 1) === 0 ? normalizeRailwayCliVolume(parseRailwayJsonOutput(attachResult.stdout ?? ""), {
|
|
1812
|
+
serviceId,
|
|
1813
|
+
environmentId,
|
|
1814
|
+
fallbackName: name,
|
|
1815
|
+
fallbackMountPath: mountPath
|
|
1816
|
+
}) : null;
|
|
1817
|
+
volume = attachedVolume ?? {
|
|
1818
|
+
...volume,
|
|
1819
|
+
instances: [{
|
|
1820
|
+
...instance ?? {
|
|
1821
|
+
id: volume.id,
|
|
1822
|
+
serviceId,
|
|
1823
|
+
environmentId,
|
|
1824
|
+
state: "READY",
|
|
1825
|
+
sizeGb: null,
|
|
1826
|
+
usedGb: null
|
|
1827
|
+
},
|
|
1828
|
+
serviceId,
|
|
1829
|
+
environmentId,
|
|
1830
|
+
mountPath
|
|
1831
|
+
}]
|
|
1832
|
+
};
|
|
1833
|
+
instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
|
|
1834
|
+
updated = true;
|
|
1835
|
+
}
|
|
1762
1836
|
if (volume.name !== name || instance?.mountPath !== mountPath) {
|
|
1763
1837
|
const updateResult = runRailway([...volumeArgs, "update", "--volume", volume.id, "--name", name, "--mount-path", mountPath, "--json"], cliOptions);
|
|
1764
1838
|
const updatedVolume = normalizeRailwayCliVolume(parseRailwayJsonOutput(updateResult.stdout ?? ""), {
|
|
@@ -1774,6 +1848,18 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1774
1848
|
};
|
|
1775
1849
|
updated = true;
|
|
1776
1850
|
}
|
|
1851
|
+
const apiVolume = await waitForRailwayServiceVolumeMount({
|
|
1852
|
+
projectId,
|
|
1853
|
+
volumeId: volume.id,
|
|
1854
|
+
volumeName: name,
|
|
1855
|
+
serviceId,
|
|
1856
|
+
environmentId,
|
|
1857
|
+
mountPath,
|
|
1858
|
+
env
|
|
1859
|
+
});
|
|
1860
|
+
if (apiVolume) {
|
|
1861
|
+
volume = apiVolume;
|
|
1862
|
+
}
|
|
1777
1863
|
return {
|
|
1778
1864
|
volume,
|
|
1779
1865
|
instance: volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null,
|
|
@@ -1781,6 +1867,33 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1781
1867
|
updated
|
|
1782
1868
|
};
|
|
1783
1869
|
}
|
|
1870
|
+
async function waitForRailwayServiceVolumeMount({
|
|
1871
|
+
projectId,
|
|
1872
|
+
volumeId,
|
|
1873
|
+
volumeName,
|
|
1874
|
+
serviceId,
|
|
1875
|
+
environmentId,
|
|
1876
|
+
mountPath,
|
|
1877
|
+
env
|
|
1878
|
+
}) {
|
|
1879
|
+
for (let attempt = 0; attempt <= 24; attempt += 1) {
|
|
1880
|
+
const volumes = await listRailwayVolumes({ projectId, env });
|
|
1881
|
+
const match = volumes.find(
|
|
1882
|
+
(entry) => entry.id === volumeId || entry.name === volumeName || entry.instances.some(
|
|
1883
|
+
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
|
|
1884
|
+
)
|
|
1885
|
+
) ?? null;
|
|
1886
|
+
if (match?.instances.some(
|
|
1887
|
+
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
|
|
1888
|
+
)) {
|
|
1889
|
+
return match;
|
|
1890
|
+
}
|
|
1891
|
+
if (attempt < 24) {
|
|
1892
|
+
await sleep(5e3);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return null;
|
|
1896
|
+
}
|
|
1784
1897
|
async function deployRailwayService(tenantRoot, service, {
|
|
1785
1898
|
dryRun = false,
|
|
1786
1899
|
write,
|
|
@@ -1928,6 +2041,8 @@ export {
|
|
|
1928
2041
|
ensureRailwayServiceVolumeWithCliFallback,
|
|
1929
2042
|
isRailwayTransientFailure,
|
|
1930
2043
|
isUsableRailwayToken,
|
|
2044
|
+
listRailwayServiceVolumesWithCli,
|
|
2045
|
+
parseRailwayJsonOutput,
|
|
1931
2046
|
planRailwayServiceDeploy,
|
|
1932
2047
|
planRailwayServiceLink,
|
|
1933
2048
|
railwayServiceRuntimeStartCommand,
|
|
@@ -33,6 +33,7 @@ import {
|
|
|
33
33
|
deriveRailwayMarketOperationsRunnerVolumeName,
|
|
34
34
|
ensureRailwayProjectContext,
|
|
35
35
|
ensureRailwayServiceVolumeWithCliFallback,
|
|
36
|
+
listRailwayServiceVolumesWithCli,
|
|
36
37
|
runRailway,
|
|
37
38
|
validateRailwayDeployPrerequisites
|
|
38
39
|
} from "../operations/services/railway-deploy.js";
|
|
@@ -46,6 +47,7 @@ import {
|
|
|
46
47
|
getRailwayServiceInstance,
|
|
47
48
|
getRailwayProject,
|
|
48
49
|
listRailwayCustomDomains,
|
|
50
|
+
listRailwayEnvironments,
|
|
49
51
|
listRailwayProjects,
|
|
50
52
|
listRailwayVolumes,
|
|
51
53
|
listRailwayVariables,
|
|
@@ -1394,6 +1396,44 @@ function relativeRailwayRootDir(tenantRoot, serviceRoot) {
|
|
|
1394
1396
|
const resolved = relative(tenantRoot, serviceRoot).replace(/\\/gu, "/");
|
|
1395
1397
|
return !resolved || resolved === "" ? "." : resolved;
|
|
1396
1398
|
}
|
|
1399
|
+
async function ensureRailwayEnvironmentForService({
|
|
1400
|
+
service,
|
|
1401
|
+
project,
|
|
1402
|
+
environmentName,
|
|
1403
|
+
env
|
|
1404
|
+
}) {
|
|
1405
|
+
try {
|
|
1406
|
+
return (await ensureRailwayEnvironment({
|
|
1407
|
+
projectId: project.id,
|
|
1408
|
+
environmentName,
|
|
1409
|
+
env
|
|
1410
|
+
})).environment;
|
|
1411
|
+
} catch (error) {
|
|
1412
|
+
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
1413
|
+
if (!/Problem processing request/iu.test(message)) {
|
|
1414
|
+
throw error;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
ensureRailwayProjectContext({
|
|
1418
|
+
...service,
|
|
1419
|
+
projectId: project.id,
|
|
1420
|
+
projectName: project.name ?? service.projectName,
|
|
1421
|
+
railwayEnvironment: environmentName
|
|
1422
|
+
}, {
|
|
1423
|
+
env,
|
|
1424
|
+
allowFailure: true,
|
|
1425
|
+
capture: true
|
|
1426
|
+
});
|
|
1427
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
1428
|
+
const environments = await listRailwayEnvironments({ projectId: project.id, env });
|
|
1429
|
+
const existing = environments.find((environment) => environment.name === environmentName || environment.id === environmentName) ?? null;
|
|
1430
|
+
if (existing) {
|
|
1431
|
+
return existing;
|
|
1432
|
+
}
|
|
1433
|
+
await new Promise((resolve2) => setTimeout(resolve2, 2500));
|
|
1434
|
+
}
|
|
1435
|
+
throw new Error(`Railway environment ${environmentName} was created through the CLI fallback but was not visible through the Railway API.`);
|
|
1436
|
+
}
|
|
1397
1437
|
async function resolveRailwayTopologyForScope(input, scope, {
|
|
1398
1438
|
ensure = false,
|
|
1399
1439
|
refresh = false,
|
|
@@ -1464,11 +1504,12 @@ async function resolveRailwayTopologyForScope(input, scope, {
|
|
|
1464
1504
|
}
|
|
1465
1505
|
let environment = project?.environments.find((entry) => entry.name === service.railwayEnvironment || entry.id === service.railwayEnvironment) ?? null;
|
|
1466
1506
|
if (project && !environment && ensure) {
|
|
1467
|
-
environment =
|
|
1468
|
-
|
|
1507
|
+
environment = await ensureRailwayEnvironmentForService({
|
|
1508
|
+
service,
|
|
1509
|
+
project,
|
|
1469
1510
|
environmentName: service.railwayEnvironment,
|
|
1470
1511
|
env
|
|
1471
|
-
})
|
|
1512
|
+
});
|
|
1472
1513
|
project = {
|
|
1473
1514
|
...project,
|
|
1474
1515
|
environments: [...project.environments.filter((entry) => entry.id !== environment?.id), environment]
|
|
@@ -2216,16 +2257,33 @@ async function verifyRailwayUnit(input) {
|
|
|
2216
2257
|
const volumes = entry.project?.id ? await listRailwayVolumes({ projectId: entry.project.id, env: topology.env }) : [];
|
|
2217
2258
|
const expectedServiceId = entry.service?.id ?? null;
|
|
2218
2259
|
const expectedEnvironmentId = entry.environment?.id ?? null;
|
|
2219
|
-
|
|
2260
|
+
let mountedVolume = volumes.find((volume) => volume.instances.some(
|
|
2220
2261
|
(instance) => instance.serviceId === expectedServiceId && instance.environmentId === expectedEnvironmentId && instance.mountPath === service.volumeMountPath
|
|
2221
2262
|
)) ?? null;
|
|
2263
|
+
let mountedVolumeSource = "api";
|
|
2264
|
+
if (!mountedVolume && serviceKey === "marketOperationsRunner" && expectedServiceId && expectedEnvironmentId) {
|
|
2265
|
+
ensureRailwayProjectContext(service, { env: topology.env, capture: true });
|
|
2266
|
+
const cliVolumes = listRailwayServiceVolumesWithCli({
|
|
2267
|
+
cwd: service.rootDir,
|
|
2268
|
+
serviceId: expectedServiceId,
|
|
2269
|
+
environmentId: expectedEnvironmentId,
|
|
2270
|
+
name: deriveRailwayMarketOperationsRunnerVolumeName(entry.service?.name ?? service.serviceName ?? service.key, entry.environment?.name ?? service.railwayEnvironment),
|
|
2271
|
+
mountPath: service.volumeMountPath,
|
|
2272
|
+
env: topology.env
|
|
2273
|
+
});
|
|
2274
|
+
mountedVolume = cliVolumes.find((volume) => volume.instances.some(
|
|
2275
|
+
(instance) => instance.serviceId === expectedServiceId && instance.environmentId === expectedEnvironmentId && instance.mountPath === service.volumeMountPath
|
|
2276
|
+
)) ?? null;
|
|
2277
|
+
mountedVolumeSource = mountedVolume ? "cli" : "api";
|
|
2278
|
+
}
|
|
2222
2279
|
checks.push(verificationCheck("railway.volume:data", "Railway service has persistent data volume mounted", "api", {
|
|
2223
2280
|
exists: Boolean(mountedVolume),
|
|
2224
2281
|
configured: Boolean(mountedVolume),
|
|
2225
2282
|
expected: service.volumeMountPath,
|
|
2226
2283
|
observed: mountedVolume ? {
|
|
2227
2284
|
name: mountedVolume.name,
|
|
2228
|
-
mountPath: service.volumeMountPath
|
|
2285
|
+
mountPath: service.volumeMountPath,
|
|
2286
|
+
source: mountedVolumeSource
|
|
2229
2287
|
} : null,
|
|
2230
2288
|
issues: mountedVolume ? [] : [`Railway service ${service.serviceName ?? service.key} is missing a persistent volume mounted at ${service.volumeMountPath}.`]
|
|
2231
2289
|
}));
|
|
@@ -2247,7 +2305,13 @@ async function verifyRailwayUnit(input) {
|
|
|
2247
2305
|
issues: Object.hasOwn(entry.currentVariables, key) ? [] : [`Railway secret ${key} is missing.`]
|
|
2248
2306
|
}));
|
|
2249
2307
|
}
|
|
2250
|
-
|
|
2308
|
+
const verification = summarizeVerification(input.unit.unitId, checks);
|
|
2309
|
+
if (!verification.verified && attempt < 12 && railwayVerificationMaySettle(verification)) {
|
|
2310
|
+
attempt += 1;
|
|
2311
|
+
sleepMs(5e3);
|
|
2312
|
+
continue;
|
|
2313
|
+
}
|
|
2314
|
+
return verification;
|
|
2251
2315
|
} catch (error) {
|
|
2252
2316
|
if (attempt >= 2 || !isTransientRailwayReconcileError(error)) {
|
|
2253
2317
|
throw error;
|
|
@@ -2257,6 +2321,11 @@ async function verifyRailwayUnit(input) {
|
|
|
2257
2321
|
}
|
|
2258
2322
|
}
|
|
2259
2323
|
}
|
|
2324
|
+
function railwayVerificationMaySettle(verification) {
|
|
2325
|
+
return verification.checks.some(
|
|
2326
|
+
(check) => !check.verified && (check.key === "railway.instance" || check.key.startsWith("railway.instance.") || check.key === "railway.volume:data")
|
|
2327
|
+
);
|
|
2328
|
+
}
|
|
2260
2329
|
function railwayStartCommandMatches(serviceKey, observed, expected) {
|
|
2261
2330
|
if (observed === expected) {
|
|
2262
2331
|
return true;
|
|
@@ -69,6 +69,7 @@ import {
|
|
|
69
69
|
syncBranchWithOrigin
|
|
70
70
|
} from "../operations/services/git-workflow.js";
|
|
71
71
|
import { resolveGitHubRepositorySlug } from "../operations/services/github-automation.js";
|
|
72
|
+
import { dispatchGitHubWorkflowRun } from "../operations/services/github-api.js";
|
|
72
73
|
import {
|
|
73
74
|
formatGitHubActionsGateFailure,
|
|
74
75
|
inspectGitHubActionsVerification,
|
|
@@ -1168,6 +1169,9 @@ function workflowSessionSnapshot(session) {
|
|
|
1168
1169
|
}))
|
|
1169
1170
|
};
|
|
1170
1171
|
}
|
|
1172
|
+
function sleep(ms) {
|
|
1173
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1174
|
+
}
|
|
1171
1175
|
function nextPendingJournalStep(journal) {
|
|
1172
1176
|
return journal.steps.find((step) => step.status === "pending") ?? null;
|
|
1173
1177
|
}
|
|
@@ -3127,9 +3131,39 @@ async function workflowSave(helpers, input) {
|
|
|
3127
3131
|
lockfileValidation: repo.lockfileValidation
|
|
3128
3132
|
}))
|
|
3129
3133
|
};
|
|
3130
|
-
const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", () => {
|
|
3134
|
+
const saveWorkflowGates = shouldUseHostedSaveCi(effectiveInput, branch) ? await executeJournalStep(root, workflowRun.runId, "hosted-ci", async () => {
|
|
3131
3135
|
if (branch === STAGING_BRANCH) {
|
|
3132
|
-
|
|
3136
|
+
const workflowGates = saveResult?.workflowGates ?? [];
|
|
3137
|
+
if (workflowGates.length > 0 || effectiveInput.verifyDeployedResources !== true || scope === "local" || !savedRootRepo.commitSha) {
|
|
3138
|
+
return { workflowGates };
|
|
3139
|
+
}
|
|
3140
|
+
helpers.write("[save][workflow] Dispatching hosted market deploy gate for deployed resource verification.");
|
|
3141
|
+
const repository = resolveGitHubRepositorySlug(savedRootRepo.path);
|
|
3142
|
+
await dispatchGitHubWorkflowRun(repository, {
|
|
3143
|
+
workflow: "deploy.yml",
|
|
3144
|
+
branch,
|
|
3145
|
+
inputs: {
|
|
3146
|
+
environment: "staging",
|
|
3147
|
+
action_kind: "deploy_web"
|
|
3148
|
+
}
|
|
3149
|
+
});
|
|
3150
|
+
await sleep(5e3);
|
|
3151
|
+
helpers.write("[save][workflow] Waiting for hosted market deploy gate.");
|
|
3152
|
+
const dispatchedGates = await waitForWorkflowGates("save", [
|
|
3153
|
+
hostedDeployGate({
|
|
3154
|
+
name: savedRootRepo.name,
|
|
3155
|
+
repoPath: savedRootRepo.path,
|
|
3156
|
+
repository,
|
|
3157
|
+
workflow: "deploy.yml",
|
|
3158
|
+
branch,
|
|
3159
|
+
headSha: savedRootRepo.commitSha
|
|
3160
|
+
})
|
|
3161
|
+
], "hosted", {
|
|
3162
|
+
root,
|
|
3163
|
+
runId: workflowRun.runId,
|
|
3164
|
+
onProgress: (line, stream) => helpers.write(line, stream)
|
|
3165
|
+
});
|
|
3166
|
+
return { workflowGates: dispatchedGates };
|
|
3133
3167
|
}
|
|
3134
3168
|
helpers.write("[save][workflow] Waiting for hosted save workflow gates.");
|
|
3135
3169
|
return waitForWorkflowGates("save", [
|