@treeseed/sdk 0.12.56 → 0.12.59

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.
@@ -40,11 +40,11 @@ export declare function rewriteInternalDependenciesToStableVersions(root: any, v
40
40
  repoName: string;
41
41
  packageJsonPath: string;
42
42
  })[];
43
- export declare function rewriteProjectInternalDependenciesToStableVersions(root: any, versions: Map<string, string>): (RewrittenDevReference & {
43
+ export declare function rewriteProjectInternalDependenciesToStableVersions(root: any, versions: Map<string, string>, targetPackageNames?: ReadonlySet<string>): (RewrittenDevReference & {
44
44
  repoName: string;
45
45
  packageJsonPath: string;
46
46
  })[];
47
- export declare function collectInternalDevReferenceIssues(root?: any, packageNames?: Set<any>): {
47
+ export declare function collectInternalDevReferenceIssues(root?: any, packageNames?: Set<any>, targetPackageNames?: ReadonlySet<string>): {
48
48
  repoName: string;
49
49
  filePath: string;
50
50
  field?: string;
@@ -182,12 +182,12 @@ function rewriteInternalDependenciesToStableVersions(root = workspaceRoot(), ver
182
182
  }
183
183
  return rewrites;
184
184
  }
185
- function rewriteProjectInternalDependenciesToStableVersions(root = workspaceRoot(), versions) {
185
+ function rewriteProjectInternalDependenciesToStableVersions(root = workspaceRoot(), versions, targetPackageNames) {
186
186
  const rewrites = [];
187
187
  const installableVersions = installableInternalDependencyVersions(root, versions);
188
188
  const targets = [
189
189
  { name: "@treeseed/market", dir: root },
190
- ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))
190
+ ...workspacePackages(root).filter((pkg) => !targetPackageNames || targetPackageNames.has(pkg.name)).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))
191
191
  ];
192
192
  for (const target of targets) {
193
193
  const packageJsonPath = resolve(target.dir, "package.json");
@@ -216,11 +216,12 @@ function rewriteProjectInternalDependenciesToStableVersions(root = workspaceRoot
216
216
  }
217
217
  return rewrites;
218
218
  }
219
- function collectInternalDevReferenceIssues(root = workspaceRoot(), packageNames = new Set(workspacePackages(root).map((pkg) => pkg.name))) {
219
+ function collectInternalDevReferenceIssues(root = workspaceRoot(), packageNames = new Set(workspacePackages(root).map((pkg) => pkg.name)), targetPackageNames) {
220
220
  const issues = [];
221
+ const workspaceTargets = workspacePackages(root).filter((pkg) => !targetPackageNames || targetPackageNames.has(pkg.name)).map((pkg) => ({ name: pkg.name, dir: pkg.dir }));
221
222
  const manifestRoots = [
222
223
  { name: "@treeseed/market", dir: root },
223
- ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))
224
+ ...workspaceTargets
224
225
  ];
225
226
  for (const pkg of manifestRoots) {
226
227
  const packageJsonPath = resolve(pkg.dir, "package.json");
@@ -239,7 +240,7 @@ function collectInternalDevReferenceIssues(root = workspaceRoot(), packageNames
239
240
  }
240
241
  }
241
242
  }
242
- const lockRoots = [{ name: "@treeseed/market", dir: root }, ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))];
243
+ const lockRoots = [{ name: "@treeseed/market", dir: root }, ...workspaceTargets];
243
244
  for (const lockRoot of lockRoots) {
244
245
  for (const lockName of ["package-lock.json", "npm-shrinkwrap.json"]) {
245
246
  const lockPath = resolve(lockRoot.dir, lockName);
@@ -944,9 +944,10 @@ async function runGitDependencySmoke(node, options, reference) {
944
944
  void reference;
945
945
  }
946
946
  async function runNpmInstallWithRetry(node, options, gitDependencyRefreshSpecs = []) {
947
- if (shouldSkipNetworkInstall()) {
948
- emitProgress(options, node, "install", "Skipped npm install because network install mode is disabled.");
949
- return { status: "skipped", attempts: 0, reason: "disabled" };
947
+ if (shouldSkipNetworkInstall() || options.deferPushUntilVerified === true) {
948
+ const reason = options.deferPushUntilVerified === true ? "atomic-save" : "disabled";
949
+ emitProgress(options, node, "install", `Skipped npm install because ${reason === "atomic-save" ? "atomic save validates local lock metadata before publishing commits" : "network install mode is disabled"}.`);
950
+ return { status: "skipped", attempts: 0, reason };
950
951
  }
951
952
  let lastError = null;
952
953
  const packageJson = node.packageJson ?? (existsSync(resolve(node.path, "package.json")) ? readJson(resolve(node.path, "package.json")) : null);
@@ -1030,9 +1031,10 @@ async function validateRepositoryLockfile(node, options) {
1030
1031
  }
1031
1032
  const { command, args } = lockfileValidationCommand(node, options);
1032
1033
  const commandText = `${command} ${args.join(" ")}`;
1033
- if (shouldSkipNetworkInstall()) {
1034
- emitProgress(options, node, "lockfile", `Skipped ${commandText} because network install mode is disabled.`);
1035
- return { status: "skipped", command: commandText, issues: [], error: "disabled" };
1034
+ if (shouldSkipNetworkInstall() || options.deferPushUntilVerified === true) {
1035
+ const reason = options.deferPushUntilVerified === true ? "atomic-save" : "disabled";
1036
+ emitProgress(options, node, "lockfile", `Validated lockfile structure without ${commandText} because ${reason === "atomic-save" ? "upstream commits are intentionally unpublished" : "network install mode is disabled"}.`);
1037
+ return { status: "passed", command: "treeseed structural lockfile validation", issues: [], error: null };
1036
1038
  }
1037
1039
  try {
1038
1040
  runCapturedCommand(node, options, "lockfile", command, args, { timeoutMs: lockfileValidationTimeoutMs(node, options), emitOutputOnSuccess: false });
@@ -1773,8 +1775,9 @@ async function saveOneRepository(node, options, state) {
1773
1775
  return value && typeof value === "object" && !Array.isArray(value) ? Object.keys(value) : [];
1774
1776
  }));
1775
1777
  const gitDependencyRefreshReferences = [...state.finalizedReferences.values()].filter((reference) => reference.mode === "dev-git-commit" && directDependencyNames.has(reference.packageName));
1778
+ const deferredGitDependencyValidation = options.deferPushUntilVerified === true && gitDependencyRefreshReferences.length > 0;
1776
1779
  const lockfileGitDependenciesSynced = syncDirectGitDependencyLockfileEntries(node, options, gitDependencyRefreshReferences);
1777
- if (!isRootWorkspaceRepository(node, options) && (lockfileGitDependenciesSynced || gitDependencyRefreshReferences.length > 0 && !existsSync(resolve(node.path, "package-lock.json")))) {
1780
+ if (!isRootWorkspaceRepository(node, options) && (lockfileGitDependenciesSynced || gitDependencyRefreshReferences.length > 0 && !existsSync(resolve(node.path, "package-lock.json"))) && !deferredGitDependencyValidation) {
1778
1781
  validateStandaloneGitDependencyLockfile(node, options);
1779
1782
  }
1780
1783
  const gitDependencyRefreshSpecs = lockfileGitDependenciesSynced ? [] : gitDependencyRefreshReferences.map((reference) => `${reference.packageName}@${reference.installSpec ?? reference.spec}`);
@@ -1809,7 +1812,7 @@ async function saveOneRepository(node, options, state) {
1809
1812
  } else if (node.kind === "project" && (dependencyChanged || node.path === options.root && submodulesChanged) && hasNpmLockfile(node.path)) {
1810
1813
  report.install = await runNpmInstallWithRetry(node, options, gitDependencyRefreshSpecs);
1811
1814
  }
1812
- if (!isRootWorkspaceRepository(node, options) && hasNpmLockfile(node.path) && (packageNeedsVersion || dependencyChanged)) {
1815
+ if (!isRootWorkspaceRepository(node, options) && hasNpmLockfile(node.path) && (packageNeedsVersion || dependencyChanged) && !deferredGitDependencyValidation) {
1813
1816
  validateStandaloneGitDependencyLockfile(node, options);
1814
1817
  }
1815
1818
  if (hasNpmLockfile(node.path) && (node.kind === "project" || packageNeedsVersion || dependencyChanged || submodulesChanged)) {
@@ -1144,6 +1144,10 @@ export declare function workflowRelease(helpers: WorkflowOperationHelpers, input
1144
1144
  cached: boolean;
1145
1145
  }[];
1146
1146
  };
1147
+ productionImageRefs: {
1148
+ persisted: Record<string, string>;
1149
+ readiness: import("../workflow-support.js").TreeseedDeploymentReadinessReport;
1150
+ };
1147
1151
  backMerge: {
1148
1152
  packages: {
1149
1153
  status: string;
@@ -17,6 +17,7 @@ import {
17
17
  applyTreeseedSafeRepairs,
18
18
  assertTreeseedCommandEnvironment,
19
19
  checkTreeseedProviderConnections,
20
+ collectTreeseedEnvironmentContext,
20
21
  collectTreeseedConfigContext,
21
22
  collectTreeseedConfigSeedValues,
22
23
  collectTreeseedPrintEnvReport,
@@ -33,6 +34,7 @@ import {
33
34
  resolveTreeseedRemoteSession,
34
35
  rotateTreeseedMachineKey,
35
36
  setTreeseedRemoteSession,
37
+ setTreeseedMachineEnvironmentValue,
36
38
  writeTreeseedMachineConfig
37
39
  } from "../operations/services/config-runtime.js";
38
40
  import { formatTreeseedDependencyFailureDetails, installTreeseedDependencies } from "../managed-dependencies.js";
@@ -746,6 +748,20 @@ function productionReleaseImageRefVersions(root, selectedVersions) {
746
748
  }
747
749
  return versions;
748
750
  }
751
+ function persistProductionReleaseImageRefs(root, releaseImageRefs) {
752
+ const registry = collectTreeseedEnvironmentContext(root);
753
+ const entries = new Map(registry.entries.map((entry) => [entry.id, entry]));
754
+ const persisted = {};
755
+ for (const [id, value] of Object.entries(releaseImageRefs)) {
756
+ const entry = entries.get(id) ?? { id, storage: "scoped", sensitivity: "plain" };
757
+ if (entry.sensitivity === "secret") {
758
+ workflowError("release", "validation_failed", `Production release image ref ${id} must be a non-secret environment value.`);
759
+ }
760
+ setTreeseedMachineEnvironmentValue(root, "prod", entry, value);
761
+ persisted[id] = value;
762
+ }
763
+ return persisted;
764
+ }
749
765
  function stableVersionLine(version) {
750
766
  const match = version.match(/^(\d+\.\d+)\.\d+(?:-[0-9A-Za-z.-]+)?$/u);
751
767
  return match?.[1] ?? null;
@@ -1194,8 +1210,12 @@ function updatePackageLockRootVersion(root, version) {
1194
1210
  }
1195
1211
  return { status: changed ? "updated" : "unchanged", path: "package-lock.json" };
1196
1212
  }
1197
- function applyStableWorkspaceVersionChanges(root, versions) {
1198
- for (const target of [{ name: "@treeseed/market", dir: root }, ...workspacePackages(root).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))]) {
1213
+ function applyStableWorkspaceVersionChanges(root, versions, targetPackageNames) {
1214
+ const targets = [
1215
+ { name: "@treeseed/market", dir: root },
1216
+ ...workspacePackages(root).filter((pkg) => targetPackageNames.has(pkg.name)).map((pkg) => ({ name: pkg.name, dir: pkg.dir }))
1217
+ ];
1218
+ for (const target of targets) {
1199
1219
  const packageJsonPath = resolve(target.dir, "package.json");
1200
1220
  if (!existsSync(packageJsonPath)) continue;
1201
1221
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
@@ -1220,7 +1240,7 @@ function applyStableWorkspaceVersionChanges(root, versions) {
1220
1240
  writeJsonFile(packageJsonPath, packageJson);
1221
1241
  }
1222
1242
  }
1223
- rewriteProjectInternalDependenciesToStableVersions(root, versions);
1243
+ rewriteProjectInternalDependenciesToStableVersions(root, versions, targetPackageNames);
1224
1244
  }
1225
1245
  function gitObjectCommit(repoDir, ref) {
1226
1246
  try {
@@ -2221,7 +2241,7 @@ function runReleaseNpmInstall(repoDir, options = {}) {
2221
2241
  if (shouldSkipReleaseInstall()) {
2222
2242
  return { status: "skipped", reason: "disabled" };
2223
2243
  }
2224
- const args = repoDir === options.workspaceRoot ? ["install", "--package-lock-only", "--ignore-scripts", "--no-audit", "--no-fund"] : ["install", "--package-lock-only", "--ignore-scripts", "--workspaces=false", "--no-audit", "--no-fund"];
2244
+ const args = repoDir === options.workspaceRoot ? ["install", "--package-lock-only", "--ignore-scripts", "--workspaces=false", "--no-audit", "--no-fund"] : ["install", "--package-lock-only", "--ignore-scripts", "--workspaces=false", "--no-audit", "--no-fund"];
2225
2245
  const spawnCommand = npmCommandForWorkflowSpawn(args);
2226
2246
  let lastDetail = "";
2227
2247
  for (let attempt = 1; attempt <= 10; attempt += 1) {
@@ -4718,7 +4738,7 @@ async function workflowSave(helpers, input) {
4718
4738
  verifyMode: normalizeSaveVerifyMode(effectiveInput.verify === false ? "skip" : effectiveInput.verifyMode),
4719
4739
  commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
4720
4740
  workflowRunId: workflowRun.runId,
4721
- deferPushUntilVerified: false,
4741
+ deferPushUntilVerified: true,
4722
4742
  onProgress: (line, stream) => helpers.write(line, stream),
4723
4743
  onWaveSaved: branch === STAGING_BRANCH && shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? async ({ nodes, reports, rootRepo: waveRootRepo }) => {
4724
4744
  const nonRootReportsForWave = reports.filter((repo, index) => nodes[index]?.id !== ".");
@@ -5691,10 +5711,12 @@ ${currentBlockers.map((entry) => `- ${entry}`).join("\n")}`, {
5691
5711
  { root, runId: workflowRun.runId, onProgress: (line, stream) => helpers.write(line, stream) }
5692
5712
  )) : (skipJournalStep(root, workflowRun.runId, "hosted-ci", { skippedReason: "ci off" }), { status: "skipped", reason: "ci off" });
5693
5713
  const workspaceLinks = await executeJournalStep(root, workflowRun.runId, "workspace-link-restore", () => ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto"));
5694
- for (const repo of checkedOutStagePromotionRepos(root)) {
5695
- syncBranchWithOrigin(repo.dir, STAGING_BRANCH);
5714
+ if (!managedWorkflowWorktreeMetadata(root)) {
5715
+ for (const repo of checkedOutStagePromotionRepos(root)) {
5716
+ syncBranchWithOrigin(repo.dir, STAGING_BRANCH);
5717
+ }
5718
+ syncBranchWithOrigin(repoRoot(root), STAGING_BRANCH);
5696
5719
  }
5697
- syncBranchWithOrigin(repoRoot(root), STAGING_BRANCH);
5698
5720
  const cleanup = cleanupMode === "success" ? await executeJournalStep(root, workflowRun.runId, "cleanup-source", () => cleanupStageSourceBranches(root, featureBranch, typedManifest)) : (skipJournalStep(root, workflowRun.runId, "cleanup-source", { skippedReason: "manual cleanup selected" }), { status: "skipped", reason: "manual cleanup selected" });
5699
5721
  const payload = {
5700
5722
  ...basePayload,
@@ -5996,6 +6018,7 @@ ${blockers.join("\n")}`, {
5996
6018
  },
5997
6019
  { id: "verify-published-artifacts", description: "Verify immutable registry artifacts exist after publish workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
5998
6020
  { id: "production-package-deploy-workflows", description: "Wait for production package deploy workflows before live verification", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
6021
+ { id: "persist-production-image-refs", description: "Persist released production image refs and verify deployment readiness", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
5999
6022
  { id: "release-root", description: `Release market ${plannedRelease.rootVersion}`, repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
6000
6023
  { id: "publish-wait", description: "Wait for production release workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
6001
6024
  { id: "release-back-merge", description: "Back-merge production release history into staging", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
@@ -6038,14 +6061,14 @@ ${blockers.join("\n")}`, {
6038
6061
  });
6039
6062
  const workspaceUnlink = await executeJournalStep(root, workflowRun.runId, "workspace-unlink", () => unlinkWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto"));
6040
6063
  const releaseMetadata = await executeJournalStep(root, workflowRun.runId, "prepare-release-metadata", () => {
6041
- applyStableWorkspaceVersionChanges(root, allVersions);
6064
+ applyStableWorkspaceVersionChanges(root, allVersions, selectedPackageSet);
6042
6065
  const adapterMetadata = checkedOutWorkspacePackageRepos(root).filter((pkg) => selectedPackageSet.has(pkg.name)).map((pkg) => ({
6043
6066
  name: pkg.name,
6044
6067
  version: selectedVersions.get(pkg.name) ?? null,
6045
6068
  result: selectedVersions.has(pkg.name) ? { status: "pending-package-release-step" } : { status: "skipped", reason: "no planned version" }
6046
6069
  }));
6047
6070
  const rootPackageLock = updatePackageLockRootVersion(root, plannedRelease.rootVersion);
6048
- const remainingDevReferences = collectInternalDevReferenceIssues(root, selectedPackageSet).filter((issue) => !issue.reason.startsWith("lockfile-"));
6071
+ const remainingDevReferences = collectInternalDevReferenceIssues(root, selectedPackageSet, selectedPackageSet).filter((issue) => !issue.reason.startsWith("lockfile-"));
6049
6072
  if (remainingDevReferences.length > 0) {
6050
6073
  const rendered = remainingDevReferences.map((issue) => `${issue.repoName}: ${issue.filePath} ${issue.dependencyName ?? ""} ${issue.reason} ${issue.spec}`).join("\n");
6051
6074
  throw new Error(`Stable release metadata still contains development references.
@@ -6128,6 +6151,26 @@ ${rendered}`);
6128
6151
  onProgress: (line, stream) => helpers.write(line, stream)
6129
6152
  }).then((workflowGates) => ({ workflowGates }));
6130
6153
  });
6154
+ const productionImageRefs = await executeJournalStep(root, workflowRun.runId, "persist-production-image-refs", async () => {
6155
+ const persisted = persistProductionReleaseImageRefs(root, releaseImageRefs);
6156
+ if (helpers.context.env) {
6157
+ Object.assign(helpers.context.env, persisted);
6158
+ Object.assign(process.env, persisted);
6159
+ }
6160
+ const readiness = await withContextEnv(persisted, () => collectTreeseedDeploymentReadiness({
6161
+ tenantRoot: root,
6162
+ environment: "prod",
6163
+ appId: singleSelectedWorkflowAppId(plannedRelease.applicationSelection)
6164
+ }));
6165
+ const failures = readiness.checks.filter((check) => check.status === "failed").map((check) => `${check.id}: ${check.message}${check.remediation ? ` Remediation: ${check.remediation}` : ""}`);
6166
+ if (failures.length > 0) {
6167
+ workflowError("release", "hosted_live_verification_failed", `Production readiness failed after persisting released image refs:
6168
+ ${failures.join("\n")}`, {
6169
+ details: { releaseImageRefs: persisted, readiness }
6170
+ });
6171
+ }
6172
+ return { persisted, readiness };
6173
+ });
6131
6174
  const rootRelease = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
6132
6175
  const rootInstall = runReleaseNpmInstall(root, { workspaceRoot: root });
6133
6176
  const changelog = updateReleaseChangelog(repoRoot(root), {
@@ -6201,6 +6244,7 @@ ${rendered}`);
6201
6244
  publishWait: publishWait.workflowGates,
6202
6245
  publishedArtifacts,
6203
6246
  productionPackageDeployWorkflows,
6247
+ productionImageRefs,
6204
6248
  backMerge,
6205
6249
  workspaceLinks,
6206
6250
  releasedCommit: String(rootRelease.commit.commitSha ?? ""),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@treeseed/sdk",
3
- "version": "0.12.56",
3
+ "version": "0.12.59",
4
4
  "description": "Shared Treeseed SDK for content-backed and D1-backed object models.",
5
5
  "license": "AGPL-3.0-only",
6
6
  "repository": {