204 lines
6.5 KiB
JavaScript
204 lines
6.5 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
import { existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
import { tmpdir } from "node:os";
|
|
import path from "node:path";
|
|
import { spawnSync } from "node:child_process";
|
|
import process from "node:process";
|
|
|
|
const UPSTREAM_REPO = "rmdes/indiekit-eleventy-theme";
|
|
const DEFAULT_REF = "main";
|
|
|
|
const targets = [
|
|
{
|
|
name: "recent-posts",
|
|
upstreamPath: "_includes/components/widgets/recent-posts.njk",
|
|
localPath: "_includes/components/widgets/recent-posts.njk",
|
|
mirrorPath: "theme/_includes/components/widgets/recent-posts.njk",
|
|
},
|
|
{
|
|
name: "recent-posts-blog",
|
|
upstreamPath: "_includes/components/widgets/recent-posts-blog.njk",
|
|
localPath: "_includes/components/widgets/recent-posts-blog.njk",
|
|
mirrorPath: "theme/_includes/components/widgets/recent-posts-blog.njk",
|
|
},
|
|
{
|
|
name: "sidebar-listing",
|
|
upstreamPath: "_includes/components/sidebar.njk",
|
|
localPath: "_includes/components/sidebar.njk",
|
|
mirrorPath: "theme/_includes/components/sidebar.njk",
|
|
},
|
|
{
|
|
name: "sidebar-blog-post",
|
|
upstreamPath: "_includes/components/blog-sidebar.njk",
|
|
localPath: "_includes/components/blog-sidebar.njk",
|
|
mirrorPath: "theme/_includes/components/blog-sidebar.njk",
|
|
},
|
|
{
|
|
name: "sidebar-homepage",
|
|
upstreamPath: "_includes/components/homepage-sidebar.njk",
|
|
localPath: "_includes/components/homepage-sidebar.njk",
|
|
mirrorPath: "theme/_includes/components/homepage-sidebar.njk",
|
|
},
|
|
{
|
|
name: "sidebar-cv",
|
|
upstreamPath: "_includes/components/cv-sidebar.njk",
|
|
localPath: "_includes/components/cv-sidebar.njk",
|
|
mirrorPath: "theme/_includes/components/cv-sidebar.njk",
|
|
},
|
|
];
|
|
|
|
function printHelp() {
|
|
console.log(`Usage: node scripts/check-upstream-widget-drift.mjs [options]\n\nOptions:\n --ref=<git-ref> Upstream branch/tag/sha to compare against (default: ${DEFAULT_REF})\n --show-diff Print unified diff when drift is found\n --strict Exit with code 1 when drift is found\n -h, --help Show this help\n\nChecks:\n - Recent Posts widgets (listing + post)
|
|
- Sidebar wrappers (listing + post + homepage + cv)\n\nExamples:\n npm run check:upstream-widgets\n npm run check:upstream-widgets -- --show-diff\n npm run check:upstream-widgets -- --ref=main --strict`);
|
|
}
|
|
|
|
function normalize(content) {
|
|
return content.replace(/\r\n/g, "\n");
|
|
}
|
|
|
|
function parseOptions(argv) {
|
|
const args = new Set(argv);
|
|
const refArg = argv.find((arg) => arg.startsWith("--ref="));
|
|
|
|
return {
|
|
ref: refArg ? refArg.slice("--ref=".length) : DEFAULT_REF,
|
|
showDiff: args.has("--show-diff"),
|
|
strict: args.has("--strict"),
|
|
help: args.has("-h") || args.has("--help"),
|
|
};
|
|
}
|
|
|
|
async function fetchUpstreamFile(ref, filePath) {
|
|
const url = `https://raw.githubusercontent.com/${UPSTREAM_REPO}/${encodeURIComponent(ref)}/${filePath}`;
|
|
const response = await fetch(url, {
|
|
headers: {
|
|
"User-Agent": "upstream-widget-drift-check",
|
|
},
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(`HTTP ${response.status} ${response.statusText} (${url})`);
|
|
}
|
|
|
|
return normalize(await response.text());
|
|
}
|
|
|
|
function printNoIndexDiff(leftFile, rightFile, leftLabel, rightLabel) {
|
|
const diffResult = spawnSync(
|
|
"git",
|
|
["--no-pager", "diff", "--no-index", "--src-prefix=a/", "--dst-prefix=b/", "--", leftFile, rightFile],
|
|
{ encoding: "utf8" },
|
|
);
|
|
|
|
let output = diffResult.stdout || "";
|
|
output = output.split(leftFile).join(leftLabel).split(rightFile).join(rightLabel);
|
|
if (output.trim()) {
|
|
process.stdout.write(output);
|
|
}
|
|
|
|
if (diffResult.status !== 0 && diffResult.status !== 1 && diffResult.stderr) {
|
|
process.stderr.write(diffResult.stderr);
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const options = parseOptions(process.argv.slice(2));
|
|
|
|
if (options.help) {
|
|
printHelp();
|
|
return;
|
|
}
|
|
|
|
const tempDir = mkdtempSync(path.join(tmpdir(), "widget-drift-"));
|
|
const cwd = process.cwd();
|
|
|
|
let upstreamDriftCount = 0;
|
|
let mirrorDriftCount = 0;
|
|
let errorCount = 0;
|
|
|
|
console.log(`Comparing local files against ${UPSTREAM_REPO}@${options.ref}`);
|
|
|
|
try {
|
|
for (const target of targets) {
|
|
const localAbs = path.join(cwd, target.localPath);
|
|
const mirrorAbs = path.join(cwd, target.mirrorPath);
|
|
|
|
console.log(`\n[${target.name}]`);
|
|
|
|
if (!existsSync(localAbs)) {
|
|
console.log(` local: ERROR (missing ${target.localPath})`);
|
|
errorCount += 1;
|
|
continue;
|
|
}
|
|
|
|
if (!existsSync(mirrorAbs)) {
|
|
console.log(` mirror: ERROR (missing ${target.mirrorPath})`);
|
|
errorCount += 1;
|
|
continue;
|
|
}
|
|
|
|
const localContent = normalize(readFileSync(localAbs, "utf8"));
|
|
const mirrorContent = normalize(readFileSync(mirrorAbs, "utf8"));
|
|
|
|
let upstreamContent;
|
|
try {
|
|
upstreamContent = await fetchUpstreamFile(options.ref, target.upstreamPath);
|
|
} catch (error) {
|
|
console.log(` upstream: ERROR (${error.message})`);
|
|
errorCount += 1;
|
|
continue;
|
|
}
|
|
|
|
const matchesUpstream = localContent === upstreamContent;
|
|
const matchesMirror = localContent === mirrorContent;
|
|
|
|
console.log(` upstream: ${matchesUpstream ? "OK" : "DRIFT"}`);
|
|
console.log(` mirror: ${matchesMirror ? "OK" : "DRIFT"}`);
|
|
|
|
if (!matchesUpstream) {
|
|
upstreamDriftCount += 1;
|
|
}
|
|
|
|
if (!matchesMirror) {
|
|
mirrorDriftCount += 1;
|
|
}
|
|
|
|
if (options.showDiff && !matchesUpstream) {
|
|
const localTmp = path.join(tempDir, `${target.name}.local.njk`);
|
|
const upstreamTmp = path.join(tempDir, `${target.name}.upstream.njk`);
|
|
writeFileSync(localTmp, localContent, "utf8");
|
|
writeFileSync(upstreamTmp, upstreamContent, "utf8");
|
|
|
|
console.log(`\n diff local vs upstream (${target.name})`);
|
|
printNoIndexDiff(localTmp, upstreamTmp, `a/${target.localPath}`, `b/upstream/${target.upstreamPath}`);
|
|
}
|
|
|
|
if (options.showDiff && !matchesMirror) {
|
|
console.log(`\n diff local vs mirror (${target.name})`);
|
|
printNoIndexDiff(localAbs, mirrorAbs, `a/${target.localPath}`, `b/${target.mirrorPath}`);
|
|
}
|
|
}
|
|
} finally {
|
|
rmSync(tempDir, { recursive: true, force: true });
|
|
}
|
|
|
|
console.log("\nSummary");
|
|
console.log(` upstream drift: ${upstreamDriftCount}`);
|
|
console.log(` mirror drift: ${mirrorDriftCount}`);
|
|
console.log(` errors: ${errorCount}`);
|
|
|
|
if (errorCount > 0) {
|
|
process.exit(2);
|
|
}
|
|
|
|
if (options.strict && (upstreamDriftCount > 0 || mirrorDriftCount > 0)) {
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error);
|
|
process.exit(2);
|
|
});
|