@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
|
-
|
|
949
|
-
|
|
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
|
-
|
|
1035
|
-
|
|
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:
|
|
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
|
-
|
|
5699
|
-
|
|
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 ?? ""),
|