@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 { configuredRailwayServices, findStaleTreeseedOperationsRunnerResources } from "./railway-deploy.js";
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 = new Set(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));
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.12.55",
3
+ "version": "0.12.56",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {