134 lines
3.8 KiB
JavaScript
134 lines
3.8 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { execFileSync } from "node:child_process";
|
|
import { writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
const args = process.argv.slice(2);
|
|
const currentBranch = git(["branch", "--show-current"]).trim() || "HEAD";
|
|
const configuredDeploymentBranch = getOptionalGitConfig("deployment.branch");
|
|
const requiredBranch =
|
|
String(process.env.REQUIRED_BRANCH || configuredDeploymentBranch || "").trim();
|
|
const positionalBaseRef = args.find((arg) => !arg.startsWith("--"));
|
|
const upstreamRef = getOptionalUpstreamRef();
|
|
const baseRef =
|
|
process.env.PR_BASE_REF || positionalBaseRef || upstreamRef || "origin/main";
|
|
const requireUpToDate =
|
|
process.env.REQUIRE_UP_TO_DATE === "true" || args.includes("--require-up-to-date");
|
|
const allowDirty = process.env.ALLOW_DIRTY === "true" || args.includes("--allow-dirty");
|
|
|
|
const failures = [];
|
|
const warnings = [];
|
|
|
|
const status = git(["status", "--porcelain"]);
|
|
if (status.trim() && !allowDirty) {
|
|
failures.push("working tree has uncommitted changes; commit, stash, or set ALLOW_DIRTY=true");
|
|
}
|
|
if (requiredBranch && currentBranch !== requiredBranch) {
|
|
failures.push(
|
|
`current branch is ${currentBranch}, but deployment requires ${requiredBranch}`
|
|
);
|
|
}
|
|
|
|
try {
|
|
git(["rev-parse", "--verify", baseRef]);
|
|
} catch {
|
|
failures.push(`base ref not found: ${baseRef}. Run \`git fetch\` or set PR_BASE_REF.`);
|
|
}
|
|
|
|
if (failures.length === 0) {
|
|
const mergeBase = git(["merge-base", "HEAD", baseRef]).trim();
|
|
const [behindText, aheadText] = git(["rev-list", "--left-right", "--count", `${baseRef}...HEAD`])
|
|
.trim()
|
|
.split(/\s+/);
|
|
const behind = Number.parseInt(behindText || "0", 10);
|
|
const ahead = Number.parseInt(aheadText || "0", 10);
|
|
const changedFiles = git(["diff", "--name-only", `${baseRef}...HEAD`])
|
|
.split(/\r?\n/)
|
|
.filter(Boolean).length;
|
|
|
|
const mergeTree = runMergeTree(mergeBase, baseRef);
|
|
const conflictLines = mergeTree
|
|
.split(/\r?\n/)
|
|
.filter((line) => /^(<<<<<<<|=======|>>>>>>>|CONFLICT\b)/.test(line));
|
|
|
|
if (conflictLines.length > 0) {
|
|
const conflictReportPath = join(tmpdir(), "yedir-branch-readiness-conflicts.txt");
|
|
writeFileSync(conflictReportPath, conflictLines.join("\n"));
|
|
failures.push(
|
|
`merge conflict markers detected against ${baseRef}; details: ${conflictReportPath}`
|
|
);
|
|
}
|
|
|
|
if (behind > 0) {
|
|
const message = `${currentBranch} is behind ${baseRef} by ${behind} commit(s)`;
|
|
if (requireUpToDate) {
|
|
failures.push(message);
|
|
} else {
|
|
warnings.push(message);
|
|
}
|
|
}
|
|
|
|
console.log(
|
|
JSON.stringify(
|
|
{
|
|
baseRef,
|
|
branch: currentBranch,
|
|
requiredBranch: requiredBranch || null,
|
|
upstreamRef: upstreamRef || null,
|
|
mergeBase,
|
|
ahead,
|
|
behind,
|
|
changedFiles,
|
|
mergeConflictMarkers: conflictLines.length,
|
|
requireUpToDate,
|
|
},
|
|
null,
|
|
2
|
|
)
|
|
);
|
|
}
|
|
|
|
if (warnings.length > 0) {
|
|
console.warn(`Branch readiness warnings:\n- ${warnings.join("\n- ")}`);
|
|
}
|
|
|
|
if (failures.length > 0) {
|
|
console.error(`Branch readiness failed:\n- ${failures.join("\n- ")}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("Branch readiness check passed.");
|
|
|
|
function git(args) {
|
|
return execFileSync("git", args, {
|
|
encoding: "utf8",
|
|
stdio: ["ignore", "pipe", "pipe"],
|
|
});
|
|
}
|
|
|
|
function getOptionalGitConfig(key) {
|
|
try {
|
|
return git(["config", "--get", key]).trim();
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function getOptionalUpstreamRef() {
|
|
try {
|
|
return git(["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]).trim();
|
|
} catch {
|
|
return "";
|
|
}
|
|
}
|
|
|
|
function runMergeTree(mergeBase, baseRef) {
|
|
try {
|
|
return git(["merge-tree", mergeBase, "HEAD", baseRef]);
|
|
} catch (error) {
|
|
return [error.stdout, error.stderr].filter(Boolean).join("\n");
|
|
}
|
|
}
|