@treeseed/sdk 0.12.32 → 0.12.34

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.
@@ -111,11 +111,12 @@ export declare function verifyRailwayManagedResources(tenantRoot: any, scope: an
111
111
  checks: any[];
112
112
  }>;
113
113
  export declare function shouldRunRailwayPredeployBuild(env?: NodeJS.ProcessEnv): boolean;
114
- export declare function deployRailwayService(tenantRoot: any, service: any, { planOnly, write, prefix, env, }?: {
114
+ export declare function deployRailwayService(tenantRoot: any, service: any, { planOnly, write, prefix, env, fetchImpl, }?: {
115
115
  planOnly?: boolean;
116
116
  write?: TreeseedBootstrapWriter;
117
117
  prefix?: TreeseedBootstrapTaskPrefix;
118
118
  env?: NodeJS.ProcessEnv | Record<string, string | undefined>;
119
+ fetchImpl?: typeof fetch;
119
120
  }): Promise<{
120
121
  service: any;
121
122
  status: string;
@@ -452,7 +452,7 @@ async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
452
452
  } = {}) {
453
453
  const startMs = performance.now();
454
454
  const deadline = Date.now() + timeoutMs;
455
- const projectId = services.find((service) => typeof service.projectId === "string" && service.projectId.trim())?.projectId ?? null;
455
+ const projectId = await resolveRailwayDeploymentProjectId(services, { env, fetchImpl });
456
456
  if (!projectId) {
457
457
  return {
458
458
  ok: false,
@@ -571,6 +571,23 @@ async function waitForRailwayManagedDeploymentsSettled(tenantRoot, scope, {
571
571
  await sleep(pollMs);
572
572
  }
573
573
  }
574
+ async function resolveRailwayDeploymentProjectId(services, { env = process.env, fetchImpl = fetch } = {}) {
575
+ const configuredProjectId = services.find((service) => typeof service.projectId === "string" && service.projectId.trim())?.projectId?.trim();
576
+ if (configuredProjectId) {
577
+ return configuredProjectId;
578
+ }
579
+ const projectName = services.find((service) => typeof service.projectName === "string" && service.projectName.trim())?.projectName?.trim();
580
+ if (!projectName) {
581
+ return null;
582
+ }
583
+ try {
584
+ const workspace = await resolveRailwayWorkspaceContext({ env, fetchImpl });
585
+ const projects = await listRailwayProjects({ env, workspaceId: workspace.id, fetchImpl });
586
+ return projects.find((project) => project.name === projectName)?.id ?? null;
587
+ } catch {
588
+ return null;
589
+ }
590
+ }
574
591
  async function fetchRailwayProjectDeploymentStatus({ projectId, env = process.env, fetchImpl = fetch }) {
575
592
  const payload = await railwayGraphqlRequest({
576
593
  query: `
@@ -1362,7 +1379,7 @@ function shouldRunRailwayPredeployBuild(env = process.env) {
1362
1379
  }
1363
1380
  return configuredEnvValue(env, "CI") !== "true";
1364
1381
  }
1365
- async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
1382
+ async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix, fetchImpl = fetch) {
1366
1383
  if (service.key !== "api") {
1367
1384
  return null;
1368
1385
  }
@@ -1387,22 +1404,24 @@ async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
1387
1404
  environmentId,
1388
1405
  serviceId,
1389
1406
  variables,
1390
- env
1407
+ env,
1408
+ fetchImpl
1391
1409
  });
1392
1410
  write ? write(`[${prefix.scope}][${prefix.system}][${prefix.task}][vars] Synced device login approval URL variables for ${service.serviceName ?? serviceId}.`, "stdout") : null;
1393
1411
  return { variables: Object.keys(variables) };
1394
1412
  }
1395
- async function resolveRailwayDeployProjectContext(service, { env = process.env } = {}) {
1413
+ async function resolveRailwayDeployProjectContext(service, { env = process.env, fetchImpl = fetch } = {}) {
1396
1414
  if (service.projectId) {
1397
1415
  return service;
1398
1416
  }
1399
- const workspace = await resolveRailwayWorkspaceContext({ env });
1417
+ const workspace = await resolveRailwayWorkspaceContext({ env, fetchImpl });
1400
1418
  const { project } = await ensureRailwayProject({
1401
1419
  projectId: service.projectId,
1402
1420
  projectName: service.projectName,
1403
1421
  defaultEnvironmentName: service.railwayEnvironment,
1404
1422
  env,
1405
- workspace: workspace.id
1423
+ workspace: workspace.id,
1424
+ fetchImpl
1406
1425
  });
1407
1426
  return {
1408
1427
  ...service,
@@ -1410,7 +1429,7 @@ async function resolveRailwayDeployProjectContext(service, { env = process.env }
1410
1429
  projectName: project.name ?? service.projectName
1411
1430
  };
1412
1431
  }
1413
- async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env, writePhase = null } = {}) {
1432
+ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env, writePhase = null, fetchImpl = fetch } = {}) {
1414
1433
  const writeSyncPhase = (stage, message) => {
1415
1434
  if (typeof writePhase === "function") {
1416
1435
  writePhase(`sync-runtime-config:${stage}`, message);
@@ -1423,7 +1442,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1423
1442
  return null;
1424
1443
  }
1425
1444
  writeSyncPhase("workspace", "Resolving Railway workspace.");
1426
- const workspace = await resolveRailwayWorkspaceContext({ env });
1445
+ const workspace = await resolveRailwayWorkspaceContext({ env, fetchImpl });
1427
1446
  let project = null;
1428
1447
  if (service.projectId) {
1429
1448
  writeSyncPhase("project", `Resolving Railway project ${service.projectName ?? service.projectId}.`);
@@ -1432,11 +1451,12 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1432
1451
  projectName: service.projectName,
1433
1452
  defaultEnvironmentName: service.railwayEnvironment,
1434
1453
  env,
1435
- workspace: workspace.id
1454
+ workspace: workspace.id,
1455
+ fetchImpl
1436
1456
  }).then((result) => result.project);
1437
1457
  } else {
1438
1458
  writeSyncPhase("project", `Looking up Railway project ${service.projectName}.`);
1439
- const projects = await listRailwayProjects({ env, workspaceId: workspace.id });
1459
+ const projects = await listRailwayProjects({ env, workspaceId: workspace.id, fetchImpl });
1440
1460
  project = projects.find((entry) => entry.name === service.projectName) ?? null;
1441
1461
  if (!project) {
1442
1462
  writeSyncPhase("project", `Creating Railway project ${service.projectName}.`);
@@ -1444,7 +1464,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1444
1464
  projectName: service.projectName,
1445
1465
  defaultEnvironmentName: service.railwayEnvironment,
1446
1466
  env,
1447
- workspace: workspace.id
1467
+ workspace: workspace.id,
1468
+ fetchImpl
1448
1469
  }).then((result) => result.project);
1449
1470
  }
1450
1471
  }
@@ -1455,7 +1476,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1455
1476
  environment = await ensureRailwayEnvironment({
1456
1477
  projectId: project.id,
1457
1478
  environmentName,
1458
- env
1479
+ env,
1480
+ fetchImpl
1459
1481
  }).then((result) => result.environment);
1460
1482
  }
1461
1483
  let railwayService = project.services.find((entry) => entry.id === service.serviceId || entry.name === service.serviceName) ?? null;
@@ -1469,9 +1491,10 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1469
1491
  imageRef: service.imageRef,
1470
1492
  sourceRepo: service.sourceRepo,
1471
1493
  sourceBranch: service.sourceBranch,
1472
- env
1494
+ env,
1495
+ fetchImpl
1473
1496
  }).then((result) => result.service);
1474
- } else if (service.imageRef) {
1497
+ } else if (service.imageRef || service.sourceRepo) {
1475
1498
  railwayService = await ensureRailwayService({
1476
1499
  projectId: project.id,
1477
1500
  serviceId: service.serviceId,
@@ -1480,7 +1503,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1480
1503
  imageRef: service.imageRef,
1481
1504
  sourceRepo: service.sourceRepo,
1482
1505
  sourceBranch: service.sourceBranch,
1483
- env
1506
+ env,
1507
+ fetchImpl
1484
1508
  }).then((result) => result.service);
1485
1509
  }
1486
1510
  if (wantsInstanceConfig) {
@@ -1499,7 +1523,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1499
1523
  restartPolicy: service.restartPolicy,
1500
1524
  runtimeMode: service.runtimeMode,
1501
1525
  deploymentRegion: wantsRunnerVolume ? configuredEnvValue(env, "TREESEED_RAILWAY_STATEFUL_REGION") || "us-west2" : null,
1502
- env
1526
+ env,
1527
+ fetchImpl
1503
1528
  }) : null;
1504
1529
  writeSyncPhase("variables", "Upserting Railway runtime variables.");
1505
1530
  await upsertRailwayVariables({
@@ -1544,7 +1569,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1544
1569
  ...configuredEnvValue(env, "TREESEED_CODEX_AUTH_JSON_B64") ? { TREESEED_CODEX_AUTH_JSON_B64: configuredEnvValue(env, "TREESEED_CODEX_AUTH_JSON_B64") } : {}
1545
1570
  } : {}
1546
1571
  },
1547
- env
1572
+ env,
1573
+ fetchImpl
1548
1574
  });
1549
1575
  const volumeMountPath = service.volumeMountPath ?? service.runnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH;
1550
1576
  if (wantsRunnerVolume) {
@@ -1556,7 +1582,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1556
1582
  serviceId: railwayService.id,
1557
1583
  name: service.key === "operationsRunner" ? deriveRailwayOperationsRunnerVolumeName(railwayService.name, environment.name) : service.key === "capacityProviderRunner" ? deriveRailwayCapacityProviderRunnerVolumeName(railwayService.name, environment.name) : deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
1558
1584
  mountPath: volumeMountPath,
1559
- env
1585
+ env,
1586
+ fetchImpl
1560
1587
  }) : null;
1561
1588
  if (wantsRunnerVolume) {
1562
1589
  if (service.key === "workerRunner") {
@@ -1572,7 +1599,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1572
1599
  TREESEED_WORKER_IDLE_EXIT_MS: configuredEnvValue(env, "TREESEED_WORKER_IDLE_EXIT_MS") || "60000",
1573
1600
  ...volumeConfiguration?.volume.id ? { TREESEED_RUNNER_VOLUME_ID: volumeConfiguration.volume.id } : {}
1574
1601
  },
1575
- env
1602
+ env,
1603
+ fetchImpl
1576
1604
  });
1577
1605
  }
1578
1606
  }
@@ -1599,7 +1627,8 @@ async function deployRailwayService(tenantRoot, service, {
1599
1627
  planOnly = false,
1600
1628
  write,
1601
1629
  prefix,
1602
- env = process.env
1630
+ env = process.env,
1631
+ fetchImpl = fetch
1603
1632
  } = {}) {
1604
1633
  const timings = [];
1605
1634
  if (planOnly) {
@@ -1618,7 +1647,7 @@ async function deployRailwayService(tenantRoot, service, {
1618
1647
  }
1619
1648
  };
1620
1649
  }
1621
- const deployService = await timedRailwayPhase(timings, "railway:resolve-context", () => resolveRailwayDeployProjectContext(service, { env }), {
1650
+ const deployService = await timedRailwayPhase(timings, "railway:resolve-context", () => resolveRailwayDeployProjectContext(service, { env, fetchImpl }), {
1622
1651
  service: service.key
1623
1652
  });
1624
1653
  const commandEnv = buildRailwayCommandEnv({ ...process.env, ...env });
@@ -1635,7 +1664,7 @@ async function deployRailwayService(tenantRoot, service, {
1635
1664
  writePhase("resolve-context", `Resolved Railway service ${deployService.serviceName ?? deployService.serviceId ?? deployService.key}.`);
1636
1665
  writePhase("sync-runtime-config", "Syncing Railway runtime configuration.");
1637
1666
  const runtimeConfiguration = await timedRailwayPhase(timings, "railway:sync-runtime-config", () => withRailwayPhaseTimeout(
1638
- () => syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, { env: commandEnv, writePhase }),
1667
+ () => syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, { env: commandEnv, writePhase, fetchImpl }),
1639
1668
  railwayPhaseTimeoutMs(commandEnv, "sync_runtime_config"),
1640
1669
  `Railway runtime configuration sync timed out for ${deployService.serviceName ?? deployService.key}.`
1641
1670
  ), { service: deployService.key });
@@ -1650,7 +1679,7 @@ async function deployRailwayService(tenantRoot, service, {
1650
1679
  };
1651
1680
  writePhase("device-login-vars", "Syncing Railway device-login variables.");
1652
1681
  await timedRailwayPhase(timings, "railway:device-login-vars", () => withRailwayPhaseTimeout(
1653
- () => syncRailwayApiDeviceLoginVariables(cliDeployService, commandEnv, write, taskPrefix),
1682
+ () => syncRailwayApiDeviceLoginVariables(cliDeployService, commandEnv, write, taskPrefix, fetchImpl),
1654
1683
  railwayPhaseTimeoutMs(commandEnv, "device_login_vars"),
1655
1684
  `Railway device-login variable sync timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
1656
1685
  ), {
@@ -1673,7 +1702,8 @@ async function deployRailwayService(tenantRoot, service, {
1673
1702
  () => deployRailwayServiceInstance({
1674
1703
  serviceId: cliDeployService.serviceId,
1675
1704
  environmentId: cliDeployService.environmentId,
1676
- env: commandEnv
1705
+ env: commandEnv,
1706
+ fetchImpl
1677
1707
  }),
1678
1708
  railwayPhaseTimeoutMs(commandEnv, "deploy"),
1679
1709
  `Railway API deploy phase timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
@@ -19,6 +19,7 @@ export type WorkspaceDependencyModeReport = {
19
19
  }>;
20
20
  created: string[];
21
21
  removed: string[];
22
+ preserved: string[];
22
23
  issues: string[];
23
24
  };
24
25
  export type DeploymentLockfileWorkspaceIssue = {
@@ -33,6 +34,7 @@ export declare function ensureLocalWorkspaceLinks(root?: any, options?: {
33
34
  }): {
34
35
  created: string[];
35
36
  removed: never[];
37
+ preserved: never[];
36
38
  mode: DependencyResolutionMode;
37
39
  enabled: boolean;
38
40
  root: string;
@@ -47,9 +49,11 @@ export declare function ensureLocalWorkspaceLinks(root?: any, options?: {
47
49
  export declare function unlinkLocalWorkspaceLinks(root?: any, options?: {
48
50
  mode?: WorkspaceLinksMode;
49
51
  env?: NodeJS.ProcessEnv;
52
+ preserveOperatorLinks?: boolean;
50
53
  }): {
51
54
  enabled: boolean;
52
55
  removed: string[];
56
+ preserved: string[];
53
57
  mode: DependencyResolutionMode;
54
58
  root: string;
55
59
  links: Array<WorkspaceLink & {
@@ -110,6 +110,27 @@ function internalDependencyNames(packageJson, packageNames) {
110
110
  }
111
111
  return [...names].sort();
112
112
  }
113
+ function operatorWorkspacePackageNames(packages) {
114
+ const packageByName = new Map(packages.map((pkg) => [String(pkg.name), pkg]));
115
+ const packageNames = new Set(packageByName.keys());
116
+ const roots = packages.filter((pkg) => {
117
+ const name = String(pkg.name ?? "");
118
+ if (name === "@treeseed/cli") return true;
119
+ return packageBinEntries(pkg.packageJson).some(([binName]) => binName === "trsd" || binName === "treeseed");
120
+ }).map((pkg) => String(pkg.name));
121
+ const closure = /* @__PURE__ */ new Set();
122
+ const visit = (packageName) => {
123
+ if (closure.has(packageName)) return;
124
+ const pkg = packageByName.get(packageName);
125
+ if (!pkg) return;
126
+ closure.add(packageName);
127
+ for (const dependencyName of internalDependencyNames(pkg.packageJson, packageNames)) {
128
+ visit(dependencyName);
129
+ }
130
+ };
131
+ for (const rootName of roots) visit(rootName);
132
+ return closure;
133
+ }
113
134
  function dependencyNames(packageJson, filter) {
114
135
  const names = /* @__PURE__ */ new Set();
115
136
  if (!packageJson) return names;
@@ -306,7 +327,7 @@ function ensureLocalWorkspaceLinks(root = workspaceRoot(), options = {}) {
306
327
  const enabled = workspaceLinksEnabled(options.mode, options.env);
307
328
  const links = discoverWorkspaceLinks(root);
308
329
  const report = inspectWorkspaceDependencyMode(root, { mode: options.mode, env: options.env });
309
- if (!enabled) return { ...report, enabled: false, created: [], removed: [] };
330
+ if (!enabled) return { ...report, enabled: false, created: [], removed: [], preserved: [] };
310
331
  ensureGitInfoExcludes(root, links);
311
332
  const managedLinks = readMetadata(root);
312
333
  const created = [];
@@ -325,7 +346,8 @@ function ensureLocalWorkspaceLinks(root = workspaceRoot(), options = {}) {
325
346
  return {
326
347
  ...inspectWorkspaceDependencyMode(root, { mode: options.mode, env: options.env }),
327
348
  created,
328
- removed: []
349
+ removed: [],
350
+ preserved: []
329
351
  };
330
352
  }
331
353
  function unlinkLocalWorkspaceLinks(root = workspaceRoot(), options = {}) {
@@ -333,20 +355,30 @@ function unlinkLocalWorkspaceLinks(root = workspaceRoot(), options = {}) {
333
355
  const links = discoverWorkspaceLinks(root);
334
356
  const managedLinks = readMetadata(root);
335
357
  const removed = [];
336
- if (!enabled) return { ...inspectWorkspaceDependencyMode(root, { mode: options.mode, env: options.env }), enabled: false, removed };
358
+ const preserved = [];
359
+ const packages = workspacePackages(root).filter((pkg) => typeof pkg.name === "string" && pkg.name.startsWith("@treeseed/"));
360
+ const packageNameByDir = new Map(packages.map((pkg) => [pathKey(pkg.dir), String(pkg.name)]));
361
+ const operatorPackageNames = options.preserveOperatorLinks ? operatorWorkspacePackageNames(packages) : /* @__PURE__ */ new Set();
362
+ if (!enabled) return { ...inspectWorkspaceDependencyMode(root, { mode: options.mode, env: options.env }), enabled: false, removed, preserved };
337
363
  for (const link of links) {
338
364
  const stat = safeLstat(link.linkPath);
339
365
  if (!stat) continue;
340
366
  const currentTarget = stat.isSymbolicLink() ? safeReadlink(link.linkPath) : null;
341
367
  const managed = managedLinks.has(pathKey(link.linkPath)) || currentTarget === pathKey(link.targetPath);
342
368
  if (!stat.isSymbolicLink() || !managed) continue;
369
+ const ownerPackageName = packageNameByDir.get(pathKey(link.ownerPath)) ?? null;
370
+ if (operatorPackageNames.has(link.packageName) || ownerPackageName && operatorPackageNames.has(ownerPackageName)) {
371
+ preserved.push(link.linkPath);
372
+ continue;
373
+ }
343
374
  unlinkSync(link.linkPath);
344
375
  removed.push(link.linkPath);
345
376
  }
346
377
  return {
347
378
  ...inspectWorkspaceDependencyMode(root, { mode: options.mode, env: options.env }),
348
379
  removed,
349
- created: []
380
+ created: [],
381
+ preserved
350
382
  };
351
383
  }
352
384
  function collectDeploymentLockfileWorkspaceIssues(root = workspaceRoot()) {
@@ -425,6 +457,7 @@ function inspectWorkspaceDependencyMode(root = workspaceRoot(), options = {}) {
425
457
  links: inspected,
426
458
  created: [],
427
459
  removed: [],
460
+ preserved: [],
428
461
  issues: [
429
462
  ...inspected.filter((link) => link.exists && (!link.linked || !link.targetMatches)).map((link) => `${link.linkPath} is not linked to ${link.targetPath}.`),
430
463
  ...lockIssues
@@ -318,10 +318,13 @@ function unlinkWorkflowWorkspaceLinks(root, helpers, mode = "auto") {
318
318
  if (!shouldManageWorkspaceLinks(mode, helpers.context.env)) {
319
319
  return inspectWorkspaceDependencyMode(root, { mode: "off", env: helpers.context.env });
320
320
  }
321
- const report = unlinkLocalWorkspaceLinks(root, { mode, env: helpers.context.env });
321
+ const report = unlinkLocalWorkspaceLinks(root, { mode, env: helpers.context.env, preserveOperatorLinks: true });
322
322
  if (report.removed.length > 0) {
323
323
  helpers.write(`[workspace][unlink] Removed ${report.removed.length} local workspace package links for deployment install.`);
324
324
  }
325
+ if (report.preserved.length > 0) {
326
+ helpers.write(`[workspace][unlink] Preserved ${report.preserved.length} operator workspace links so local trsd tooling remains available.`);
327
+ }
325
328
  return report;
326
329
  }
327
330
  function normalizeCiMode(mode, operation) {
@@ -5652,6 +5655,7 @@ ${blockers.join("\n")}`, {
5652
5655
  { id: "production-api-guarantees", description: "Run production API release guarantees before root deploy", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
5653
5656
  { id: "release-root", description: `Release market ${plannedRelease.rootVersion}`, repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
5654
5657
  { id: "publish-wait", description: "Wait for production release workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
5658
+ { id: "production-web-live-verification", description: "Run production web live verification after root deploy", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
5655
5659
  { id: "release-back-merge", description: "Back-merge production release history into staging", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
5656
5660
  { id: "workspace-link", description: "Restore local workspace links after release", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true }
5657
5661
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.12.32",
3
+ "version": "0.12.34",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {