@treeseed/sdk 0.12.55 → 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.
|
@@ -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}.`);
|
|
@@ -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,
|
|
@@ -3784,13 +3828,16 @@ async function reconcileStaleOperationsRunnerResourcesForScope(input, topology)
|
|
|
3784
3828
|
);
|
|
3785
3829
|
const projects = new Map(projectEntries.map((entry) => [entry.project.id, entry]));
|
|
3786
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);
|
|
3787
3834
|
await reconcileStaleOperationsRunnerResourcesForProject(input, {
|
|
3788
3835
|
scope,
|
|
3789
3836
|
env: topology.env,
|
|
3790
3837
|
project,
|
|
3791
3838
|
environment,
|
|
3792
|
-
desiredServiceNames,
|
|
3793
|
-
desiredVolumeNames
|
|
3839
|
+
desiredServiceNames: /* @__PURE__ */ new Set([...desiredServiceNames, ...retainedSiblingServiceNames]),
|
|
3840
|
+
desiredVolumeNames: /* @__PURE__ */ new Set([...desiredVolumeNames, ...retainedSiblingVolumeNames])
|
|
3794
3841
|
});
|
|
3795
3842
|
}
|
|
3796
3843
|
}
|