@treeseed/sdk 0.12.54 → 0.12.56
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/live-hosted-service-checks.js +11 -2
- package/dist/operations/services/railway-deploy.d.ts +1 -1
- package/dist/operations/services/railway-deploy.js +19 -8
- package/dist/reconcile/builtin-adapters.js +96 -2
- package/dist/reconcile/providers/railway-iac.d.ts +4 -1
- package/dist/reconcile/providers/railway-iac.js +20 -1
- package/package.json +1 -1
|
@@ -12,7 +12,11 @@ import {
|
|
|
12
12
|
normalizeRailwayEnvironmentName,
|
|
13
13
|
resolveRailwayWorkspaceContext
|
|
14
14
|
} from "./railway-api.js";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
configuredRailwayServices,
|
|
17
|
+
findStaleTreeseedOperationsRunnerResources,
|
|
18
|
+
isTreeseedOperationsRunnerResourceName
|
|
19
|
+
} from "./railway-deploy.js";
|
|
16
20
|
import { discoverTreeseedApplications } from "../../hosting/apps.js";
|
|
17
21
|
import {
|
|
18
22
|
collectTreeseedHostedServiceChecks
|
|
@@ -235,6 +239,8 @@ async function collectRailwayObservations(options) {
|
|
|
235
239
|
const workspace = await resolveRailwayWorkspaceContext({ env: options.env, fetchImpl: options.fetchImpl });
|
|
236
240
|
const projects = await listRailwayProjects({ workspaceId: workspace.id, env: options.env, fetchImpl: options.fetchImpl });
|
|
237
241
|
const configuredServices = configuredRailwayServices(options.tenantRoot, options.target, options.env).filter((entry) => serviceMatchesAppSelection(entry, options.tenantRoot, options.appId, applications)).filter((entry) => serviceIsSelected(selectedServiceKeys, entry.key));
|
|
242
|
+
const siblingTarget = options.target === "staging" ? "prod" : options.target === "prod" ? "staging" : null;
|
|
243
|
+
const configuredSiblingServices = siblingTarget ? configuredRailwayServices(options.tenantRoot, siblingTarget, options.env, { identityOnly: true }).filter((entry) => entry.enabled !== false).filter((entry) => entry.key === "operationsRunner") : [];
|
|
238
244
|
if (selectedServiceKeys.size === 0 || selectedServiceKeys.has("api") || selectedServiceKeys.has("operationsRunner")) {
|
|
239
245
|
for (const descriptor of treeseedDatabaseDescriptors(options.tenantRoot, options)) {
|
|
240
246
|
await verifyRailwayPostgresTopology({ descriptor, configuredServices, projects, options, issues });
|
|
@@ -278,7 +284,10 @@ async function collectRailwayObservations(options) {
|
|
|
278
284
|
const runnerScope = `${project.id}:${environment.id}`;
|
|
279
285
|
if (service.key === "operationsRunner" && !inspectedRunnerScopes.has(runnerScope)) {
|
|
280
286
|
inspectedRunnerScopes.add(runnerScope);
|
|
281
|
-
const desiredRunnerNames =
|
|
287
|
+
const desiredRunnerNames = /* @__PURE__ */ new Set([
|
|
288
|
+
...configuredServices.filter((entry) => entry.key === "operationsRunner").filter((entry) => entry.projectId ? entry.projectId === project.id : entry.projectName === project.name).filter((entry) => normalizeRailwayEnvironmentName(entry.railwayEnvironment) === normalizeRailwayEnvironmentName(environment.name)).map((entry) => entry.serviceName).filter(Boolean),
|
|
289
|
+
...configuredSiblingServices.filter((entry) => entry.projectId ? entry.projectId === project.id : entry.projectName === project.name).map((entry) => entry.serviceName).filter((name) => Boolean(name) && isTreeseedOperationsRunnerResourceName(name))
|
|
290
|
+
]);
|
|
282
291
|
const desiredRunnerServiceIds = new Set(services.filter((entry) => desiredRunnerNames.has(entry.name)).map((entry) => entry.id));
|
|
283
292
|
for (const staleService of findStaleTreeseedOperationsRunnerResources(services, desiredRunnerNames)) {
|
|
284
293
|
issues.push(`${staleService.name}: stale operations runner Railway service remains in project ${project.name}.`);
|
|
@@ -59,7 +59,7 @@ export declare function waitForRailwayManagedDeploymentsSettled(tenantRoot: any,
|
|
|
59
59
|
};
|
|
60
60
|
message: string;
|
|
61
61
|
}>;
|
|
62
|
-
export declare function configuredRailwayServices(tenantRoot: any, scope: any, envOverlay?: {}): unknown[];
|
|
62
|
+
export declare function configuredRailwayServices(tenantRoot: any, scope: any, envOverlay?: {}, options?: {}): unknown[];
|
|
63
63
|
export declare function configuredRailwayScheduledJobs(tenantRoot: any, scope: any, { phase }?: {
|
|
64
64
|
phase?: string | undefined;
|
|
65
65
|
}): any[];
|
|
@@ -641,8 +641,9 @@ query TreeseedRailwayDeploymentStatus($projectId: String!) {
|
|
|
641
641
|
});
|
|
642
642
|
return payload.data?.project ?? null;
|
|
643
643
|
}
|
|
644
|
-
function configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, application = null, machineConfigRoot = tenantRoot, envOverlay = {}) {
|
|
644
|
+
function configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, application = null, machineConfigRoot = tenantRoot, envOverlay = {}, options = {}) {
|
|
645
645
|
const normalizedScope = normalizeScope(scope);
|
|
646
|
+
const identityOnly = options.identityOnly === true;
|
|
646
647
|
const imageRefKeys = [
|
|
647
648
|
"TREESEED_API_IMAGE_REF",
|
|
648
649
|
"TREESEED_OPERATIONS_RUNNER_IMAGE_REF",
|
|
@@ -702,7 +703,13 @@ function configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, app
|
|
|
702
703
|
const configuredImageRefEnv = service.railway?.imageRefEnv ?? railwayImageRefEnvForService(serviceKey);
|
|
703
704
|
const canUseImageRefEnv = normalizedScope === "prod" || serviceKey === "capacityProviderManager" || serviceKey === "capacityProviderRunner";
|
|
704
705
|
const imageRef = service.railway?.imageRef ?? (canUseImageRefEnv && configuredImageRefEnv ? envValue(configuredImageRefEnv, imageRefEnv) || null : null) ?? defaultRailwayImageRef(serviceKey, normalizedScope, imageRefEnv);
|
|
705
|
-
const sourcePolicy =
|
|
706
|
+
const sourcePolicy = identityOnly ? {
|
|
707
|
+
sourceMode: normalizedScope === "prod" ? "image" : "git",
|
|
708
|
+
sourceRepo: null,
|
|
709
|
+
sourceBranch: null,
|
|
710
|
+
sourceCommit: null,
|
|
711
|
+
sourceRootDirectory: null
|
|
712
|
+
} : resolveRailwayServiceSourcePolicy({
|
|
706
713
|
tenantRoot,
|
|
707
714
|
scope: normalizedScope,
|
|
708
715
|
serviceKey,
|
|
@@ -760,11 +767,12 @@ function configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, app
|
|
|
760
767
|
hostingKind,
|
|
761
768
|
application,
|
|
762
769
|
imageRefEnv,
|
|
763
|
-
workspaceRoot: machineConfigRoot
|
|
770
|
+
workspaceRoot: machineConfigRoot,
|
|
771
|
+
identityOnly
|
|
764
772
|
})
|
|
765
773
|
];
|
|
766
774
|
}
|
|
767
|
-
function configuredPublicTreeDxRailwayServices({ tenantRoot, scope, deployConfig, identity, hostingKind, application, imageRefEnv, workspaceRoot }) {
|
|
775
|
+
function configuredPublicTreeDxRailwayServices({ tenantRoot, scope, deployConfig, identity, hostingKind, application, imageRefEnv, workspaceRoot, identityOnly = false }) {
|
|
768
776
|
if (deployConfig.hosting?.kind !== "treeseed_control_plane") {
|
|
769
777
|
return [];
|
|
770
778
|
}
|
|
@@ -803,7 +811,9 @@ function configuredPublicTreeDxRailwayServices({ tenantRoot, scope, deployConfig
|
|
|
803
811
|
buildCommand: sourceMode === "git" ? railway.buildCommand ?? null : null,
|
|
804
812
|
startCommand: sourceMode === "git" ? railway.startCommand ?? null : null
|
|
805
813
|
};
|
|
806
|
-
|
|
814
|
+
if (!identityOnly) {
|
|
815
|
+
assertApiRailwaySourcePolicy(scope, service);
|
|
816
|
+
}
|
|
807
817
|
return {
|
|
808
818
|
key: service.key,
|
|
809
819
|
instanceKey: serviceName,
|
|
@@ -957,9 +967,9 @@ function resolveRailwayCapacityProviderRoot(tenantRoot, service) {
|
|
|
957
967
|
);
|
|
958
968
|
return found ?? resolve(tenantRoot, "packages", "agent");
|
|
959
969
|
}
|
|
960
|
-
function configuredRailwayServices(tenantRoot, scope, envOverlay = {}) {
|
|
970
|
+
function configuredRailwayServices(tenantRoot, scope, envOverlay = {}, options = {}) {
|
|
961
971
|
const deployConfig = loadCliDeployConfig(tenantRoot);
|
|
962
|
-
const direct = configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, null, tenantRoot, envOverlay);
|
|
972
|
+
const direct = configuredRailwayServicesForConfig(tenantRoot, scope, deployConfig, null, tenantRoot, envOverlay, options);
|
|
963
973
|
const nested = discoverTreeseedApplications(tenantRoot).filter((application) => application.root !== resolve(tenantRoot)).flatMap((application) => configuredRailwayServicesForConfig(
|
|
964
974
|
application.root,
|
|
965
975
|
scope,
|
|
@@ -970,7 +980,8 @@ function configuredRailwayServices(tenantRoot, scope, envOverlay = {}) {
|
|
|
970
980
|
relativeRoot: application.relativeRoot
|
|
971
981
|
},
|
|
972
982
|
tenantRoot,
|
|
973
|
-
envOverlay
|
|
983
|
+
envOverlay,
|
|
984
|
+
options
|
|
974
985
|
));
|
|
975
986
|
return [...direct, ...nested];
|
|
976
987
|
}
|
|
@@ -42,6 +42,7 @@ import {
|
|
|
42
42
|
import {
|
|
43
43
|
configuredRailwayServices,
|
|
44
44
|
findStaleTreeseedOperationsRunnerResources,
|
|
45
|
+
isTreeseedOperationsRunnerResourceName,
|
|
45
46
|
validateRailwayDeployPrerequisites
|
|
46
47
|
} from "../operations/services/railway-deploy.js";
|
|
47
48
|
import { shouldExposeManagedHostRuntimeSecret } from "../operations/services/managed-host-security.js";
|
|
@@ -54,6 +55,7 @@ import {
|
|
|
54
55
|
ensureRailwayServiceVolume,
|
|
55
56
|
ensureRailwayPostgresService,
|
|
56
57
|
deleteRailwayService,
|
|
58
|
+
deleteRailwayCustomDomain,
|
|
57
59
|
deleteRailwayVolume,
|
|
58
60
|
deployRailwayServiceInstance,
|
|
59
61
|
inspectRailwayServiceDeploymentHealth,
|
|
@@ -2372,6 +2374,48 @@ async function ensureRailwayCustomDomain(input, service, domain, env, identifier
|
|
|
2372
2374
|
if (matched) {
|
|
2373
2375
|
return matched;
|
|
2374
2376
|
}
|
|
2377
|
+
const environmentServices = await listRailwayEnvironmentServices({
|
|
2378
|
+
environmentId: identifiers.environmentId,
|
|
2379
|
+
env
|
|
2380
|
+
});
|
|
2381
|
+
for (const candidate of environmentServices) {
|
|
2382
|
+
const candidateServiceId = typeof candidate.id === "string" ? candidate.id.trim() : "";
|
|
2383
|
+
if (!candidateServiceId || candidateServiceId === identifiers.serviceId) {
|
|
2384
|
+
continue;
|
|
2385
|
+
}
|
|
2386
|
+
const candidateDomains = await listRailwayCustomDomains({
|
|
2387
|
+
projectId: identifiers.projectId,
|
|
2388
|
+
environmentId: identifiers.environmentId,
|
|
2389
|
+
serviceId: candidateServiceId,
|
|
2390
|
+
env
|
|
2391
|
+
});
|
|
2392
|
+
const staleAttachment = candidateDomains.find((entry) => entry.domain === domain) ?? null;
|
|
2393
|
+
if (!staleAttachment) {
|
|
2394
|
+
continue;
|
|
2395
|
+
}
|
|
2396
|
+
if (!staleAttachment.id) {
|
|
2397
|
+
throw new Error(`Railway custom domain ${domain} is attached to service ${candidateServiceId}, but Railway did not return its domain id for exact-state reattachment.`);
|
|
2398
|
+
}
|
|
2399
|
+
await deleteRailwayCustomDomain({ domainId: staleAttachment.id, env });
|
|
2400
|
+
let detached = false;
|
|
2401
|
+
for (let attempt = 0; attempt < 12; attempt += 1) {
|
|
2402
|
+
const remaining = await listRailwayCustomDomains({
|
|
2403
|
+
projectId: identifiers.projectId,
|
|
2404
|
+
environmentId: identifiers.environmentId,
|
|
2405
|
+
serviceId: candidateServiceId,
|
|
2406
|
+
env
|
|
2407
|
+
});
|
|
2408
|
+
if (!remaining.some((entry) => entry.domain === domain)) {
|
|
2409
|
+
detached = true;
|
|
2410
|
+
break;
|
|
2411
|
+
}
|
|
2412
|
+
await new Promise((resolve2) => setTimeout(resolve2, 1e3));
|
|
2413
|
+
}
|
|
2414
|
+
if (!detached) {
|
|
2415
|
+
throw new Error(`Railway custom domain ${domain} remained attached to service ${candidateServiceId} after deletion.`);
|
|
2416
|
+
}
|
|
2417
|
+
break;
|
|
2418
|
+
}
|
|
2375
2419
|
const ensured = await ensureRailwayCustomDomainViaApi({
|
|
2376
2420
|
projectId: identifiers.projectId,
|
|
2377
2421
|
environmentId: identifiers.environmentId,
|
|
@@ -3657,6 +3701,24 @@ function configuredRailwayProjectSyncGroups(input, scope, serviceKeys) {
|
|
|
3657
3701
|
}
|
|
3658
3702
|
return [...grouped.values()];
|
|
3659
3703
|
}
|
|
3704
|
+
function configuredRailwaySiblingResourceNames(input, scope, projectName) {
|
|
3705
|
+
const siblingScope = scope === "prod" ? "staging" : scope === "staging" ? "prod" : null;
|
|
3706
|
+
if (!siblingScope) return [];
|
|
3707
|
+
const siblingServices = configuredRailwayServices(
|
|
3708
|
+
input.context.tenantRoot,
|
|
3709
|
+
siblingScope,
|
|
3710
|
+
resolveReconcileEnvironmentValues(input, siblingScope),
|
|
3711
|
+
{ identityOnly: true }
|
|
3712
|
+
).filter((service) => service.enabled !== false).filter((service) => !isRailwayCapacityProviderService(service)).filter((service) => service.projectName === projectName);
|
|
3713
|
+
const database = configuredRailwayIacDatabase(input, siblingServices);
|
|
3714
|
+
return [.../* @__PURE__ */ new Set([
|
|
3715
|
+
...siblingServices.flatMap((service) => [
|
|
3716
|
+
service.serviceName,
|
|
3717
|
+
...service.volumeMountPath ? [`${service.serviceName}-volume`] : []
|
|
3718
|
+
]),
|
|
3719
|
+
...database ? [database.serviceName, `${database.serviceName}-volume`] : []
|
|
3720
|
+
])];
|
|
3721
|
+
}
|
|
3660
3722
|
function railwayIacServiceInput(input, sync, service, scope) {
|
|
3661
3723
|
if (scope === "staging" && service.imageRef) {
|
|
3662
3724
|
throw new Error(`Railway staging service ${service.serviceName} must build from Git source, not Docker image ${service.imageRef}.`);
|
|
@@ -3766,13 +3828,16 @@ async function reconcileStaleOperationsRunnerResourcesForScope(input, topology)
|
|
|
3766
3828
|
);
|
|
3767
3829
|
const projects = new Map(projectEntries.map((entry) => [entry.project.id, entry]));
|
|
3768
3830
|
for (const { project, environment } of projects.values()) {
|
|
3831
|
+
const siblingResourceNames = configuredRailwaySiblingResourceNames(input, scope, project.name);
|
|
3832
|
+
const retainedSiblingServiceNames = siblingResourceNames.filter((name) => !name.endsWith("-volume")).filter(isTreeseedOperationsRunnerResourceName);
|
|
3833
|
+
const retainedSiblingVolumeNames = siblingResourceNames.filter((name) => name.endsWith("-volume")).filter(isTreeseedOperationsRunnerResourceName);
|
|
3769
3834
|
await reconcileStaleOperationsRunnerResourcesForProject(input, {
|
|
3770
3835
|
scope,
|
|
3771
3836
|
env: topology.env,
|
|
3772
3837
|
project,
|
|
3773
3838
|
environment,
|
|
3774
|
-
desiredServiceNames,
|
|
3775
|
-
desiredVolumeNames
|
|
3839
|
+
desiredServiceNames: /* @__PURE__ */ new Set([...desiredServiceNames, ...retainedSiblingServiceNames]),
|
|
3840
|
+
desiredVolumeNames: /* @__PURE__ */ new Set([...desiredVolumeNames, ...retainedSiblingVolumeNames])
|
|
3776
3841
|
});
|
|
3777
3842
|
}
|
|
3778
3843
|
}
|
|
@@ -3931,11 +3996,13 @@ async function syncRailwayEnvironmentForScope(input, { planOnly = false, service
|
|
|
3931
3996
|
})),
|
|
3932
3997
|
database: databaseForIac
|
|
3933
3998
|
};
|
|
3999
|
+
const siblingResourceNames = configuredRailwaySiblingResourceNames(input, scope, project.name);
|
|
3934
4000
|
const {
|
|
3935
4001
|
applyRailwayIacProject,
|
|
3936
4002
|
cleanupRailwayIacRender,
|
|
3937
4003
|
planRailwayIacProject,
|
|
3938
4004
|
renderRailwayIacProject,
|
|
4005
|
+
selectRailwayIacRetainedResources,
|
|
3939
4006
|
validateRailwayIacChangeSet
|
|
3940
4007
|
} = await import("./providers/railway-iac.js");
|
|
3941
4008
|
let effectiveIacInput = iacInput;
|
|
@@ -3986,6 +4053,33 @@ async function syncRailwayEnvironmentForScope(input, { planOnly = false, service
|
|
|
3986
4053
|
]))
|
|
3987
4054
|
});
|
|
3988
4055
|
}
|
|
4056
|
+
const retainedResources = selectRailwayIacRetainedResources(plan, siblingResourceNames);
|
|
4057
|
+
if (retainedResources.length > 0) {
|
|
4058
|
+
traceRailwayReconcile(
|
|
4059
|
+
topology.env,
|
|
4060
|
+
"sync:iac-retain-sibling-environment",
|
|
4061
|
+
`${project.name}/${environment.name}:${retainedResources.map((resource) => resource.name).join(",")}`
|
|
4062
|
+
);
|
|
4063
|
+
cleanupRailwayIacRender(rendered);
|
|
4064
|
+
effectiveIacInput = { ...effectiveIacInput, retainedResources };
|
|
4065
|
+
rendered = renderRailwayIacProject(effectiveIacInput);
|
|
4066
|
+
plan = await planRailwayIacProject(effectiveIacInput, rendered);
|
|
4067
|
+
if (!plan.ok) {
|
|
4068
|
+
const diagnostics = plan.diagnostics.map((entry) => entry.message).filter(Boolean).join("; ");
|
|
4069
|
+
throw new Error(`Railway IaC plan failed for ${project.name}/${environment.name}${diagnostics ? `: ${diagnostics}` : "."}`);
|
|
4070
|
+
}
|
|
4071
|
+
validation = validateRailwayIacChangeSet(plan.changeSet, {
|
|
4072
|
+
services: rendered.serviceNames,
|
|
4073
|
+
volumes: rendered.volumeNames,
|
|
4074
|
+
database: rendered.databaseName,
|
|
4075
|
+
scope,
|
|
4076
|
+
serviceSourceModes: Object.fromEntries(effectiveIacInput.services.map((service) => [service.serviceName, service.sourceMode ?? null])),
|
|
4077
|
+
serviceSourceRefs: Object.fromEntries(effectiveIacInput.services.map((service) => [
|
|
4078
|
+
service.serviceName,
|
|
4079
|
+
service.sourceMode === "git" && service.sourceRepo ? `github:${service.sourceRepo}:${service.sourceBranch ?? ""}:${service.sourceRootDirectory ?? ""}:${service.sourceCommit ?? ""}` : service.sourceMode === "image" && service.imageRef ? `image:${service.imageRef}` : null
|
|
4080
|
+
]))
|
|
4081
|
+
});
|
|
4082
|
+
}
|
|
3989
4083
|
if (!validation.ok) {
|
|
3990
4084
|
throw new Error(`Railway IaC plan rejected for ${project.name}/${environment.name}: ${validation.blockedReasons.join("; ")}`);
|
|
3991
4085
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type RailwayChangeSet, type RailwayIacApplyResponse, type RailwayIacPlanResponse } from 'railway/iac';
|
|
1
|
+
import { type RailwayChangeSet, type RailwayIacApplyResponse, type RailwayIacPlanResponse, type ResourceNode } from 'railway/iac';
|
|
2
2
|
export type TreeseedRailwayIacService = {
|
|
3
3
|
key: string;
|
|
4
4
|
serviceName: string;
|
|
@@ -37,6 +37,7 @@ export type TreeseedRailwayIacProjectInput = {
|
|
|
37
37
|
railwayApiUrl?: string | null;
|
|
38
38
|
services: TreeseedRailwayIacService[];
|
|
39
39
|
database: TreeseedRailwayIacDatabase | null;
|
|
40
|
+
retainedResources?: ResourceNode[];
|
|
40
41
|
region?: string | null;
|
|
41
42
|
};
|
|
42
43
|
export type TreeseedRailwayIacRenderResult = {
|
|
@@ -47,6 +48,7 @@ export type TreeseedRailwayIacRenderResult = {
|
|
|
47
48
|
serviceNames: string[];
|
|
48
49
|
volumeNames: string[];
|
|
49
50
|
databaseName: string | null;
|
|
51
|
+
retainedResourceNames: string[];
|
|
50
52
|
source: string;
|
|
51
53
|
};
|
|
52
54
|
export type RailwayIacValidationResult = {
|
|
@@ -56,6 +58,7 @@ export type RailwayIacValidationResult = {
|
|
|
56
58
|
allowedDrift: string[];
|
|
57
59
|
};
|
|
58
60
|
export declare function renderRailwayIacProject(input: TreeseedRailwayIacProjectInput): TreeseedRailwayIacRenderResult;
|
|
61
|
+
export declare function selectRailwayIacRetainedResources(plan: Pick<RailwayIacPlanResponse, 'changeSet' | 'currentGraph'>, allowedNames: Iterable<string>): ResourceNode[];
|
|
59
62
|
export declare function validateRailwayIacChangeSet(changeSet: RailwayChangeSet | undefined, desiredNames: {
|
|
60
63
|
services: string[];
|
|
61
64
|
volumes: string[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { resolve } from "node:path";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
runRailwayIac
|
|
5
|
+
} from "railway/iac";
|
|
4
6
|
import { assertApiRailwaySourcePolicy, isApiRailwaySourcePolicyService } from "../../operations/services/railway-source-policy.js";
|
|
5
7
|
function js(value) {
|
|
6
8
|
return JSON.stringify(value);
|
|
@@ -115,6 +117,16 @@ function renderRailwayIacProject(input) {
|
|
|
115
117
|
const volumeNames = [];
|
|
116
118
|
const databaseVariableName = input.database ? "db" : null;
|
|
117
119
|
const databaseEnvName = input.database?.environmentVariable ?? null;
|
|
120
|
+
const desiredResourceNames = /* @__PURE__ */ new Set([
|
|
121
|
+
...input.services.map((service) => service.serviceName),
|
|
122
|
+
...input.services.filter((service) => service.volumeMountPath).map((service) => `${service.serviceName}-volume`),
|
|
123
|
+
...input.database ? [input.database.serviceName, `${input.database.serviceName}-volume`] : []
|
|
124
|
+
]);
|
|
125
|
+
const retainedResources = (input.retainedResources ?? []).filter((resource) => !desiredResourceNames.has(resource.name));
|
|
126
|
+
if (retainedResources.length > 0) {
|
|
127
|
+
declarations.push(` const retainedResources = ${js(retainedResources)};`);
|
|
128
|
+
resources.push("...retainedResources");
|
|
129
|
+
}
|
|
118
130
|
if (input.database) {
|
|
119
131
|
const postgresVolumeName = `${input.database.serviceName}-volume`;
|
|
120
132
|
const postgresMountPath = input.database.mountPath?.trim() || "/var/lib/postgresql/data";
|
|
@@ -205,9 +217,15 @@ ${declarations.join("\n")}
|
|
|
205
217
|
serviceNames: input.services.map((service) => service.serviceName),
|
|
206
218
|
volumeNames,
|
|
207
219
|
databaseName: input.database?.serviceName ?? null,
|
|
220
|
+
retainedResourceNames: retainedResources.map((resource) => resource.name),
|
|
208
221
|
source
|
|
209
222
|
};
|
|
210
223
|
}
|
|
224
|
+
function selectRailwayIacRetainedResources(plan, allowedNames) {
|
|
225
|
+
const allowed = new Set(allowedNames);
|
|
226
|
+
const deletedAllowedNames = new Set((plan.changeSet?.changes ?? []).filter((change) => change.kind === "resource.delete").map((change) => changeName(change)).filter((name) => allowed.has(name)));
|
|
227
|
+
return (plan.currentGraph?.resources ?? []).filter((resource) => deletedAllowedNames.has(resource.name));
|
|
228
|
+
}
|
|
211
229
|
function changeName(change) {
|
|
212
230
|
return String(change?.resource?.name ?? change?.previous?.name ?? change?.address ?? change?.path ?? "");
|
|
213
231
|
}
|
|
@@ -322,5 +340,6 @@ export {
|
|
|
322
340
|
cleanupRailwayIacRender,
|
|
323
341
|
planRailwayIacProject,
|
|
324
342
|
renderRailwayIacProject,
|
|
343
|
+
selectRailwayIacRetainedResources,
|
|
325
344
|
validateRailwayIacChangeSet
|
|
326
345
|
};
|