@treeseed/sdk 0.12.58 → 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.
@@ -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;
@@ -2225,7 +2241,7 @@ function runReleaseNpmInstall(repoDir, options = {}) {
2225
2241
  if (shouldSkipReleaseInstall()) {
2226
2242
  return { status: "skipped", reason: "disabled" };
2227
2243
  }
2228
- 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"];
2229
2245
  const spawnCommand = npmCommandForWorkflowSpawn(args);
2230
2246
  let lastDetail = "";
2231
2247
  for (let attempt = 1; attempt <= 10; attempt += 1) {
@@ -4722,7 +4738,7 @@ async function workflowSave(helpers, input) {
4722
4738
  verifyMode: normalizeSaveVerifyMode(effectiveInput.verify === false ? "skip" : effectiveInput.verifyMode),
4723
4739
  commitMessageMode: effectiveInput.commitMessageMode ?? "auto",
4724
4740
  workflowRunId: workflowRun.runId,
4725
- deferPushUntilVerified: false,
4741
+ deferPushUntilVerified: true,
4726
4742
  onProgress: (line, stream) => helpers.write(line, stream),
4727
4743
  onWaveSaved: branch === STAGING_BRANCH && shouldUseHostedSaveCi(effectiveInput, branch, saveLane) ? async ({ nodes, reports, rootRepo: waveRootRepo }) => {
4728
4744
  const nonRootReportsForWave = reports.filter((repo, index) => nodes[index]?.id !== ".");
@@ -5695,10 +5711,12 @@ ${currentBlockers.map((entry) => `- ${entry}`).join("\n")}`, {
5695
5711
  { root, runId: workflowRun.runId, onProgress: (line, stream) => helpers.write(line, stream) }
5696
5712
  )) : (skipJournalStep(root, workflowRun.runId, "hosted-ci", { skippedReason: "ci off" }), { status: "skipped", reason: "ci off" });
5697
5713
  const workspaceLinks = await executeJournalStep(root, workflowRun.runId, "workspace-link-restore", () => ensureWorkflowWorkspaceLinks(root, helpers, effectiveInput.workspaceLinks ?? "auto"));
5698
- for (const repo of checkedOutStagePromotionRepos(root)) {
5699
- 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);
5700
5719
  }
5701
- syncBranchWithOrigin(repoRoot(root), STAGING_BRANCH);
5702
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" });
5703
5721
  const payload = {
5704
5722
  ...basePayload,
@@ -6000,6 +6018,7 @@ ${blockers.join("\n")}`, {
6000
6018
  },
6001
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 },
6002
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 },
6003
6022
  { id: "release-root", description: `Release market ${plannedRelease.rootVersion}`, repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
6004
6023
  { id: "publish-wait", description: "Wait for production release workflows", repoName: rootRepo.name, repoPath: rootRepo.path, branch: PRODUCTION_BRANCH, resumable: true },
6005
6024
  { id: "release-back-merge", description: "Back-merge production release history into staging", repoName: rootRepo.name, repoPath: rootRepo.path, branch: STAGING_BRANCH, resumable: true },
@@ -6132,6 +6151,26 @@ ${rendered}`);
6132
6151
  onProgress: (line, stream) => helpers.write(line, stream)
6133
6152
  }).then((workflowGates) => ({ workflowGates }));
6134
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
+ });
6135
6174
  const rootRelease = await executeJournalStep(root, workflowRun.runId, "release-root", () => {
6136
6175
  const rootInstall = runReleaseNpmInstall(root, { workspaceRoot: root });
6137
6176
  const changelog = updateReleaseChangelog(repoRoot(root), {
@@ -6205,6 +6244,7 @@ ${rendered}`);
6205
6244
  publishWait: publishWait.workflowGates,
6206
6245
  publishedArtifacts,
6207
6246
  productionPackageDeployWorkflows,
6247
+ productionImageRefs,
6208
6248
  backMerge,
6209
6249
  workspaceLinks,
6210
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.58",
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": {