@treeseed/sdk 0.12.46 → 0.12.48
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.
- package/dist/operations/services/git-workflow.js +6 -1
- package/dist/operations/services/repository-save-orchestrator.js +3 -1
- package/dist/reconcile/providers/release-private.d.ts +6 -19
- package/dist/reconcile/providers/release-private.js +24 -80
- package/dist/workflow/operations.js +6 -2
- package/package.json +1 -1
|
@@ -440,7 +440,12 @@ function checkoutTaskBranchFromStaging(cwd, branchName, { createIfMissing = true
|
|
|
440
440
|
function checkoutNewTaskBranchWithChanges(cwd, branchName, { pushIfCreated = false } = {}) {
|
|
441
441
|
const repoDir = repoRoot(cwd);
|
|
442
442
|
if (currentBranch(repoDir) !== STAGING_BRANCH) {
|
|
443
|
-
|
|
443
|
+
const stagingHead = remoteBranchExists(repoDir, STAGING_BRANCH) ? remoteHeadCommit(repoDir, STAGING_BRANCH) : null;
|
|
444
|
+
const canNormalizeStagingCheckout = gitStatusPorcelain(repoDir).length === 0 && stagingHead !== null && headCommit(repoDir) === stagingHead;
|
|
445
|
+
if (!canNormalizeStagingCheckout) {
|
|
446
|
+
throw new Error(`Dirty change adoption requires ${repoDir} to be on ${STAGING_BRANCH}.`);
|
|
447
|
+
}
|
|
448
|
+
syncBranchWithOrigin(repoDir, STAGING_BRANCH);
|
|
444
449
|
}
|
|
445
450
|
if (branchExists(repoDir, branchName) || remoteBranchExists(repoDir, branchName)) {
|
|
446
451
|
throw new Error(`Dirty change adoption requires a new branch; ${branchName} already exists.`);
|
|
@@ -731,6 +731,8 @@ function syncDirectGitDependencyLockfileEntries(node, options, references) {
|
|
|
731
731
|
let changed = false;
|
|
732
732
|
for (const reference of references) {
|
|
733
733
|
const manifestSpec = reference.manifestSpec ?? reference.spec;
|
|
734
|
+
const declaredSpec = ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"].map((field) => node.packageJson?.[field]).filter((value) => Boolean(value && typeof value === "object" && !Array.isArray(value))).map((dependencies) => dependencies[reference.packageName]).find((value) => typeof value === "string");
|
|
735
|
+
if (declaredSpec !== manifestSpec) continue;
|
|
734
736
|
const visitDependencyMaps = (value) => {
|
|
735
737
|
if (!value || typeof value !== "object") return;
|
|
736
738
|
if (Array.isArray(value)) {
|
|
@@ -743,7 +745,7 @@ function syncDirectGitDependencyLockfileEntries(node, options, references) {
|
|
|
743
745
|
if (!dependencies || typeof dependencies !== "object" || Array.isArray(dependencies)) continue;
|
|
744
746
|
const dependencyMap = dependencies;
|
|
745
747
|
const current = dependencyMap[reference.packageName];
|
|
746
|
-
if (typeof current === "string" &&
|
|
748
|
+
if (typeof current === "string" && current !== manifestSpec) {
|
|
747
749
|
dependencyMap[reference.packageName] = manifestSpec;
|
|
748
750
|
changed = true;
|
|
749
751
|
}
|
|
@@ -8,30 +8,17 @@ export declare function runReleaseVerifyCommand(input: {
|
|
|
8
8
|
ok: boolean;
|
|
9
9
|
skipped: boolean;
|
|
10
10
|
reason: string;
|
|
11
|
-
status?: undefined;
|
|
12
|
-
signal?: undefined;
|
|
13
|
-
command?: undefined;
|
|
14
11
|
dependencies?: undefined;
|
|
15
|
-
stdout?: undefined;
|
|
16
|
-
stderr?: undefined;
|
|
17
12
|
} | {
|
|
18
13
|
ok: boolean;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
command: import("../../index.js").TreeseedPackageCommand;
|
|
14
|
+
skipped: boolean;
|
|
15
|
+
reason: string;
|
|
22
16
|
dependencies: {
|
|
23
|
-
status: "
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
status: "restored";
|
|
28
|
-
} | {
|
|
29
|
-
status: "workspace-linked";
|
|
17
|
+
status: "staging-proof-reused";
|
|
18
|
+
candidatePath: string;
|
|
19
|
+
packageCommit: string;
|
|
20
|
+
rootCommit: string;
|
|
30
21
|
};
|
|
31
|
-
stdout: string;
|
|
32
|
-
stderr: string;
|
|
33
|
-
skipped?: undefined;
|
|
34
|
-
reason?: undefined;
|
|
35
22
|
}>;
|
|
36
23
|
export declare function runTemplateReleaseVerifyCommand(input: {
|
|
37
24
|
tenantRoot: string;
|
|
@@ -1,42 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { dirname, resolve } from "node:path";
|
|
4
4
|
import { findTreeseedPackageAdapter } from "../../operations/services/package-adapters.js";
|
|
5
5
|
import { checkedOutTemplateRepositories } from "../../operations/services/managed-repositories.js";
|
|
6
6
|
import { runTreeseedGitText } from "../../operations/services/git-runner.js";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
if (!existsSync(
|
|
10
|
-
|
|
7
|
+
function requireMatchingStageCandidate(tenantRoot, packageId, packageDir) {
|
|
8
|
+
const candidatePath = resolve(tenantRoot, ".treeseed/workflow/stage-candidates/latest.json");
|
|
9
|
+
if (!existsSync(candidatePath)) {
|
|
10
|
+
throw new Error(`Release verification requires a successful staged candidate; ${candidatePath} is missing.`);
|
|
11
11
|
}
|
|
12
|
-
const
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if (inspection.status === 0) return { status: "ready" };
|
|
19
|
-
input.onProgress?.("Package dependencies are incomplete; restoring the standalone lockfile installation.");
|
|
20
|
-
const install = spawnSync("npm", [
|
|
21
|
-
"ci",
|
|
22
|
-
"--ignore-scripts",
|
|
23
|
-
"--workspaces=false",
|
|
24
|
-
"--no-audit",
|
|
25
|
-
"--no-fund"
|
|
26
|
-
], {
|
|
27
|
-
cwd: input.packageDir,
|
|
28
|
-
env,
|
|
29
|
-
encoding: "utf8"
|
|
30
|
-
});
|
|
31
|
-
if (install.status !== 0) {
|
|
32
|
-
throw new Error([
|
|
33
|
-
"Standalone package dependency hydration failed before release verification.",
|
|
34
|
-
install.stderr,
|
|
35
|
-
install.stdout
|
|
36
|
-
].filter(Boolean).join("\n").trim());
|
|
12
|
+
const candidate = JSON.parse(readFileSync(candidatePath, "utf8"));
|
|
13
|
+
const packageProof = candidate.packages?.find((entry) => entry.name === packageId);
|
|
14
|
+
const packageHead = runTreeseedGitText(["rev-parse", "HEAD"], { cwd: packageDir, mode: "read" }).trim();
|
|
15
|
+
const rootHead = runTreeseedGitText(["rev-parse", "HEAD"], { cwd: tenantRoot, mode: "read" }).trim();
|
|
16
|
+
if (candidate.targetBranch !== "staging" || candidate.root?.verified !== true || candidate.root.commit !== rootHead || packageProof?.verified !== true || packageProof.commit !== packageHead) {
|
|
17
|
+
throw new Error(`Release verification requires ${packageId} and the Market root to match the latest verified staging candidate.`);
|
|
37
18
|
}
|
|
38
|
-
|
|
39
|
-
|
|
19
|
+
return {
|
|
20
|
+
status: "staging-proof-reused",
|
|
21
|
+
candidatePath,
|
|
22
|
+
packageCommit: packageHead,
|
|
23
|
+
rootCommit: rootHead
|
|
24
|
+
};
|
|
40
25
|
}
|
|
41
26
|
async function runReleaseVerifyCommand(input) {
|
|
42
27
|
const adapter = findTreeseedPackageAdapter(input.tenantRoot, input.packageId);
|
|
@@ -51,54 +36,13 @@ async function runReleaseVerifyCommand(input) {
|
|
|
51
36
|
reason: `${input.packageId} has no release verify command.`
|
|
52
37
|
};
|
|
53
38
|
}
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
return { status: "workspace-linked" };
|
|
57
|
-
})() : ensureReleaseVerifyDependencies({
|
|
58
|
-
tenantRoot: input.tenantRoot,
|
|
59
|
-
packageDir: adapter.dir,
|
|
60
|
-
env: input.env,
|
|
61
|
-
onProgress: input.onProgress
|
|
62
|
-
});
|
|
63
|
-
const renderedCommand = [command.command, ...command.args].join(" ");
|
|
64
|
-
input.onProgress?.(`Running ${input.packageId} release verification: ${renderedCommand}`);
|
|
65
|
-
const started = Date.now();
|
|
66
|
-
let stdout = "";
|
|
67
|
-
let stderr = "";
|
|
68
|
-
const result = await new Promise((resolve2, reject) => {
|
|
69
|
-
const child = spawn(command.command, command.args, {
|
|
70
|
-
cwd: command.cwd,
|
|
71
|
-
env: { ...process.env, ...input.env ?? {} },
|
|
72
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
73
|
-
});
|
|
74
|
-
const heartbeat = setInterval(() => {
|
|
75
|
-
input.onProgress?.(`Still running ${input.packageId} release verification after ${Math.round((Date.now() - started) / 1e3)}s.`);
|
|
76
|
-
}, 3e4);
|
|
77
|
-
child.stdout?.on("data", (chunk) => {
|
|
78
|
-
stdout += String(chunk);
|
|
79
|
-
});
|
|
80
|
-
child.stderr?.on("data", (chunk) => {
|
|
81
|
-
stderr += String(chunk);
|
|
82
|
-
});
|
|
83
|
-
child.on("error", (error) => {
|
|
84
|
-
clearInterval(heartbeat);
|
|
85
|
-
reject(error);
|
|
86
|
-
});
|
|
87
|
-
child.on("close", (status, signal) => {
|
|
88
|
-
clearInterval(heartbeat);
|
|
89
|
-
resolve2({ status, signal });
|
|
90
|
-
});
|
|
91
|
-
});
|
|
92
|
-
const elapsedSeconds = Math.round((Date.now() - started) / 1e3);
|
|
93
|
-
input.onProgress?.(`${input.packageId} release verification ${result.status === 0 ? "passed" : "failed"} in ${elapsedSeconds}s.`);
|
|
39
|
+
const stagingProof = requireMatchingStageCandidate(input.tenantRoot, input.packageId, adapter.dir);
|
|
40
|
+
input.onProgress?.(`Reusing exact-SHA staging verification for ${input.packageId} at ${stagingProof.packageCommit.slice(0, 12)}.`);
|
|
94
41
|
return {
|
|
95
|
-
ok:
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
dependencies,
|
|
100
|
-
stdout,
|
|
101
|
-
stderr
|
|
42
|
+
ok: true,
|
|
43
|
+
skipped: true,
|
|
44
|
+
reason: `${input.packageId} matches the latest verified staging candidate; tag workflows perform independent release verification before publication or deployment.`,
|
|
45
|
+
dependencies: stagingProof
|
|
102
46
|
};
|
|
103
47
|
}
|
|
104
48
|
function runTemplateCommand(command, cwd, env) {
|
|
@@ -2133,8 +2133,12 @@ function productionPackageDeployGates(root, versions) {
|
|
|
2133
2133
|
function prepareAdapterReleaseMetadata(root, pkg, version) {
|
|
2134
2134
|
const adapter = discoverTreeseedPackageAdapters(root).find((entry) => entry.id === pkg.name || entry.name === pkg.name);
|
|
2135
2135
|
if (adapter?.kind === "beam-elixir-rust" && existsSync(resolve(pkg.dir, "scripts", "bump-release-version.ts"))) {
|
|
2136
|
-
|
|
2137
|
-
|
|
2136
|
+
const tsx = resolve(root, "node_modules/.bin/tsx");
|
|
2137
|
+
if (!existsSync(tsx)) {
|
|
2138
|
+
throw new Error(`TreeSeed release requires the workspace tsx executable at ${tsx}. Run trsd install and restore workspace dependencies before retrying.`);
|
|
2139
|
+
}
|
|
2140
|
+
run(tsx, ["scripts/bump-release-version.ts", version], { cwd: pkg.dir });
|
|
2141
|
+
return { status: "updated", adapter: adapter.id, command: `${tsx} scripts/bump-release-version.ts` };
|
|
2138
2142
|
}
|
|
2139
2143
|
if (existsSync(resolve(pkg.dir, "package.json"))) {
|
|
2140
2144
|
return {
|