@treeseed/sdk 0.10.19 → 0.10.21
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.
|
@@ -613,9 +613,7 @@ function setRailwaySecretVariable({ cwd, service, environment, key, value, env =
|
|
|
613
613
|
}
|
|
614
614
|
function ensureRailwayProjectExists(service, { env = process.env } = {}) {
|
|
615
615
|
const projectName = typeof service?.projectName === "string" ? service.projectName.trim() : "";
|
|
616
|
-
|
|
617
|
-
throw new Error(`Railway service ${service?.key ?? service?.serviceName ?? service?.serviceId ?? "(unknown)"} is missing a projectName.`);
|
|
618
|
-
}
|
|
616
|
+
const projectId = typeof service?.projectId === "string" ? service.projectId.trim() : "";
|
|
619
617
|
const listed = runRailway(["list", "--json"], {
|
|
620
618
|
cwd: service.rootDir,
|
|
621
619
|
capture: true,
|
|
@@ -623,11 +621,17 @@ function ensureRailwayProjectExists(service, { env = process.env } = {}) {
|
|
|
623
621
|
env
|
|
624
622
|
});
|
|
625
623
|
if (listed.status === 0) {
|
|
626
|
-
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);
|
|
627
625
|
if (match) {
|
|
628
626
|
return match;
|
|
629
627
|
}
|
|
630
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
|
+
}
|
|
631
635
|
const args = ["init", "--name", projectName, "--json"];
|
|
632
636
|
const workspace = resolveRailwayWorkspace(env);
|
|
633
637
|
if (workspace) {
|
|
@@ -1758,6 +1762,16 @@ async function ensureRailwayServiceVolumeWithCliFallback({
|
|
|
1758
1762
|
capture: true,
|
|
1759
1763
|
env
|
|
1760
1764
|
};
|
|
1765
|
+
ensureRailwayProjectContext({
|
|
1766
|
+
key: serviceName,
|
|
1767
|
+
projectId,
|
|
1768
|
+
serviceName,
|
|
1769
|
+
rootDir: tenantRoot,
|
|
1770
|
+
railwayEnvironment: environmentName
|
|
1771
|
+
}, {
|
|
1772
|
+
env,
|
|
1773
|
+
capture: true
|
|
1774
|
+
});
|
|
1761
1775
|
const volumeArgs = ["volume", "--service", serviceId, "--environment", environmentId];
|
|
1762
1776
|
const listResult = runRailway([...volumeArgs, "list", "--json"], cliOptions);
|
|
1763
1777
|
const existingVolumes = normalizeRailwayCliVolumeList(parseRailwayJsonOutput(listResult.stdout ?? ""), {
|
|
@@ -47,6 +47,7 @@ import {
|
|
|
47
47
|
getRailwayServiceInstance,
|
|
48
48
|
getRailwayProject,
|
|
49
49
|
listRailwayCustomDomains,
|
|
50
|
+
listRailwayEnvironments,
|
|
50
51
|
listRailwayProjects,
|
|
51
52
|
listRailwayVolumes,
|
|
52
53
|
listRailwayVariables,
|
|
@@ -1395,6 +1396,44 @@ function relativeRailwayRootDir(tenantRoot, serviceRoot) {
|
|
|
1395
1396
|
const resolved = relative(tenantRoot, serviceRoot).replace(/\\/gu, "/");
|
|
1396
1397
|
return !resolved || resolved === "" ? "." : resolved;
|
|
1397
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
|
+
}
|
|
1398
1437
|
async function resolveRailwayTopologyForScope(input, scope, {
|
|
1399
1438
|
ensure = false,
|
|
1400
1439
|
refresh = false,
|
|
@@ -1465,11 +1504,12 @@ async function resolveRailwayTopologyForScope(input, scope, {
|
|
|
1465
1504
|
}
|
|
1466
1505
|
let environment = project?.environments.find((entry) => entry.name === service.railwayEnvironment || entry.id === service.railwayEnvironment) ?? null;
|
|
1467
1506
|
if (project && !environment && ensure) {
|
|
1468
|
-
environment =
|
|
1469
|
-
|
|
1507
|
+
environment = await ensureRailwayEnvironmentForService({
|
|
1508
|
+
service,
|
|
1509
|
+
project,
|
|
1470
1510
|
environmentName: service.railwayEnvironment,
|
|
1471
1511
|
env
|
|
1472
|
-
})
|
|
1512
|
+
});
|
|
1473
1513
|
project = {
|
|
1474
1514
|
...project,
|
|
1475
1515
|
environments: [...project.environments.filter((entry) => entry.id !== environment?.id), environment]
|
|
@@ -2093,7 +2133,8 @@ async function verifyRailwayUnit(input) {
|
|
|
2093
2133
|
const topology = await resolveRailwayTopologyForScope(input, scope, {
|
|
2094
2134
|
serviceKeys: [serviceKey],
|
|
2095
2135
|
includeInstances: true,
|
|
2096
|
-
includeVariables: true
|
|
2136
|
+
includeVariables: true,
|
|
2137
|
+
refresh: true
|
|
2097
2138
|
});
|
|
2098
2139
|
const entry = topology.services.get(serviceKey) ?? null;
|
|
2099
2140
|
const service = entry?.configuredService ?? null;
|
|
@@ -408,7 +408,7 @@ export declare function workflowSave(helpers: WorkflowOperationHelpers, input: T
|
|
|
408
408
|
};
|
|
409
409
|
ciMode: "hosted" | "off";
|
|
410
410
|
verifyMode: "action-first" | "local-only" | import("../workflow.ts").TreeseedWorkflowVerifyMode;
|
|
411
|
-
workflowGates: Record<string, unknown>
|
|
411
|
+
workflowGates: (Record<string, unknown> | {
|
|
412
412
|
name: string;
|
|
413
413
|
repository: string | null;
|
|
414
414
|
workflow: string;
|
|
@@ -423,7 +423,7 @@ export declare function workflowSave(helpers: WorkflowOperationHelpers, input: T
|
|
|
423
423
|
updatedAt: null;
|
|
424
424
|
timeoutSeconds: number | null;
|
|
425
425
|
cached: boolean;
|
|
426
|
-
}[];
|
|
426
|
+
})[];
|
|
427
427
|
releaseCandidate: ReleaseCandidateReport | null;
|
|
428
428
|
hostingAudit: import("../workflow-support.ts").TreeseedHostingAuditReport | null;
|
|
429
429
|
} & {
|
|
@@ -551,7 +551,7 @@ export declare function workflowClose(helpers: WorkflowOperationHelpers, input:
|
|
|
551
551
|
};
|
|
552
552
|
ciMode: "hosted" | "off";
|
|
553
553
|
verifyMode: "action-first" | "local-only" | import("../workflow.ts").TreeseedWorkflowVerifyMode;
|
|
554
|
-
workflowGates: Record<string, unknown>
|
|
554
|
+
workflowGates: (Record<string, unknown> | {
|
|
555
555
|
name: string;
|
|
556
556
|
repository: string | null;
|
|
557
557
|
workflow: string;
|
|
@@ -566,7 +566,7 @@ export declare function workflowClose(helpers: WorkflowOperationHelpers, input:
|
|
|
566
566
|
updatedAt: null;
|
|
567
567
|
timeoutSeconds: number | null;
|
|
568
568
|
cached: boolean;
|
|
569
|
-
}[];
|
|
569
|
+
})[];
|
|
570
570
|
releaseCandidate: ReleaseCandidateReport | null;
|
|
571
571
|
hostingAudit: import("../workflow-support.ts").TreeseedHostingAuditReport | null;
|
|
572
572
|
} & {
|
|
@@ -735,7 +735,7 @@ export declare function workflowStage(helpers: WorkflowOperationHelpers, input:
|
|
|
735
735
|
};
|
|
736
736
|
ciMode: "hosted" | "off";
|
|
737
737
|
verifyMode: "action-first" | "local-only" | import("../workflow.ts").TreeseedWorkflowVerifyMode;
|
|
738
|
-
workflowGates: Record<string, unknown>
|
|
738
|
+
workflowGates: (Record<string, unknown> | {
|
|
739
739
|
name: string;
|
|
740
740
|
repository: string | null;
|
|
741
741
|
workflow: string;
|
|
@@ -750,7 +750,7 @@ export declare function workflowStage(helpers: WorkflowOperationHelpers, input:
|
|
|
750
750
|
updatedAt: null;
|
|
751
751
|
timeoutSeconds: number | null;
|
|
752
752
|
cached: boolean;
|
|
753
|
-
}[];
|
|
753
|
+
})[];
|
|
754
754
|
releaseCandidate: ReleaseCandidateReport | null;
|
|
755
755
|
hostingAudit: import("../workflow-support.ts").TreeseedHostingAuditReport | null;
|
|
756
756
|
} & {
|
|
@@ -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,44 @@ 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 (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 {
|
|
3167
|
+
workflowGates: [
|
|
3168
|
+
...workflowGates.filter((gate) => !(gate.repository === repository && gate.workflow === "deploy.yml")),
|
|
3169
|
+
...dispatchedGates
|
|
3170
|
+
]
|
|
3171
|
+
};
|
|
3133
3172
|
}
|
|
3134
3173
|
helpers.write("[save][workflow] Waiting for hosted save workflow gates.");
|
|
3135
3174
|
return waitForWorkflowGates("save", [
|