@treeseed/sdk 0.12.59 → 0.12.61
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/guarantees/index.js +59 -56
- package/dist/hosting/contracts.d.ts +0 -19
- package/dist/hosting/graph.d.ts +1 -86
- package/dist/hosting/graph.js +96 -247
- package/dist/local-dev/managed-dev.js +30 -8
- package/dist/managed-dependencies.d.ts +3 -0
- package/dist/managed-dependencies.js +294 -20
- package/dist/operations/services/deploy.js +6 -6
- package/dist/operations/services/deployment-readiness.js +5 -4
- package/dist/operations/services/git-runner.d.ts +2 -0
- package/dist/operations/services/git-runner.js +23 -2
- package/dist/operations/services/hosted-service-checks.js +28 -0
- package/dist/operations/services/live-hosted-service-checks.js +56 -14
- package/dist/operations/services/local-cleanup.d.ts +1 -0
- package/dist/operations/services/local-cleanup.js +28 -9
- package/dist/operations/services/package-adapters.js +3 -3
- package/dist/operations/services/railway-api.d.ts +72 -28
- package/dist/operations/services/railway-api.js +321 -876
- package/dist/operations/services/railway-cli.d.ts +47 -0
- package/dist/operations/services/railway-cli.js +142 -0
- package/dist/operations/services/railway-deploy.d.ts +5 -0
- package/dist/operations/services/railway-deploy.js +47 -75
- package/dist/operations/services/railway-source-policy.d.ts +6 -0
- package/dist/operations/services/railway-source-policy.js +52 -9
- package/dist/operations/services/repository-save-orchestrator.d.ts +2 -0
- package/dist/operations/services/repository-save-orchestrator.js +45 -14
- package/dist/operations-types.d.ts +3 -1
- package/dist/platform/contracts.d.ts +1 -0
- package/dist/platform/deploy-config.js +2 -1
- package/dist/reconcile/builtin-adapters.js +524 -680
- package/dist/reconcile/desired-state.js +5 -3
- package/dist/reconcile/engine.js +34 -28
- package/dist/reconcile/live-acceptance.js +2 -11
- package/dist/reconcile/providers/railway-iac.d.ts +148 -0
- package/dist/reconcile/providers/railway-iac.js +294 -18
- package/dist/scenes/runner.js +11 -11
- package/dist/scripts/build-dist.js +22 -0
- package/dist/workflow/operations.d.ts +12 -0
- package/dist/workflow/operations.js +265 -90
- package/dist/workflow/runs.d.ts +4 -0
- package/dist/workflow/runs.js +25 -0
- package/dist/workflow-support.d.ts +1 -1
- package/dist/workflow-support.js +3 -1
- package/package.json +1 -2
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
type RailwayCliInput = {
|
|
2
|
+
args: string[];
|
|
3
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
4
|
+
cwd?: string;
|
|
5
|
+
stdin?: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
};
|
|
8
|
+
export declare function runRailwayCliJson<T = unknown>({ args, env, cwd, stdin, timeoutMs }: RailwayCliInput): Promise<T>;
|
|
9
|
+
export declare function deleteRailwayVolumeWithCli(input: {
|
|
10
|
+
projectId: string;
|
|
11
|
+
environmentId: string;
|
|
12
|
+
volumeId: string;
|
|
13
|
+
env?: NodeJS.ProcessEnv;
|
|
14
|
+
}): Promise<unknown>;
|
|
15
|
+
export declare function detachRailwayVolumeWithCli(input: {
|
|
16
|
+
projectId: string;
|
|
17
|
+
environmentId: string;
|
|
18
|
+
serviceId: string;
|
|
19
|
+
volumeId: string;
|
|
20
|
+
env?: NodeJS.ProcessEnv;
|
|
21
|
+
}): Promise<unknown>;
|
|
22
|
+
export declare function attachRailwayVolumeWithCli(input: {
|
|
23
|
+
projectId: string;
|
|
24
|
+
environmentId: string;
|
|
25
|
+
serviceId: string;
|
|
26
|
+
volumeId: string;
|
|
27
|
+
env?: NodeJS.ProcessEnv;
|
|
28
|
+
}): Promise<unknown>;
|
|
29
|
+
export declare function updateRailwayVolumeWithCli(input: {
|
|
30
|
+
projectId: string;
|
|
31
|
+
environmentId: string;
|
|
32
|
+
serviceId: string;
|
|
33
|
+
volumeId: string;
|
|
34
|
+
name: string;
|
|
35
|
+
mountPath: string;
|
|
36
|
+
env?: NodeJS.ProcessEnv;
|
|
37
|
+
}): Promise<unknown>;
|
|
38
|
+
export declare function connectRailwayServiceSourceWithCli(input: {
|
|
39
|
+
projectId?: string | null;
|
|
40
|
+
environmentId: string;
|
|
41
|
+
serviceId: string;
|
|
42
|
+
repo?: string | null;
|
|
43
|
+
branch?: string | null;
|
|
44
|
+
image?: string | null;
|
|
45
|
+
env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
|
|
46
|
+
}): Promise<unknown>;
|
|
47
|
+
export {};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { resolveTreeseedToolCommand } from "../../managed-dependencies.js";
|
|
3
|
+
import { withTreeseedServiceCredentialEnv } from "../../service-credentials.js";
|
|
4
|
+
async function runRailwayCliJson({ args, env = process.env, cwd = process.cwd(), stdin, timeoutMs = 6e4 }) {
|
|
5
|
+
const effectiveEnv = withTreeseedServiceCredentialEnv(env);
|
|
6
|
+
const command = resolveTreeseedToolCommand("railway", { env: effectiveEnv });
|
|
7
|
+
if (!command) {
|
|
8
|
+
throw new Error("The managed Railway CLI is unavailable. Run `npx trsd install --json` before retrying reconciliation.");
|
|
9
|
+
}
|
|
10
|
+
return await new Promise((resolve, reject) => {
|
|
11
|
+
const detached = process.platform !== "win32";
|
|
12
|
+
const child = spawn(command.command, [...command.argsPrefix, ...args], {
|
|
13
|
+
cwd,
|
|
14
|
+
env: effectiveEnv,
|
|
15
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
16
|
+
detached
|
|
17
|
+
});
|
|
18
|
+
let stdout = "";
|
|
19
|
+
let stderr = "";
|
|
20
|
+
let timedOut = false;
|
|
21
|
+
let settled = false;
|
|
22
|
+
let forceKill = null;
|
|
23
|
+
const killProcessTree = (signal) => {
|
|
24
|
+
if (child.pid && detached) {
|
|
25
|
+
try {
|
|
26
|
+
process.kill(-child.pid, signal);
|
|
27
|
+
return;
|
|
28
|
+
} catch {
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
child.kill(signal);
|
|
32
|
+
};
|
|
33
|
+
const timeout = setTimeout(() => {
|
|
34
|
+
timedOut = true;
|
|
35
|
+
killProcessTree("SIGTERM");
|
|
36
|
+
forceKill = setTimeout(() => killProcessTree("SIGKILL"), 2e3);
|
|
37
|
+
}, timeoutMs);
|
|
38
|
+
const finish = (callback) => {
|
|
39
|
+
if (settled) return;
|
|
40
|
+
settled = true;
|
|
41
|
+
clearTimeout(timeout);
|
|
42
|
+
if (forceKill) clearTimeout(forceKill);
|
|
43
|
+
callback();
|
|
44
|
+
};
|
|
45
|
+
child.stdout.setEncoding("utf8");
|
|
46
|
+
child.stderr.setEncoding("utf8");
|
|
47
|
+
child.stdout.on("data", (chunk) => {
|
|
48
|
+
stdout += chunk;
|
|
49
|
+
});
|
|
50
|
+
child.stderr.on("data", (chunk) => {
|
|
51
|
+
stderr += chunk;
|
|
52
|
+
});
|
|
53
|
+
child.on("error", (error) => finish(() => reject(error)));
|
|
54
|
+
child.on("close", (code) => {
|
|
55
|
+
finish(() => {
|
|
56
|
+
if (timedOut) {
|
|
57
|
+
reject(new Error(`Railway CLI timed out after ${timeoutMs}ms: railway ${args.join(" ")}`));
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (code !== 0) {
|
|
61
|
+
reject(new Error(`Railway CLI failed (${code ?? "signal"}): ${stderr.trim() || stdout.trim() || args[0]}`));
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
resolve(stdout.trim() ? JSON.parse(stdout) : {});
|
|
66
|
+
} catch (error) {
|
|
67
|
+
reject(new Error(`Railway CLI returned invalid JSON for ${args.join(" ")}: ${error instanceof Error ? error.message : String(error)}`));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
if (stdin !== void 0) child.stdin.end(stdin);
|
|
72
|
+
else child.stdin.end();
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
function volumeScope(projectId, environmentId, serviceId) {
|
|
76
|
+
return ["volume", "--project", projectId, "--environment", environmentId, ...serviceId ? ["--service", serviceId] : []];
|
|
77
|
+
}
|
|
78
|
+
async function deleteRailwayVolumeWithCli(input) {
|
|
79
|
+
return runRailwayCliJson({
|
|
80
|
+
args: [...volumeScope(input.projectId, input.environmentId), "delete", "--volume", input.volumeId, "--yes", "--json"],
|
|
81
|
+
env: input.env
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async function detachRailwayVolumeWithCli(input) {
|
|
85
|
+
return runRailwayCliJson({
|
|
86
|
+
args: [...volumeScope(input.projectId, input.environmentId, input.serviceId), "detach", "--volume", input.volumeId, "--yes", "--json"],
|
|
87
|
+
env: input.env
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
async function attachRailwayVolumeWithCli(input) {
|
|
91
|
+
return runRailwayCliJson({
|
|
92
|
+
args: [...volumeScope(input.projectId, input.environmentId, input.serviceId), "attach", "--volume", input.volumeId, "--yes", "--json"],
|
|
93
|
+
env: input.env
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
async function updateRailwayVolumeWithCli(input) {
|
|
97
|
+
return runRailwayCliJson({
|
|
98
|
+
args: [
|
|
99
|
+
...volumeScope(input.projectId, input.environmentId, input.serviceId),
|
|
100
|
+
"update",
|
|
101
|
+
"--volume",
|
|
102
|
+
input.volumeId,
|
|
103
|
+
"--name",
|
|
104
|
+
input.name,
|
|
105
|
+
"--mount-path",
|
|
106
|
+
input.mountPath,
|
|
107
|
+
"--json"
|
|
108
|
+
],
|
|
109
|
+
env: input.env
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
async function connectRailwayServiceSourceWithCli(input) {
|
|
113
|
+
const repo = String(input.repo ?? "").trim();
|
|
114
|
+
const image = String(input.image ?? "").trim();
|
|
115
|
+
if (Boolean(repo) === Boolean(image)) {
|
|
116
|
+
throw new Error("Railway service source connection requires exactly one GitHub repository or image reference.");
|
|
117
|
+
}
|
|
118
|
+
return runRailwayCliJson({
|
|
119
|
+
args: [
|
|
120
|
+
"service",
|
|
121
|
+
"source",
|
|
122
|
+
"connect",
|
|
123
|
+
...String(input.projectId ?? "").trim() ? ["--project", String(input.projectId).trim()] : [],
|
|
124
|
+
"--environment",
|
|
125
|
+
input.environmentId,
|
|
126
|
+
"--service",
|
|
127
|
+
input.serviceId,
|
|
128
|
+
...repo ? ["--repo", repo] : ["--image", image],
|
|
129
|
+
...repo && String(input.branch ?? "").trim() ? ["--branch", String(input.branch).trim()] : [],
|
|
130
|
+
"--json"
|
|
131
|
+
],
|
|
132
|
+
env: input.env
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
export {
|
|
136
|
+
attachRailwayVolumeWithCli,
|
|
137
|
+
connectRailwayServiceSourceWithCli,
|
|
138
|
+
deleteRailwayVolumeWithCli,
|
|
139
|
+
detachRailwayVolumeWithCli,
|
|
140
|
+
runRailwayCliJson,
|
|
141
|
+
updateRailwayVolumeWithCli
|
|
142
|
+
};
|
|
@@ -60,6 +60,11 @@ export declare function waitForRailwayManagedDeploymentsSettled(tenantRoot: any,
|
|
|
60
60
|
message: string;
|
|
61
61
|
}>;
|
|
62
62
|
export declare function configuredRailwayServices(tenantRoot: any, scope: any, envOverlay?: {}, options?: {}): unknown[];
|
|
63
|
+
export declare function obsoleteUnqualifiedRailwayResourceNames(services: ReturnType<typeof configuredRailwayServices>): string[];
|
|
64
|
+
export declare function railwayObsoleteAliasCleanupPolicy(scope: 'staging' | 'prod', services: ReturnType<typeof configuredRailwayServices>, liveProjectServiceNames?: Iterable<string>, activeEnvironmentServiceNames?: Iterable<string>): {
|
|
65
|
+
retainedResourceNames: string[];
|
|
66
|
+
allowedResourceDeletions: string[];
|
|
67
|
+
};
|
|
63
68
|
export declare function configuredRailwayScheduledJobs(tenantRoot: any, scope: any, { phase }?: {
|
|
64
69
|
phase?: string | undefined;
|
|
65
70
|
}): any[];
|
|
@@ -6,7 +6,7 @@ import { resolveTreeseedMachineEnvironmentValues } from "./config-runtime.js";
|
|
|
6
6
|
import { createPersistentDeployTarget, resolveTreeseedResourceIdentity } from "./deploy.js";
|
|
7
7
|
import { classifyTreeseedGitMode, runTreeseedGitText } from "./git-runner.js";
|
|
8
8
|
import { discoverTreeseedApplications } from "../../hosting/apps.js";
|
|
9
|
-
import { apiRailwayDefaultDockerfilePath, apiRailwayDefaultSourceRepo, assertApiRailwaySourcePolicy, isApiRailwaySourcePolicyService } from "./railway-source-policy.js";
|
|
9
|
+
import { apiRailwayDefaultDockerfilePath, apiRailwayDefaultSourceRepo, assertApiRailwaySourcePolicy, isApiRailwaySourcePolicyService, railwayEnvironmentQualifiedServiceName, railwayTreeDxServiceName } from "./railway-source-policy.js";
|
|
10
10
|
import { runPrefixedCommand, sleep } from "./bootstrap-runner.js";
|
|
11
11
|
import {
|
|
12
12
|
ensureRailwayEnvironment,
|
|
@@ -384,74 +384,6 @@ function isRailwayScheduleCapabilityError(error) {
|
|
|
384
384
|
const message = error instanceof Error ? error.message : String(error ?? "");
|
|
385
385
|
return /cronTriggers|cronTriggerCreate|cronTriggerUpdate/iu.test(message);
|
|
386
386
|
}
|
|
387
|
-
function defaultRailwayScheduleQueries() {
|
|
388
|
-
return {
|
|
389
|
-
listQuery: envValue("TREESEED_RAILWAY_SCHEDULE_LIST_QUERY") || `
|
|
390
|
-
query TreeseedScheduleList($serviceId: String!, $environmentId: String!, $projectId: String) {
|
|
391
|
-
service(id: $serviceId) {
|
|
392
|
-
id
|
|
393
|
-
name
|
|
394
|
-
cronTriggers {
|
|
395
|
-
edges {
|
|
396
|
-
node {
|
|
397
|
-
id
|
|
398
|
-
name
|
|
399
|
-
schedule
|
|
400
|
-
command
|
|
401
|
-
enabled
|
|
402
|
-
service { id name }
|
|
403
|
-
environment { id name }
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
}
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
`.trim(),
|
|
410
|
-
createMutation: envValue("TREESEED_RAILWAY_SCHEDULE_CREATE_MUTATION") || `
|
|
411
|
-
mutation TreeseedScheduleCreate($serviceId: String!, $environmentId: String!, $name: String!, $schedule: String!, $command: String!, $enabled: Boolean!) {
|
|
412
|
-
cronTriggerCreate(
|
|
413
|
-
input: {
|
|
414
|
-
serviceId: $serviceId
|
|
415
|
-
environmentId: $environmentId
|
|
416
|
-
name: $name
|
|
417
|
-
schedule: $schedule
|
|
418
|
-
command: $command
|
|
419
|
-
enabled: $enabled
|
|
420
|
-
}
|
|
421
|
-
) {
|
|
422
|
-
id
|
|
423
|
-
name
|
|
424
|
-
schedule
|
|
425
|
-
command
|
|
426
|
-
enabled
|
|
427
|
-
service { id name }
|
|
428
|
-
environment { id name }
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
`.trim(),
|
|
432
|
-
updateMutation: envValue("TREESEED_RAILWAY_SCHEDULE_UPDATE_MUTATION") || `
|
|
433
|
-
mutation TreeseedScheduleUpdate($id: String!, $name: String!, $schedule: String!, $command: String!, $enabled: Boolean!) {
|
|
434
|
-
cronTriggerUpdate(
|
|
435
|
-
id: $id
|
|
436
|
-
input: {
|
|
437
|
-
name: $name
|
|
438
|
-
schedule: $schedule
|
|
439
|
-
command: $command
|
|
440
|
-
enabled: $enabled
|
|
441
|
-
}
|
|
442
|
-
) {
|
|
443
|
-
id
|
|
444
|
-
name
|
|
445
|
-
schedule
|
|
446
|
-
command
|
|
447
|
-
enabled
|
|
448
|
-
service { id name }
|
|
449
|
-
environment { id name }
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
`.trim()
|
|
453
|
-
};
|
|
454
|
-
}
|
|
455
387
|
async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
|
|
456
388
|
services = configuredRailwayServices(tenantRoot, scope),
|
|
457
389
|
env = process.env,
|
|
@@ -686,7 +618,7 @@ function configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, app
|
|
|
686
618
|
const publicBaseUrl = service.environments?.[normalizedScope]?.baseUrl ?? service.publicBaseUrl ?? (serviceKey === "api" ? configuredApiPublicBaseUrl(deployConfig, normalizedScope) : null);
|
|
687
619
|
const environmentConfig = service.environments?.[normalizedScope];
|
|
688
620
|
const baseServiceName = service.railway?.serviceName ?? (serviceKey === "workerRunner" ? deriveRailwayWorkerRunnerServiceName(identity.deploymentKey) : `${identity.deploymentKey}-${railwayServiceNameSuffix(serviceKey)}`);
|
|
689
|
-
const configuredServiceName = environmentConfig?.
|
|
621
|
+
const configuredServiceName = typeof environmentConfig?.serviceName === "string" && environmentConfig.serviceName.trim() ? environmentConfig.serviceName.trim() : isApiRailwaySourcePolicyService({ key: serviceKey, serviceName: baseServiceName }) ? railwayEnvironmentQualifiedServiceName(baseServiceName, normalizedScope) : baseServiceName;
|
|
690
622
|
const configuredRunnerPool = service.railway?.runnerPool && typeof service.railway.runnerPool === "object" ? service.railway.runnerPool : null;
|
|
691
623
|
const runnerPool = serviceKey === "operationsRunner" || serviceKey === "capacityProviderRunner" ? {
|
|
692
624
|
bootstrapCount: Math.max(1, Number.parseInt(String(configuredRunnerPool?.bootstrapCount ?? (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)), 10) || (serviceKey === "capacityProviderRunner" ? 1 : OPERATIONS_RUNNER_BOOTSTRAP_COUNT)),
|
|
@@ -797,9 +729,10 @@ function configuredPublicTreeDxRailwayServices({ tenantRoot, scope, deployConfig
|
|
|
797
729
|
const baseImageRef = envValue("TREESEED_PUBLIC_TREEDX_IMAGE_REF", imageRefEnv) || "treeseed/treedx";
|
|
798
730
|
return Array.from({ length: bootstrapCount }, (_, offset) => {
|
|
799
731
|
const index = offset + 1;
|
|
800
|
-
const
|
|
732
|
+
const baseServiceName = `${PUBLIC_TREEDX_NODE_SERVICE_KEY_PREFIX}${String(index).padStart(2, "0")}`;
|
|
733
|
+
const serviceName = railwayTreeDxServiceName(index, scope);
|
|
801
734
|
const service = {
|
|
802
|
-
key:
|
|
735
|
+
key: baseServiceName,
|
|
803
736
|
serviceName,
|
|
804
737
|
sourceMode,
|
|
805
738
|
sourceRepo: sourceMode === "git" ? repository : null,
|
|
@@ -985,6 +918,42 @@ function configuredRailwayServices(tenantRoot, scope, envOverlay = {}, options =
|
|
|
985
918
|
));
|
|
986
919
|
return [...direct, ...nested];
|
|
987
920
|
}
|
|
921
|
+
function obsoleteUnqualifiedRailwayResourceNames(services) {
|
|
922
|
+
const aliases = /* @__PURE__ */ new Set();
|
|
923
|
+
for (const service of services) {
|
|
924
|
+
const alias = service.serviceName.replace(/-(?:staging|production)(?=-\d+$|$)/u, "");
|
|
925
|
+
if (!alias || alias === service.serviceName) continue;
|
|
926
|
+
aliases.add(alias);
|
|
927
|
+
if (service.volumeMountPath) aliases.add(`${alias}-volume`);
|
|
928
|
+
const index = /-(\d+)$/u.exec(service.serviceName)?.[1] ?? "01";
|
|
929
|
+
const environmentSuffix = service.railwayEnvironment === "production" ? "production" : "staging";
|
|
930
|
+
const formerNames = service.key === "operationsRunner" ? [
|
|
931
|
+
`treeseed-api-operations-runner-${index}`,
|
|
932
|
+
`treeseed-api-operations-runner-${environmentSuffix}-${index}`
|
|
933
|
+
] : service.key.startsWith("public-treedx-node-") ? [
|
|
934
|
+
`public-treedx-node-${index}`,
|
|
935
|
+
`public-treedx-node-${environmentSuffix}-${index}`
|
|
936
|
+
] : [];
|
|
937
|
+
for (const formerName of formerNames) {
|
|
938
|
+
if (formerName === service.serviceName) continue;
|
|
939
|
+
aliases.add(formerName);
|
|
940
|
+
if (service.volumeMountPath) aliases.add(`${formerName}-volume`);
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
return [...aliases];
|
|
944
|
+
}
|
|
945
|
+
function railwayObsoleteAliasCleanupPolicy(scope, services, liveProjectServiceNames = [], activeEnvironmentServiceNames = []) {
|
|
946
|
+
const aliases = obsoleteUnqualifiedRailwayResourceNames(services);
|
|
947
|
+
const liveNames = new Set(liveProjectServiceNames);
|
|
948
|
+
void scope;
|
|
949
|
+
void activeEnvironmentServiceNames;
|
|
950
|
+
const qualifiedServices = services.filter((service) => service.serviceName !== service.serviceName.replace(/-(?:staging|production)(?=-\d+$|$)/u, "")).map((service) => service.serviceName);
|
|
951
|
+
const qualifiedResourcesExist = aliases.length > 0 && qualifiedServices.every((name) => liveNames.has(name));
|
|
952
|
+
return {
|
|
953
|
+
retainedResourceNames: qualifiedResourcesExist ? [] : aliases,
|
|
954
|
+
allowedResourceDeletions: qualifiedResourcesExist ? aliases : []
|
|
955
|
+
};
|
|
956
|
+
}
|
|
988
957
|
function configuredRailwayScheduledJobs(tenantRoot, scope, { phase = "deploy" } = {}) {
|
|
989
958
|
if (!shouldManageRailwaySchedules(scope, phase)) {
|
|
990
959
|
return [];
|
|
@@ -1709,7 +1678,7 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1709
1678
|
return {
|
|
1710
1679
|
service: service.key,
|
|
1711
1680
|
status: "planned",
|
|
1712
|
-
command: "railway-
|
|
1681
|
+
command: "railway-cli service redeploy",
|
|
1713
1682
|
cwd: service.rootDir,
|
|
1714
1683
|
publicBaseUrl: service.publicBaseUrl,
|
|
1715
1684
|
timings,
|
|
@@ -1771,9 +1740,10 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1771
1740
|
}
|
|
1772
1741
|
}
|
|
1773
1742
|
if (deployTransport !== "cli-fallback") {
|
|
1774
|
-
writePhase("deploy", `Deploying Railway service ${cliDeployService.serviceName ?? cliDeployService.serviceId ?? cliDeployService.key} through the Railway
|
|
1743
|
+
writePhase("deploy", `Deploying Railway service ${cliDeployService.serviceName ?? cliDeployService.serviceId ?? cliDeployService.key} through the managed Railway CLI.`);
|
|
1775
1744
|
const apiDeploy = await timedRailwayPhase(timings, "railway:api-deploy", () => withRailwayPhaseTimeout(
|
|
1776
1745
|
() => deployRailwayServiceInstance({
|
|
1746
|
+
projectId: cliDeployService.projectId,
|
|
1777
1747
|
serviceId: cliDeployService.serviceId,
|
|
1778
1748
|
environmentId: cliDeployService.environmentId,
|
|
1779
1749
|
env: commandEnv,
|
|
@@ -1785,7 +1755,7 @@ async function deployRailwayService(tenantRoot, service, {
|
|
|
1785
1755
|
return {
|
|
1786
1756
|
service: deployService.key,
|
|
1787
1757
|
status: "deployed",
|
|
1788
|
-
command: "railway-
|
|
1758
|
+
command: "railway-cli service redeploy",
|
|
1789
1759
|
cwd: deployService.rootDir,
|
|
1790
1760
|
publicBaseUrl: deployService.publicBaseUrl,
|
|
1791
1761
|
timings,
|
|
@@ -1823,7 +1793,9 @@ export {
|
|
|
1823
1793
|
findStaleTreeseedOperationsRunnerResources,
|
|
1824
1794
|
isTreeseedOperationsRunnerResourceName,
|
|
1825
1795
|
isUsableRailwayToken,
|
|
1796
|
+
obsoleteUnqualifiedRailwayResourceNames,
|
|
1826
1797
|
parseRailwayJsonOutput,
|
|
1798
|
+
railwayObsoleteAliasCleanupPolicy,
|
|
1827
1799
|
railwayServiceRuntimeStartCommand,
|
|
1828
1800
|
resolveRailwayAuthToken,
|
|
1829
1801
|
resolveRailwayDeploymentProfile,
|
|
@@ -12,6 +12,12 @@ export type TreeseedRailwaySourcePolicyService = {
|
|
|
12
12
|
buildCommand?: string | null;
|
|
13
13
|
startCommand?: string | null;
|
|
14
14
|
};
|
|
15
|
+
export type TreeseedRailwaySourceIdentity = TreeseedRailwaySourcePolicyService & {
|
|
16
|
+
environment?: string | null;
|
|
17
|
+
};
|
|
18
|
+
export declare function railwayEnvironmentQualifiedServiceName(serviceName: string, scope: TreeseedRailwaySourcePolicyScope | string): string;
|
|
19
|
+
export declare function railwayTreeDxServiceName(index: number, scope: string): string;
|
|
20
|
+
export declare function assertNoRailwaySourceIdentityCollisions(services: TreeseedRailwaySourceIdentity[]): void;
|
|
15
21
|
export declare function isApiRailwaySourcePolicyService(service: TreeseedRailwaySourcePolicyService): boolean;
|
|
16
22
|
export declare function isImmutableRailwayImageRef(value: unknown): boolean;
|
|
17
23
|
export declare function apiRailwayDefaultSourceRepo(service: TreeseedRailwaySourcePolicyService): "treeseed-ai/treedx" | "treeseed-ai/api" | null;
|
|
@@ -1,7 +1,45 @@
|
|
|
1
|
+
const SOURCE_DIVERGENT_ENVIRONMENT_SUFFIX = /-(?:staging|production)(?=-\d+$|$)/u;
|
|
2
|
+
function railwayEnvironmentQualifiedServiceName(serviceName, scope) {
|
|
3
|
+
const normalizedName = String(serviceName ?? "").trim();
|
|
4
|
+
const suffix = scope === "prod" || scope === "production" ? "production" : scope === "staging" ? "staging" : null;
|
|
5
|
+
if (!normalizedName || !suffix) return normalizedName;
|
|
6
|
+
const unqualifiedName = normalizedName.replace(SOURCE_DIVERGENT_ENVIRONMENT_SUFFIX, "");
|
|
7
|
+
const indexedName = /^(.*?)(-\d+)$/u.exec(unqualifiedName);
|
|
8
|
+
return indexedName ? `${indexedName[1]}-${suffix}${indexedName[2]}` : `${unqualifiedName}-${suffix}`;
|
|
9
|
+
}
|
|
10
|
+
function railwayTreeDxServiceName(index, scope) {
|
|
11
|
+
const normalizedIndex = String(Math.max(1, Math.trunc(index))).padStart(2, "0");
|
|
12
|
+
return railwayEnvironmentQualifiedServiceName(`treeseed-treedx-${normalizedIndex}`, scope);
|
|
13
|
+
}
|
|
14
|
+
function railwaySourceSignature(service) {
|
|
15
|
+
return JSON.stringify({
|
|
16
|
+
sourceMode: service.sourceMode ?? null,
|
|
17
|
+
sourceRepo: service.sourceRepo ?? null,
|
|
18
|
+
sourceBranch: service.sourceBranch ?? null,
|
|
19
|
+
sourceRootDirectory: service.sourceRootDirectory ?? null,
|
|
20
|
+
imageRef: service.imageRef ?? null,
|
|
21
|
+
dockerfilePath: service.dockerfilePath ?? null,
|
|
22
|
+
buildCommand: service.buildCommand ?? null
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
function assertNoRailwaySourceIdentityCollisions(services) {
|
|
26
|
+
const identities = /* @__PURE__ */ new Map();
|
|
27
|
+
for (const service of services) {
|
|
28
|
+
const serviceName = String(service.serviceName ?? "").trim();
|
|
29
|
+
if (!serviceName) continue;
|
|
30
|
+
const existing = identities.get(serviceName);
|
|
31
|
+
if (existing && railwaySourceSignature(existing) !== railwaySourceSignature(service)) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`${serviceName}: Railway service identity is shared by ${existing.environment ?? "one environment"} and ${service.environment ?? "another environment"} with different source/build configurations. Use environment-qualified service names.`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
identities.set(serviceName, service);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
1
39
|
function isApiRailwaySourcePolicyService(service) {
|
|
2
40
|
const key = String(service.key ?? "").trim();
|
|
3
41
|
const serviceName = String(service.serviceName ?? "").trim();
|
|
4
|
-
return key.startsWith("public-treedx-node-") || /^treeseed-api(?:-production)?$/u.test(serviceName) || /^treeseed-api-operations-runner(?:-production)?-\d+$/u.test(serviceName) || /^public-treedx-node(?:-production)?-\d+$/u.test(serviceName);
|
|
42
|
+
return key.startsWith("public-treedx-node-") || /^treeseed-api(?:-(?:staging|production))?$/u.test(serviceName) || /^treeseed-api-operations-runner(?:-(?:staging|production))?(?:-\d+)?$/u.test(serviceName) || /^treeseed-ops(?:-(?:staging|production))?-\d+$/u.test(serviceName) || /^public-treedx-node(?:-(?:staging|production))?-\d+$/u.test(serviceName) || /^treeseed-treedx(?:-(?:staging|production))?-\d+$/u.test(serviceName);
|
|
5
43
|
}
|
|
6
44
|
function isImmutableRailwayImageRef(value) {
|
|
7
45
|
const imageRef = typeof value === "string" ? value.trim() : "";
|
|
@@ -11,24 +49,26 @@ function isImmutableRailwayImageRef(value) {
|
|
|
11
49
|
}
|
|
12
50
|
function apiRailwayDefaultSourceRepo(service) {
|
|
13
51
|
const serviceName = String(service.serviceName ?? "").trim();
|
|
14
|
-
if (String(service.key ?? "") === "api" || String(service.key ?? "") === "operationsRunner" || /^treeseed-api(?:-production)?$/u.test(serviceName) || /^treeseed-api-operations-runner(?:-production)
|
|
15
|
-
if (/^public-treedx-node(?:-production)?-\d+$/u.test(serviceName) || String(service.key ?? "").startsWith("public-treedx-node-")) return "treeseed-ai/treedx";
|
|
52
|
+
if (String(service.key ?? "") === "api" || String(service.key ?? "") === "operationsRunner" || /^treeseed-api(?:-(?:staging|production))?$/u.test(serviceName) || /^treeseed-api-operations-runner(?:-(?:staging|production))?(?:-\d+)?$/u.test(serviceName)) return "treeseed-ai/api";
|
|
53
|
+
if (/^public-treedx-node(?:-(?:staging|production))?-\d+$/u.test(serviceName) || String(service.key ?? "").startsWith("public-treedx-node-")) return "treeseed-ai/treedx";
|
|
16
54
|
return null;
|
|
17
55
|
}
|
|
18
56
|
function apiRailwayDefaultDockerfilePath(service) {
|
|
19
57
|
const serviceName = String(service.serviceName ?? "").trim();
|
|
20
|
-
if (String(service.key ?? "") === "api" || /^treeseed-api(?:-production)?$/u.test(serviceName)) return "/Dockerfile.api";
|
|
21
|
-
if (String(service.key ?? "") === "operationsRunner" || /^treeseed-api-operations-runner(?:-production)
|
|
22
|
-
if (/^public-treedx-node(?:-production)?-\d+$/u.test(serviceName) || String(service.key ?? "").startsWith("public-treedx-node-")) return "/Dockerfile";
|
|
58
|
+
if (String(service.key ?? "") === "api" || /^treeseed-api(?:-(?:staging|production))?$/u.test(serviceName)) return "/Dockerfile.api";
|
|
59
|
+
if (String(service.key ?? "") === "operationsRunner" || /^treeseed-api-operations-runner(?:-(?:staging|production))?(?:-\d+)?$/u.test(serviceName)) return "/Dockerfile.operations-runner";
|
|
60
|
+
if (/^public-treedx-node(?:-(?:staging|production))?-\d+$/u.test(serviceName) || String(service.key ?? "").startsWith("public-treedx-node-")) return "/Dockerfile";
|
|
23
61
|
return null;
|
|
24
62
|
}
|
|
25
63
|
function assertApiRailwaySourcePolicy(scope, service) {
|
|
26
64
|
if (!isApiRailwaySourcePolicyService(service)) return;
|
|
27
65
|
const normalizedScope = scope === "prod" ? "prod" : scope === "staging" ? "staging" : "local";
|
|
28
66
|
const label = service.serviceName ?? service.key ?? "Railway service";
|
|
67
|
+
const serviceName = String(service.serviceName ?? "").trim();
|
|
68
|
+
const expectedServiceName = railwayEnvironmentQualifiedServiceName(serviceName, normalizedScope);
|
|
29
69
|
if (normalizedScope === "staging") {
|
|
30
70
|
const issues = [
|
|
31
|
-
/-
|
|
71
|
+
serviceName === expectedServiceName && /-staging(?:-|$)/u.test(serviceName) ? null : `serviceName must be ${expectedServiceName}`,
|
|
32
72
|
service.sourceMode === "git" ? null : "sourceMode must be git",
|
|
33
73
|
service.imageRef ? "imageRef must be empty" : null,
|
|
34
74
|
service.sourceRepo ? null : "sourceRepo must be set",
|
|
@@ -43,7 +83,7 @@ function assertApiRailwaySourcePolicy(scope, service) {
|
|
|
43
83
|
}
|
|
44
84
|
if (normalizedScope === "prod") {
|
|
45
85
|
const issues = [
|
|
46
|
-
/-production(?:-|$)/u.test(
|
|
86
|
+
serviceName === expectedServiceName && /-production(?:-|$)/u.test(serviceName) ? null : `serviceName must be ${expectedServiceName}`,
|
|
47
87
|
service.sourceMode === "image" ? null : "sourceMode must be image",
|
|
48
88
|
isImmutableRailwayImageRef(service.imageRef) ? null : "imageRef must be an immutable released image tag",
|
|
49
89
|
service.sourceRepo ? "sourceRepo must be empty" : null,
|
|
@@ -63,6 +103,9 @@ export {
|
|
|
63
103
|
apiRailwayDefaultDockerfilePath,
|
|
64
104
|
apiRailwayDefaultSourceRepo,
|
|
65
105
|
assertApiRailwaySourcePolicy,
|
|
106
|
+
assertNoRailwaySourceIdentityCollisions,
|
|
66
107
|
isApiRailwaySourcePolicyService,
|
|
67
|
-
isImmutableRailwayImageRef
|
|
108
|
+
isImmutableRailwayImageRef,
|
|
109
|
+
railwayEnvironmentQualifiedServiceName,
|
|
110
|
+
railwayTreeDxServiceName
|
|
68
111
|
};
|
|
@@ -174,6 +174,8 @@ export declare function discoverRepositorySaveNodes(root: string, gitRoot?: stri
|
|
|
174
174
|
stablePackageRelease?: boolean;
|
|
175
175
|
}): RepositorySaveNode[];
|
|
176
176
|
export declare function repositorySaveWaves(nodes: RepositorySaveNode[]): RepositorySaveNode[][];
|
|
177
|
+
export declare function validateStandaloneGitDependencyLockfile(node: RepositorySaveNode, options: Pick<RepositorySaveOptions, 'onProgress'>): boolean;
|
|
178
|
+
export declare function applyPackageVersion(node: RepositorySaveNode, version: string): boolean;
|
|
177
179
|
export declare function planRepositorySave(options: RepositorySaveOptions): RepositorySavePlan;
|
|
178
180
|
export declare function refreshAndValidateRootWorkspaceLockfileForSave(options: {
|
|
179
181
|
root: string;
|
|
@@ -779,6 +779,8 @@ function syncDirectGitDependencyLockfileEntries(node, options, references) {
|
|
|
779
779
|
function validateStandaloneGitDependencyLockfile(node, options) {
|
|
780
780
|
const lockfilePath = resolve(node.path, "package-lock.json");
|
|
781
781
|
const lockfileExists = existsSync(lockfilePath);
|
|
782
|
+
const previousLockfile = lockfileExists ? readFileSync(lockfilePath, "utf8") : null;
|
|
783
|
+
const isolatedRoot = mkdtempSync(resolve(tmpdir(), "treeseed-lockfile-"));
|
|
782
784
|
const validateArgs = [
|
|
783
785
|
"ci",
|
|
784
786
|
"--package-lock-only",
|
|
@@ -789,11 +791,16 @@ function validateStandaloneGitDependencyLockfile(node, options) {
|
|
|
789
791
|
];
|
|
790
792
|
try {
|
|
791
793
|
if (!lockfileExists) throw new Error("standalone lockfile missing");
|
|
792
|
-
|
|
794
|
+
copyFileSync(resolve(node.path, "package.json"), resolve(isolatedRoot, "package.json"));
|
|
795
|
+
copyFileSync(lockfilePath, resolve(isolatedRoot, "package-lock.json"));
|
|
796
|
+
runCapturedCommand(node, options, "lockfile", "npm", validateArgs, {
|
|
797
|
+
cwd: isolatedRoot,
|
|
798
|
+
timeoutMs: 5 * 6e4
|
|
799
|
+
});
|
|
793
800
|
} catch (validationError) {
|
|
794
|
-
const previousLockfile = lockfileExists ? readFileSync(lockfilePath, "utf8") : null;
|
|
795
|
-
const isolatedRoot = mkdtempSync(resolve(tmpdir(), "treeseed-lockfile-"));
|
|
796
801
|
try {
|
|
802
|
+
rmSync(isolatedRoot, { recursive: true, force: true });
|
|
803
|
+
mkdirSync(isolatedRoot, { recursive: true });
|
|
797
804
|
copyFileSync(resolve(node.path, "package.json"), resolve(isolatedRoot, "package.json"));
|
|
798
805
|
runCapturedCommand(node, options, "lockfile", "npm", [
|
|
799
806
|
"install",
|
|
@@ -814,9 +821,9 @@ function validateStandaloneGitDependencyLockfile(node, options) {
|
|
|
814
821
|
} catch (regenerationError) {
|
|
815
822
|
if (previousLockfile !== null) writeFileSync(lockfilePath, previousLockfile, "utf8");
|
|
816
823
|
throw regenerationError instanceof Error ? regenerationError : validationError;
|
|
817
|
-
} finally {
|
|
818
|
-
rmSync(isolatedRoot, { recursive: true, force: true });
|
|
819
824
|
}
|
|
825
|
+
} finally {
|
|
826
|
+
rmSync(isolatedRoot, { recursive: true, force: true });
|
|
820
827
|
}
|
|
821
828
|
emitProgress(options, node, "lockfile", "Validated the standalone lockfile against the committed package manifest.");
|
|
822
829
|
return true;
|
|
@@ -828,10 +835,30 @@ function planPackageVersion(node, options) {
|
|
|
828
835
|
}
|
|
829
836
|
function applyPackageVersion(node, version) {
|
|
830
837
|
if (!node.packageJson || !node.packageJsonPath) return false;
|
|
831
|
-
|
|
832
|
-
node.packageJson.version
|
|
833
|
-
|
|
834
|
-
|
|
838
|
+
let changed = false;
|
|
839
|
+
if (node.packageJson.version !== version) {
|
|
840
|
+
node.packageJson.version = version;
|
|
841
|
+
writeJson(node.packageJsonPath, node.packageJson);
|
|
842
|
+
changed = true;
|
|
843
|
+
}
|
|
844
|
+
const lockfilePath = resolve(node.path, "package-lock.json");
|
|
845
|
+
if (existsSync(lockfilePath)) {
|
|
846
|
+
const lockfile = readJson(lockfilePath);
|
|
847
|
+
const rootEntry = lockfile.packages && typeof lockfile.packages === "object" && !Array.isArray(lockfile.packages) ? lockfile.packages[""] : null;
|
|
848
|
+
const lockfileMatches = lockfile.version === version && rootEntry?.version === version && (typeof node.packageJson.name !== "string" || rootEntry?.name === node.packageJson.name);
|
|
849
|
+
if (lockfileMatches) return changed;
|
|
850
|
+
lockfile.version = version;
|
|
851
|
+
const packages = lockfile.packages && typeof lockfile.packages === "object" && !Array.isArray(lockfile.packages) ? lockfile.packages : {};
|
|
852
|
+
packages[""] = {
|
|
853
|
+
...packages[""] ?? {},
|
|
854
|
+
...typeof node.packageJson.name === "string" ? { name: node.packageJson.name } : {},
|
|
855
|
+
version
|
|
856
|
+
};
|
|
857
|
+
lockfile.packages = packages;
|
|
858
|
+
writeJson(lockfilePath, lockfile);
|
|
859
|
+
changed = true;
|
|
860
|
+
}
|
|
861
|
+
return changed;
|
|
835
862
|
}
|
|
836
863
|
function shouldSkipNetworkInstall() {
|
|
837
864
|
return process.env.TREESEED_SAVE_NPM_INSTALL_MODE !== "allow";
|
|
@@ -952,6 +979,10 @@ async function runNpmInstallWithRetry(node, options, gitDependencyRefreshSpecs =
|
|
|
952
979
|
let lastError = null;
|
|
953
980
|
const packageJson = node.packageJson ?? (existsSync(resolve(node.path, "package.json")) ? readJson(resolve(node.path, "package.json")) : null);
|
|
954
981
|
const rootWorkspaceInstall = node.path === options.root && Array.isArray(packageJson?.workspaces);
|
|
982
|
+
if (!rootWorkspaceInstall && node.branchMode !== "project-save") {
|
|
983
|
+
validateStandaloneGitDependencyLockfile(node, options);
|
|
984
|
+
return { status: "completed", attempts: 1, reason: "isolated-lockfile-validation" };
|
|
985
|
+
}
|
|
955
986
|
const installFlags = rootWorkspaceInstall ? ["--package-lock-only", "--ignore-scripts"] : node.branchMode === "project-save" ? ["--ignore-scripts"] : ["--package-lock-only", "--ignore-scripts"];
|
|
956
987
|
const args = rootWorkspaceInstall ? gitDependencyRefreshSpecs.length > 0 ? ["install", ...gitDependencyRefreshSpecs, ...installFlags, "--force"] : ["install", ...installFlags] : gitDependencyRefreshSpecs.length > 0 ? ["install", ...gitDependencyRefreshSpecs, ...installFlags, "--force", "--workspaces=false"] : ["install", ...installFlags, "--workspaces=false"];
|
|
957
988
|
for (let attempt = 1; attempt <= 5; attempt += 1) {
|
|
@@ -1795,13 +1826,11 @@ async function saveOneRepository(node, options, state) {
|
|
|
1795
1826
|
if (selection.reused) {
|
|
1796
1827
|
emitProgress(options, node, "version", `Reusing existing interrupted save version ${plannedVersion}.`);
|
|
1797
1828
|
} else {
|
|
1798
|
-
|
|
1829
|
+
emitProgress(options, node, "version", `Planned ${plannedVersion}.`);
|
|
1799
1830
|
}
|
|
1831
|
+
applyPackageVersion(node, plannedVersion);
|
|
1800
1832
|
node.plannedVersion = plannedVersion;
|
|
1801
1833
|
report.version = plannedVersion;
|
|
1802
|
-
if (!selection.reused) {
|
|
1803
|
-
emitProgress(options, node, "version", `Planned ${plannedVersion}.`);
|
|
1804
|
-
}
|
|
1805
1834
|
const reference = finalizePackageReference(node, plannedVersion, options);
|
|
1806
1835
|
node.plannedTag = reference.tagName;
|
|
1807
1836
|
report.tagName = reference.tagName;
|
|
@@ -2068,6 +2097,7 @@ function repositorySaveErrorDetails(error) {
|
|
|
2068
2097
|
};
|
|
2069
2098
|
}
|
|
2070
2099
|
export {
|
|
2100
|
+
applyPackageVersion,
|
|
2071
2101
|
discoverRepositorySaveNodes,
|
|
2072
2102
|
nextDevVersion,
|
|
2073
2103
|
planRepositorySave,
|
|
@@ -2075,5 +2105,6 @@ export {
|
|
|
2075
2105
|
repositorySaveErrorDetails,
|
|
2076
2106
|
repositorySaveWaves,
|
|
2077
2107
|
runRepositorySaveOrchestrator,
|
|
2078
|
-
runStreamingCommand
|
|
2108
|
+
runStreamingCommand,
|
|
2109
|
+
validateStandaloneGitDependencyLockfile
|
|
2079
2110
|
};
|
|
@@ -36,7 +36,9 @@ export type TreeseedOperationConfirm = (message: string, expected: string) => Pr
|
|
|
36
36
|
export type TreeseedOperationSpawn = (command: string, args: string[], options: {
|
|
37
37
|
cwd: string;
|
|
38
38
|
env: NodeJS.ProcessEnv;
|
|
39
|
-
stdio?: 'inherit';
|
|
39
|
+
stdio?: 'inherit' | 'pipe';
|
|
40
|
+
timeout?: number;
|
|
41
|
+
killSignal?: NodeJS.Signals;
|
|
40
42
|
}) => {
|
|
41
43
|
status?: number | null;
|
|
42
44
|
};
|
|
@@ -213,6 +213,7 @@ export interface TreeseedManagedServiceEnvironmentConfig {
|
|
|
213
213
|
domain?: string;
|
|
214
214
|
railwayEnvironment?: string;
|
|
215
215
|
railwayProjectName?: string;
|
|
216
|
+
serviceName?: string;
|
|
216
217
|
railwayServiceName?: string;
|
|
217
218
|
}
|
|
218
219
|
export type TreeseedLocalRuntimeMode = 'auto' | 'provider' | 'local';
|
|
@@ -334,7 +334,8 @@ function parseServiceEnvironmentConfig(value, label) {
|
|
|
334
334
|
domain: optionalString(record.domain),
|
|
335
335
|
railwayEnvironment: optionalString(record.railwayEnvironment),
|
|
336
336
|
railwayProjectName: optionalString(record.railwayProjectName),
|
|
337
|
-
|
|
337
|
+
serviceName: optionalString(record.serviceName ?? record.railwayServiceName),
|
|
338
|
+
railwayServiceName: optionalString(record.railwayServiceName ?? record.serviceName)
|
|
338
339
|
};
|
|
339
340
|
}
|
|
340
341
|
function parseLocalRuntimeConfig(value, label) {
|