文件
hyb-oa/deploy/trialrun/check-source-provenance.mjs

157 行
5.2 KiB
JavaScript

#!/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",
]);
export function deriveCandidateBuildEnv(commit) {
const normalized = String(commit ?? "").trim();
if (!/^[0-9a-f]{40,64}$/u.test(normalized)) {
throw new SourceProvenanceError("commit_invalid");
}
return Object.freeze({
BUILD_SHA: normalized,
VITE_BUILD_SHA: normalized.slice(0, 12),
});
}
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 buildEnv = deriveCandidateBuildEnv(commit);
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: buildEnv.BUILD_SHA,
shortCommit: buildEnv.VITE_BUILD_SHA,
buildEnv,
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();
}