@treeseed/sdk 0.12.33 → 0.12.35

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.
@@ -222,6 +222,9 @@ function beamPackageAdapter(root, dir) {
222
222
  const versionSource = resolve(dir, versionSourceRel);
223
223
  const image = typeof manifest?.image === "string" && manifest.image.trim() ? manifest.image.trim() : id === "treedx" ? "treeseed/treedx" : null;
224
224
  const dockerArtifacts = manifestDockerArtifacts(manifest?.artifacts);
225
+ const dockerImages = stringRecord(manifest?.dockerImages);
226
+ const dockerImageReleaseWorkflow = stringValue(dockerImages.releaseWorkflow);
227
+ const dockerImageArchitectures = stringArray(dockerImages.architectures);
225
228
  const repository = stringValue(manifest?.repository) ?? (id === "treedx" ? "treeseed-ai/treedx" : null);
226
229
  const hostedVerifyWorkflow = stringValue(manifest?.hostedVerifyWorkflow) ?? stringValue(stringRecord(manifest?.releaseGate).workflow) ?? (existsSync(resolve(dir, ".github/workflows/release-gate.yml")) ? "release-gate.yml" : null);
227
230
  const projectArchitecture = normalizeTreeseedPackageProjectArchitecture(manifest?.projectArchitecture, id);
@@ -255,7 +258,7 @@ function beamPackageAdapter(root, dir) {
255
258
  context: artifact.context ?? ".",
256
259
  target: artifact.target,
257
260
  role: artifact.role ?? id,
258
- architectures: artifact.architectures.length > 0 ? artifact.architectures : stringArray(stringRecord(manifest?.dockerImages).architectures)
261
+ architectures: artifact.architectures.length > 0 ? artifact.architectures : dockerImageArchitectures
259
262
  })) : image ? [{
260
263
  provider: "docker",
261
264
  name: image,
@@ -264,7 +267,7 @@ function beamPackageAdapter(root, dir) {
264
267
  context: ".",
265
268
  target: null,
266
269
  role: id,
267
- architectures: stringArray(stringRecord(manifest?.dockerImages).architectures)
270
+ architectures: dockerImageArchitectures
268
271
  }] : [],
269
272
  releaseChecks: dockerArtifacts.length > 0 ? dockerArtifacts.map((artifact) => ({ kind: "docker-manifest", name: `${artifact.name} Docker image manifest`, detail: `${artifact.name}:${version ?? "<version>"}` })) : image ? [{ kind: "docker-manifest", name: "Docker image manifest", detail: `${image}:${version ?? "<version>"}` }] : [],
270
273
  metadata: {
@@ -272,7 +275,9 @@ function beamPackageAdapter(root, dir) {
272
275
  hasDockerfile: existsSync(resolve(dir, "Dockerfile")),
273
276
  repository,
274
277
  deploymentSource: stringRecord(manifest?.deploymentSource),
275
- imageHosting: stringRecord(stringRecord(manifest?.dockerImages).hosting),
278
+ dockerImageReleaseWorkflow: dockerImageReleaseWorkflow ? `.github/workflows/${dockerImageReleaseWorkflow}` : null,
279
+ dockerImageArchitectures,
280
+ imageHosting: stringRecord(dockerImages.hosting),
276
281
  versionSource: versionSourceRel,
277
282
  ...hostedVerifyWorkflow ? {
278
283
  hostedVerifyWorkflow: hostedVerifyWorkflow.startsWith(".github/workflows/") ? hostedVerifyWorkflow : `.github/workflows/${hostedVerifyWorkflow}`
@@ -818,6 +823,9 @@ function shellQuoteWorkflowArg(value) {
818
823
  return `'${value.replace(/'/gu, `'\\''`)}'`;
819
824
  }
820
825
  function workflowTemplatesForAdapter(adapter) {
826
+ if (adapter.metadata.workflowTemplateVersion === "custom") {
827
+ return [];
828
+ }
821
829
  const hasDocker = adapter.artifacts.some((artifact) => artifact.provider === "docker");
822
830
  const hasNpm = adapter.artifacts.some((artifact) => artifact.provider === "npm");
823
831
  return [
@@ -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;
@@ -1379,7 +1379,7 @@ function shouldRunRailwayPredeployBuild(env = process.env) {
1379
1379
  }
1380
1380
  return configuredEnvValue(env, "CI") !== "true";
1381
1381
  }
1382
- async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
1382
+ async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix, fetchImpl = fetch) {
1383
1383
  if (service.key !== "api") {
1384
1384
  return null;
1385
1385
  }
@@ -1404,22 +1404,24 @@ async function syncRailwayApiDeviceLoginVariables(service, env, write, prefix) {
1404
1404
  environmentId,
1405
1405
  serviceId,
1406
1406
  variables,
1407
- env
1407
+ env,
1408
+ fetchImpl
1408
1409
  });
1409
1410
  write ? write(`[${prefix.scope}][${prefix.system}][${prefix.task}][vars] Synced device login approval URL variables for ${service.serviceName ?? serviceId}.`, "stdout") : null;
1410
1411
  return { variables: Object.keys(variables) };
1411
1412
  }
1412
- async function resolveRailwayDeployProjectContext(service, { env = process.env } = {}) {
1413
+ async function resolveRailwayDeployProjectContext(service, { env = process.env, fetchImpl = fetch } = {}) {
1413
1414
  if (service.projectId) {
1414
1415
  return service;
1415
1416
  }
1416
- const workspace = await resolveRailwayWorkspaceContext({ env });
1417
+ const workspace = await resolveRailwayWorkspaceContext({ env, fetchImpl });
1417
1418
  const { project } = await ensureRailwayProject({
1418
1419
  projectId: service.projectId,
1419
1420
  projectName: service.projectName,
1420
1421
  defaultEnvironmentName: service.railwayEnvironment,
1421
1422
  env,
1422
- workspace: workspace.id
1423
+ workspace: workspace.id,
1424
+ fetchImpl
1423
1425
  });
1424
1426
  return {
1425
1427
  ...service,
@@ -1427,7 +1429,7 @@ async function resolveRailwayDeployProjectContext(service, { env = process.env }
1427
1429
  projectName: project.name ?? service.projectName
1428
1430
  };
1429
1431
  }
1430
- async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env, writePhase = null } = {}) {
1432
+ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, service, { env = process.env, writePhase = null, fetchImpl = fetch } = {}) {
1431
1433
  const writeSyncPhase = (stage, message) => {
1432
1434
  if (typeof writePhase === "function") {
1433
1435
  writePhase(`sync-runtime-config:${stage}`, message);
@@ -1440,7 +1442,7 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1440
1442
  return null;
1441
1443
  }
1442
1444
  writeSyncPhase("workspace", "Resolving Railway workspace.");
1443
- const workspace = await resolveRailwayWorkspaceContext({ env });
1445
+ const workspace = await resolveRailwayWorkspaceContext({ env, fetchImpl });
1444
1446
  let project = null;
1445
1447
  if (service.projectId) {
1446
1448
  writeSyncPhase("project", `Resolving Railway project ${service.projectName ?? service.projectId}.`);
@@ -1449,11 +1451,12 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1449
1451
  projectName: service.projectName,
1450
1452
  defaultEnvironmentName: service.railwayEnvironment,
1451
1453
  env,
1452
- workspace: workspace.id
1454
+ workspace: workspace.id,
1455
+ fetchImpl
1453
1456
  }).then((result) => result.project);
1454
1457
  } else {
1455
1458
  writeSyncPhase("project", `Looking up Railway project ${service.projectName}.`);
1456
- const projects = await listRailwayProjects({ env, workspaceId: workspace.id });
1459
+ const projects = await listRailwayProjects({ env, workspaceId: workspace.id, fetchImpl });
1457
1460
  project = projects.find((entry) => entry.name === service.projectName) ?? null;
1458
1461
  if (!project) {
1459
1462
  writeSyncPhase("project", `Creating Railway project ${service.projectName}.`);
@@ -1461,7 +1464,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1461
1464
  projectName: service.projectName,
1462
1465
  defaultEnvironmentName: service.railwayEnvironment,
1463
1466
  env,
1464
- workspace: workspace.id
1467
+ workspace: workspace.id,
1468
+ fetchImpl
1465
1469
  }).then((result) => result.project);
1466
1470
  }
1467
1471
  }
@@ -1472,7 +1476,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1472
1476
  environment = await ensureRailwayEnvironment({
1473
1477
  projectId: project.id,
1474
1478
  environmentName,
1475
- env
1479
+ env,
1480
+ fetchImpl
1476
1481
  }).then((result) => result.environment);
1477
1482
  }
1478
1483
  let railwayService = project.services.find((entry) => entry.id === service.serviceId || entry.name === service.serviceName) ?? null;
@@ -1486,9 +1491,10 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1486
1491
  imageRef: service.imageRef,
1487
1492
  sourceRepo: service.sourceRepo,
1488
1493
  sourceBranch: service.sourceBranch,
1489
- env
1494
+ env,
1495
+ fetchImpl
1490
1496
  }).then((result) => result.service);
1491
- } else if (service.imageRef) {
1497
+ } else if (service.imageRef || service.sourceRepo) {
1492
1498
  railwayService = await ensureRailwayService({
1493
1499
  projectId: project.id,
1494
1500
  serviceId: service.serviceId,
@@ -1497,7 +1503,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1497
1503
  imageRef: service.imageRef,
1498
1504
  sourceRepo: service.sourceRepo,
1499
1505
  sourceBranch: service.sourceBranch,
1500
- env
1506
+ env,
1507
+ fetchImpl
1501
1508
  }).then((result) => result.service);
1502
1509
  }
1503
1510
  if (wantsInstanceConfig) {
@@ -1516,7 +1523,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1516
1523
  restartPolicy: service.restartPolicy,
1517
1524
  runtimeMode: service.runtimeMode,
1518
1525
  deploymentRegion: wantsRunnerVolume ? configuredEnvValue(env, "TREESEED_RAILWAY_STATEFUL_REGION") || "us-west2" : null,
1519
- env
1526
+ env,
1527
+ fetchImpl
1520
1528
  }) : null;
1521
1529
  writeSyncPhase("variables", "Upserting Railway runtime variables.");
1522
1530
  await upsertRailwayVariables({
@@ -1561,7 +1569,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1561
1569
  ...configuredEnvValue(env, "TREESEED_CODEX_AUTH_JSON_B64") ? { TREESEED_CODEX_AUTH_JSON_B64: configuredEnvValue(env, "TREESEED_CODEX_AUTH_JSON_B64") } : {}
1562
1570
  } : {}
1563
1571
  },
1564
- env
1572
+ env,
1573
+ fetchImpl
1565
1574
  });
1566
1575
  const volumeMountPath = service.volumeMountPath ?? service.runnerPool?.volumeMountPath ?? WORKER_RUNNER_VOLUME_MOUNT_PATH;
1567
1576
  if (wantsRunnerVolume) {
@@ -1573,7 +1582,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1573
1582
  serviceId: railwayService.id,
1574
1583
  name: service.key === "operationsRunner" ? deriveRailwayOperationsRunnerVolumeName(railwayService.name, environment.name) : service.key === "capacityProviderRunner" ? deriveRailwayCapacityProviderRunnerVolumeName(railwayService.name, environment.name) : deriveRailwayWorkerRunnerVolumeName(railwayService.name, environment.name),
1575
1584
  mountPath: volumeMountPath,
1576
- env
1585
+ env,
1586
+ fetchImpl
1577
1587
  }) : null;
1578
1588
  if (wantsRunnerVolume) {
1579
1589
  if (service.key === "workerRunner") {
@@ -1589,7 +1599,8 @@ async function syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, ser
1589
1599
  TREESEED_WORKER_IDLE_EXIT_MS: configuredEnvValue(env, "TREESEED_WORKER_IDLE_EXIT_MS") || "60000",
1590
1600
  ...volumeConfiguration?.volume.id ? { TREESEED_RUNNER_VOLUME_ID: volumeConfiguration.volume.id } : {}
1591
1601
  },
1592
- env
1602
+ env,
1603
+ fetchImpl
1593
1604
  });
1594
1605
  }
1595
1606
  }
@@ -1616,7 +1627,8 @@ async function deployRailwayService(tenantRoot, service, {
1616
1627
  planOnly = false,
1617
1628
  write,
1618
1629
  prefix,
1619
- env = process.env
1630
+ env = process.env,
1631
+ fetchImpl = fetch
1620
1632
  } = {}) {
1621
1633
  const timings = [];
1622
1634
  if (planOnly) {
@@ -1635,7 +1647,7 @@ async function deployRailwayService(tenantRoot, service, {
1635
1647
  }
1636
1648
  };
1637
1649
  }
1638
- const deployService = await timedRailwayPhase(timings, "railway:resolve-context", () => resolveRailwayDeployProjectContext(service, { env }), {
1650
+ const deployService = await timedRailwayPhase(timings, "railway:resolve-context", () => resolveRailwayDeployProjectContext(service, { env, fetchImpl }), {
1639
1651
  service: service.key
1640
1652
  });
1641
1653
  const commandEnv = buildRailwayCommandEnv({ ...process.env, ...env });
@@ -1652,7 +1664,7 @@ async function deployRailwayService(tenantRoot, service, {
1652
1664
  writePhase("resolve-context", `Resolved Railway service ${deployService.serviceName ?? deployService.serviceId ?? deployService.key}.`);
1653
1665
  writePhase("sync-runtime-config", "Syncing Railway runtime configuration.");
1654
1666
  const runtimeConfiguration = await timedRailwayPhase(timings, "railway:sync-runtime-config", () => withRailwayPhaseTimeout(
1655
- () => syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, { env: commandEnv, writePhase }),
1667
+ () => syncRailwayServiceRuntimeConfigurationAfterDeploy(tenantRoot, deployService, { env: commandEnv, writePhase, fetchImpl }),
1656
1668
  railwayPhaseTimeoutMs(commandEnv, "sync_runtime_config"),
1657
1669
  `Railway runtime configuration sync timed out for ${deployService.serviceName ?? deployService.key}.`
1658
1670
  ), { service: deployService.key });
@@ -1667,7 +1679,7 @@ async function deployRailwayService(tenantRoot, service, {
1667
1679
  };
1668
1680
  writePhase("device-login-vars", "Syncing Railway device-login variables.");
1669
1681
  await timedRailwayPhase(timings, "railway:device-login-vars", () => withRailwayPhaseTimeout(
1670
- () => syncRailwayApiDeviceLoginVariables(cliDeployService, commandEnv, write, taskPrefix),
1682
+ () => syncRailwayApiDeviceLoginVariables(cliDeployService, commandEnv, write, taskPrefix, fetchImpl),
1671
1683
  railwayPhaseTimeoutMs(commandEnv, "device_login_vars"),
1672
1684
  `Railway device-login variable sync timed out for ${cliDeployService.serviceName ?? cliDeployService.key}.`
1673
1685
  ), {
@@ -1690,7 +1702,8 @@ async function deployRailwayService(tenantRoot, service, {
1690
1702
  () => deployRailwayServiceInstance({
1691
1703
  serviceId: cliDeployService.serviceId,
1692
1704
  environmentId: cliDeployService.environmentId,
1693
- env: commandEnv
1705
+ env: commandEnv,
1706
+ fetchImpl
1694
1707
  }),
1695
1708
  railwayPhaseTimeoutMs(commandEnv, "deploy"),
1696
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) {
@@ -2003,9 +2006,9 @@ function failWorkflowRun(root, runId, error, recovery) {
2003
2006
  releaseWorkflowLock(root, runId);
2004
2007
  }
2005
2008
  function validatePackageReleaseWorkflows(root, packageNames) {
2006
- const missing = checkedOutWorkspacePackageRepos(root).filter((pkg) => packageNames.includes(pkg.name)).filter((pkg) => !existsSync(resolve(pkg.dir, ".github/workflows/publish.yml"))).map((pkg) => pkg.name);
2009
+ const missing = checkedOutWorkspacePackageRepos(root).filter((pkg) => packageNames.includes(pkg.name)).map((pkg) => ({ pkg, workflow: releaseWorkflowForPackage(root, pkg.name) })).filter((entry) => !existsSync(resolve(entry.pkg.dir, ".github/workflows", entry.workflow))).map((entry) => `${entry.pkg.name} (${entry.workflow})`);
2007
2010
  if (missing.length > 0) {
2008
- workflowError("release", "workflow_contract_missing", `Treeseed release requires .github/workflows/publish.yml in: ${missing.join(", ")}.`, {
2011
+ workflowError("release", "workflow_contract_missing", `Treeseed release requires package release workflows in: ${missing.join(", ")}.`, {
2009
2012
  details: {
2010
2013
  missing
2011
2014
  }
@@ -2013,9 +2016,9 @@ function validatePackageReleaseWorkflows(root, packageNames) {
2013
2016
  }
2014
2017
  }
2015
2018
  function releaseWorkflowForPackage(root, packageName) {
2016
- void root;
2017
- void packageName;
2018
- return "publish.yml";
2019
+ const adapter = discoverTreeseedPackageAdapters(root).find((entry) => entry.id === packageName || entry.name === packageName);
2020
+ const configured = typeof adapter?.metadata?.dockerImageReleaseWorkflow === "string" ? adapter.metadata.dockerImageReleaseWorkflow : null;
2021
+ return configured && configured.trim() ? configured.trim().replace(/^\.github\/workflows\//u, "") : "publish.yml";
2019
2022
  }
2020
2023
  function productionDeployWorkflowForPackage(root, packageName) {
2021
2024
  const adapter = discoverTreeseedPackageAdapters(root).find((entry) => entry.id === packageName || entry.name === packageName);
@@ -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.33",
3
+ "version": "0.12.35",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {