完善 readiness、发布追溯与敏感治理(07-15-production-trust-closeout-cleanup)
这个提交包含在:
@@ -0,0 +1,143 @@
|
||||
#!/usr/bin/env node
|
||||
import { spawnSync } from "node:child_process";
|
||||
import path from "node:path";
|
||||
import process from "node:process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
export class SourceProvenanceError extends Error {
|
||||
constructor(code) {
|
||||
super(code);
|
||||
this.name = "SourceProvenanceError";
|
||||
this.code = code;
|
||||
}
|
||||
}
|
||||
|
||||
const ALLOWED_UNPUBLISHED_REASONS = new Set([
|
||||
"offline-disaster-recovery",
|
||||
"remote-outage-approved",
|
||||
"controlled-local-recovery",
|
||||
]);
|
||||
|
||||
function runGit(rootDir, args) {
|
||||
return spawnSync("git", args, {
|
||||
cwd: rootDir,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
}
|
||||
|
||||
function gitText(rootDir, args, failureCode) {
|
||||
const result = runGit(rootDir, args);
|
||||
if (result.status !== 0) {
|
||||
throw new SourceProvenanceError(failureCode);
|
||||
}
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
export function collectSourceProvenance({ rootDir, allowUnpublished = false }) {
|
||||
const inside = gitText(rootDir, ["rev-parse", "--is-inside-work-tree"], "not_a_git_worktree");
|
||||
if (inside !== "true") {
|
||||
throw new SourceProvenanceError("not_a_git_worktree");
|
||||
}
|
||||
|
||||
const dirty = gitText(
|
||||
rootDir,
|
||||
["status", "--porcelain=v1", "--untracked-files=all"],
|
||||
"git_status_failed",
|
||||
);
|
||||
if (dirty) {
|
||||
throw new SourceProvenanceError("dirty_worktree");
|
||||
}
|
||||
|
||||
const commit = gitText(rootDir, ["rev-parse", "--verify", "HEAD"], "commit_unavailable");
|
||||
const branch = gitText(rootDir, ["branch", "--show-current"], "branch_unavailable") || "detached";
|
||||
const upstreamResult = runGit(rootDir, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]);
|
||||
const upstream = upstreamResult.status === 0 ? upstreamResult.stdout.trim() : null;
|
||||
if (!upstream && !allowUnpublished) {
|
||||
throw new SourceProvenanceError("upstream_missing");
|
||||
}
|
||||
|
||||
const published = Boolean(upstream) && runGit(rootDir, ["merge-base", "--is-ancestor", commit, upstream]).status === 0;
|
||||
if (!published && !allowUnpublished) {
|
||||
throw new SourceProvenanceError("commit_not_in_upstream");
|
||||
}
|
||||
|
||||
return {
|
||||
status: "PASS",
|
||||
commit,
|
||||
shortCommit: commit.slice(0, 12),
|
||||
branch,
|
||||
upstream,
|
||||
published,
|
||||
overrideUsed: !published,
|
||||
};
|
||||
}
|
||||
|
||||
export function refreshUpstreamRef({ rootDir, allowFailure = false }) {
|
||||
const branch = gitText(rootDir, ["branch", "--show-current"], "branch_unavailable");
|
||||
const upstreamResult = runGit(rootDir, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]);
|
||||
const upstream = upstreamResult.status === 0 ? upstreamResult.stdout.trim() : null;
|
||||
if (!upstream) {
|
||||
if (allowFailure) {
|
||||
return { refreshed: false, upstream: null, remote: null, code: "upstream_missing" };
|
||||
}
|
||||
throw new SourceProvenanceError("upstream_missing");
|
||||
}
|
||||
|
||||
const remoteResult = runGit(rootDir, ["config", "--get", `branch.${branch}.remote`]);
|
||||
const remote = remoteResult.status === 0 ? remoteResult.stdout.trim() : null;
|
||||
if (!remote || remote === ".") {
|
||||
if (allowFailure) {
|
||||
return { refreshed: false, upstream, remote: null, code: "upstream_remote_missing" };
|
||||
}
|
||||
throw new SourceProvenanceError("upstream_remote_missing");
|
||||
}
|
||||
|
||||
const fetchResult = runGit(rootDir, ["fetch", "--quiet", "--prune", remote]);
|
||||
if (fetchResult.status !== 0) {
|
||||
if (allowFailure) {
|
||||
return { refreshed: false, upstream, remote, code: "upstream_refresh_failed" };
|
||||
}
|
||||
throw new SourceProvenanceError("upstream_refresh_failed");
|
||||
}
|
||||
return { refreshed: true, upstream, remote, code: null };
|
||||
}
|
||||
|
||||
function parseRootArg(argv) {
|
||||
const item = argv.find((value) => value.startsWith("--root="));
|
||||
if (item) {
|
||||
return path.resolve(item.slice("--root=".length));
|
||||
}
|
||||
return path.resolve(new URL("../..", import.meta.url).pathname);
|
||||
}
|
||||
|
||||
function main() {
|
||||
try {
|
||||
const rootDir = parseRootArg(process.argv.slice(2));
|
||||
const allowUnpublished = process.env.DEPLOY_ALLOW_UNPUBLISHED_COMMIT === "1";
|
||||
const overrideReason = String(process.env.DEPLOY_UNPUBLISHED_REASON ?? "").trim();
|
||||
if (allowUnpublished && !ALLOWED_UNPUBLISHED_REASONS.has(overrideReason)) {
|
||||
throw new SourceProvenanceError("unpublished_override_reason_invalid");
|
||||
}
|
||||
|
||||
collectSourceProvenance({ rootDir, allowUnpublished: true });
|
||||
const refresh = refreshUpstreamRef({ rootDir, allowFailure: allowUnpublished });
|
||||
const result = collectSourceProvenance({ rootDir, allowUnpublished });
|
||||
console.log(JSON.stringify({
|
||||
...result,
|
||||
remoteVerified: refresh.refreshed,
|
||||
upstreamRefreshCode: refresh.code,
|
||||
overrideRequested: allowUnpublished,
|
||||
overrideReason: allowUnpublished ? overrideReason : null,
|
||||
}));
|
||||
} catch (error) {
|
||||
const code = error instanceof SourceProvenanceError ? error.code : "unexpected_error";
|
||||
console.error(JSON.stringify({ status: "FAIL", code }));
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
|
||||
const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
||||
if (isMain) {
|
||||
main();
|
||||
}
|
||||
在新工单中引用
屏蔽一个用户