94 lines
2.7 KiB
JavaScript
94 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
const { execFileSync } = require("child_process");
|
|
|
|
const { parseRawDiff } = require("../lib/git-raw-diff");
|
|
const {
|
|
getManagedFiles,
|
|
loadWorkflowContract,
|
|
matchesContractEntry,
|
|
validateRawRepoPath,
|
|
} = require("../lib/workflow-contract");
|
|
|
|
function parseArgs(argv) {
|
|
const options = { base: "origin/main", head: "HEAD", includeReleaseManaged: false };
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const argument = argv[index];
|
|
if (argument === "--include-release-managed") {
|
|
options.includeReleaseManaged = true;
|
|
continue;
|
|
}
|
|
if (argument === "--base" || argument === "--head") {
|
|
const value = argv[index + 1];
|
|
if (!value || value.startsWith("--")) {
|
|
throw new Error(`${argument} requires a Git revision.`);
|
|
}
|
|
options[argument.slice(2)] = value;
|
|
index += 1;
|
|
continue;
|
|
}
|
|
throw new Error(`Unknown argument: ${argument}`);
|
|
}
|
|
return options;
|
|
}
|
|
|
|
function validateRecords(records, managedFiles) {
|
|
if (!Array.isArray(records) || records.length === 0) {
|
|
throw new Error("Canonical sync PR contains no changed files.");
|
|
}
|
|
|
|
const rejected = [];
|
|
for (const record of records) {
|
|
for (const filePath of [record.old_path, record.new_path].filter(Boolean)) {
|
|
const pathValidation = validateRawRepoPath(filePath);
|
|
const managed = managedFiles.some((entry) => matchesContractEntry(filePath, entry));
|
|
if (!pathValidation.safe || !managed) {
|
|
rejected.push(filePath);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (rejected.length > 0) {
|
|
throw new Error(
|
|
`Canonical sync PR contains unmanaged or unsafe paths: ${[...new Set(rejected)].sort().join(", ")}`,
|
|
);
|
|
}
|
|
|
|
return {
|
|
changedFiles: [...new Set(records.flatMap((record) => [record.old_path, record.new_path]).filter(Boolean))].sort(),
|
|
};
|
|
}
|
|
|
|
function readDiff(base, head) {
|
|
return execFileSync(
|
|
"git",
|
|
["diff", "--raw", "-z", "--no-abbrev", "--no-renames", `${base}...${head}`, "--"],
|
|
{ encoding: "buffer", maxBuffer: 16 * 1024 * 1024 },
|
|
);
|
|
}
|
|
|
|
function main() {
|
|
const options = parseArgs(process.argv.slice(2));
|
|
const contract = loadWorkflowContract(__dirname);
|
|
const managedFiles = getManagedFiles(contract, {
|
|
includeMixed: true,
|
|
includeReleaseManaged: options.includeReleaseManaged,
|
|
});
|
|
const records = parseRawDiff(readDiff(options.base, options.head), { allowEmpty: true });
|
|
const result = validateRecords(records, managedFiles);
|
|
process.stdout.write(
|
|
`Canonical sync PR is restricted to ${result.changedFiles.length} managed file(s).\n`,
|
|
);
|
|
}
|
|
|
|
if (require.main === module) {
|
|
try {
|
|
main();
|
|
} catch (error) {
|
|
process.stderr.write(`${error.message}\n`);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|
|
|
|
module.exports = { parseArgs, validateRecords };
|