@treeseed/sdk 0.10.18 → 0.10.19
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 +102 -1
- package/dist/reconcile/builtin-adapters.js +32 -3
- 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
|
}
|
|
@@ -1758,7 +1782,43 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1758
1782
|
}
|
|
1759
1783
|
created = true;
|
|
1760
1784
|
}
|
|
1761
|
-
|
|
1785
|
+
let instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
|
|
1786
|
+
if (!instance || instance.mountPath !== mountPath) {
|
|
1787
|
+
const attachResult = runRailway([...volumeArgs, "attach", "--volume", volume.id, "--yes", "--json"], {
|
|
1788
|
+
...cliOptions,
|
|
1789
|
+
allowFailure: true
|
|
1790
|
+
});
|
|
1791
|
+
if ((attachResult.status ?? 1) !== 0) {
|
|
1792
|
+
const attachMessage = attachResult.stderr?.trim() || attachResult.stdout?.trim() || "";
|
|
1793
|
+
if (!/already mounted/iu.test(attachMessage)) {
|
|
1794
|
+
throw new Error(attachMessage || `Railway volume attach failed for ${serviceName} in ${environmentName}.`);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
const attachedVolume = (attachResult.status ?? 1) === 0 ? normalizeRailwayCliVolume(parseRailwayJsonOutput(attachResult.stdout ?? ""), {
|
|
1798
|
+
serviceId,
|
|
1799
|
+
environmentId,
|
|
1800
|
+
fallbackName: name,
|
|
1801
|
+
fallbackMountPath: mountPath
|
|
1802
|
+
}) : null;
|
|
1803
|
+
volume = attachedVolume ?? {
|
|
1804
|
+
...volume,
|
|
1805
|
+
instances: [{
|
|
1806
|
+
...instance ?? {
|
|
1807
|
+
id: volume.id,
|
|
1808
|
+
serviceId,
|
|
1809
|
+
environmentId,
|
|
1810
|
+
state: "READY",
|
|
1811
|
+
sizeGb: null,
|
|
1812
|
+
usedGb: null
|
|
1813
|
+
},
|
|
1814
|
+
serviceId,
|
|
1815
|
+
environmentId,
|
|
1816
|
+
mountPath
|
|
1817
|
+
}]
|
|
1818
|
+
};
|
|
1819
|
+
instance = volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null;
|
|
1820
|
+
updated = true;
|
|
1821
|
+
}
|
|
1762
1822
|
if (volume.name !== name || instance?.mountPath !== mountPath) {
|
|
1763
1823
|
const updateResult = runRailway([...volumeArgs, "update", "--volume", volume.id, "--name", name, "--mount-path", mountPath, "--json"], cliOptions);
|
|
1764
1824
|
const updatedVolume = normalizeRailwayCliVolume(parseRailwayJsonOutput(updateResult.stdout ?? ""), {
|
|
@@ -1774,6 +1834,18 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1774
1834
|
};
|
|
1775
1835
|
updated = true;
|
|
1776
1836
|
}
|
|
1837
|
+
const apiVolume = await waitForRailwayServiceVolumeMount({
|
|
1838
|
+
projectId,
|
|
1839
|
+
volumeId: volume.id,
|
|
1840
|
+
volumeName: name,
|
|
1841
|
+
serviceId,
|
|
1842
|
+
environmentId,
|
|
1843
|
+
mountPath,
|
|
1844
|
+
env
|
|
1845
|
+
});
|
|
1846
|
+
if (apiVolume) {
|
|
1847
|
+
volume = apiVolume;
|
|
1848
|
+
}
|
|
1777
1849
|
return {
|
|
1778
1850
|
volume,
|
|
1779
1851
|
instance: volume.instances.find((entry) => entry.serviceId === serviceId && entry.environmentId === environmentId) ?? volume.instances[0] ?? null,
|
|
@@ -1781,6 +1853,33 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1781
1853
|
updated
|
|
1782
1854
|
};
|
|
1783
1855
|
}
|
|
1856
|
+
async function waitForRailwayServiceVolumeMount({
|
|
1857
|
+
projectId,
|
|
1858
|
+
volumeId,
|
|
1859
|
+
volumeName,
|
|
1860
|
+
serviceId,
|
|
1861
|
+
environmentId,
|
|
1862
|
+
mountPath,
|
|
1863
|
+
env
|
|
1864
|
+
}) {
|
|
1865
|
+
for (let attempt = 0; attempt <= 24; attempt += 1) {
|
|
1866
|
+
const volumes = await listRailwayVolumes({ projectId, env });
|
|
1867
|
+
const match = volumes.find(
|
|
1868
|
+
(entry) => entry.id === volumeId || entry.name === volumeName || entry.instances.some(
|
|
1869
|
+
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
|
|
1870
|
+
)
|
|
1871
|
+
) ?? null;
|
|
1872
|
+
if (match?.instances.some(
|
|
1873
|
+
(instance) => instance.serviceId === serviceId && instance.environmentId === environmentId && instance.mountPath === mountPath
|
|
1874
|
+
)) {
|
|
1875
|
+
return match;
|
|
1876
|
+
}
|
|
1877
|
+
if (attempt < 24) {
|
|
1878
|
+
await sleep(5e3);
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
return null;
|
|
1882
|
+
}
|
|
1784
1883
|
async function deployRailwayService(tenantRoot, service, {
|
|
1785
1884
|
dryRun = false,
|
|
1786
1885
|
write,
|
|
@@ -1928,6 +2027,8 @@ export {
|
|
|
1928
2027
|
ensureRailwayServiceVolumeWithCliFallback,
|
|
1929
2028
|
isRailwayTransientFailure,
|
|
1930
2029
|
isUsableRailwayToken,
|
|
2030
|
+
listRailwayServiceVolumesWithCli,
|
|
2031
|
+
parseRailwayJsonOutput,
|
|
1931
2032
|
planRailwayServiceDeploy,
|
|
1932
2033
|
planRailwayServiceLink,
|
|
1933
2034
|
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";
|
|
@@ -2216,16 +2217,33 @@ async function verifyRailwayUnit(input) {
|
|
|
2216
2217
|
const volumes = entry.project?.id ? await listRailwayVolumes({ projectId: entry.project.id, env: topology.env }) : [];
|
|
2217
2218
|
const expectedServiceId = entry.service?.id ?? null;
|
|
2218
2219
|
const expectedEnvironmentId = entry.environment?.id ?? null;
|
|
2219
|
-
|
|
2220
|
+
let mountedVolume = volumes.find((volume) => volume.instances.some(
|
|
2220
2221
|
(instance) => instance.serviceId === expectedServiceId && instance.environmentId === expectedEnvironmentId && instance.mountPath === service.volumeMountPath
|
|
2221
2222
|
)) ?? null;
|
|
2223
|
+
let mountedVolumeSource = "api";
|
|
2224
|
+
if (!mountedVolume && serviceKey === "marketOperationsRunner" && expectedServiceId && expectedEnvironmentId) {
|
|
2225
|
+
ensureRailwayProjectContext(service, { env: topology.env, capture: true });
|
|
2226
|
+
const cliVolumes = listRailwayServiceVolumesWithCli({
|
|
2227
|
+
cwd: service.rootDir,
|
|
2228
|
+
serviceId: expectedServiceId,
|
|
2229
|
+
environmentId: expectedEnvironmentId,
|
|
2230
|
+
name: deriveRailwayMarketOperationsRunnerVolumeName(entry.service?.name ?? service.serviceName ?? service.key, entry.environment?.name ?? service.railwayEnvironment),
|
|
2231
|
+
mountPath: service.volumeMountPath,
|
|
2232
|
+
env: topology.env
|
|
2233
|
+
});
|
|
2234
|
+
mountedVolume = cliVolumes.find((volume) => volume.instances.some(
|
|
2235
|
+
(instance) => instance.serviceId === expectedServiceId && instance.environmentId === expectedEnvironmentId && instance.mountPath === service.volumeMountPath
|
|
2236
|
+
)) ?? null;
|
|
2237
|
+
mountedVolumeSource = mountedVolume ? "cli" : "api";
|
|
2238
|
+
}
|
|
2222
2239
|
checks.push(verificationCheck("railway.volume:data", "Railway service has persistent data volume mounted", "api", {
|
|
2223
2240
|
exists: Boolean(mountedVolume),
|
|
2224
2241
|
configured: Boolean(mountedVolume),
|
|
2225
2242
|
expected: service.volumeMountPath,
|
|
2226
2243
|
observed: mountedVolume ? {
|
|
2227
2244
|
name: mountedVolume.name,
|
|
2228
|
-
mountPath: service.volumeMountPath
|
|
2245
|
+
mountPath: service.volumeMountPath,
|
|
2246
|
+
source: mountedVolumeSource
|
|
2229
2247
|
} : null,
|
|
2230
2248
|
issues: mountedVolume ? [] : [`Railway service ${service.serviceName ?? service.key} is missing a persistent volume mounted at ${service.volumeMountPath}.`]
|
|
2231
2249
|
}));
|
|
@@ -2247,7 +2265,13 @@ async function verifyRailwayUnit(input) {
|
|
|
2247
2265
|
issues: Object.hasOwn(entry.currentVariables, key) ? [] : [`Railway secret ${key} is missing.`]
|
|
2248
2266
|
}));
|
|
2249
2267
|
}
|
|
2250
|
-
|
|
2268
|
+
const verification = summarizeVerification(input.unit.unitId, checks);
|
|
2269
|
+
if (!verification.verified && attempt < 12 && railwayVerificationMaySettle(verification)) {
|
|
2270
|
+
attempt += 1;
|
|
2271
|
+
sleepMs(5e3);
|
|
2272
|
+
continue;
|
|
2273
|
+
}
|
|
2274
|
+
return verification;
|
|
2251
2275
|
} catch (error) {
|
|
2252
2276
|
if (attempt >= 2 || !isTransientRailwayReconcileError(error)) {
|
|
2253
2277
|
throw error;
|
|
@@ -2257,6 +2281,11 @@ async function verifyRailwayUnit(input) {
|
|
|
2257
2281
|
}
|
|
2258
2282
|
}
|
|
2259
2283
|
}
|
|
2284
|
+
function railwayVerificationMaySettle(verification) {
|
|
2285
|
+
return verification.checks.some(
|
|
2286
|
+
(check) => !check.verified && (check.key === "railway.instance" || check.key.startsWith("railway.instance.") || check.key === "railway.volume:data")
|
|
2287
|
+
);
|
|
2288
|
+
}
|
|
2260
2289
|
function railwayStartCommandMatches(serviceKey, observed, expected) {
|
|
2261
2290
|
if (observed === expected) {
|
|
2262
2291
|
return true;
|