88 lines
2.2 KiB
JavaScript
88 lines
2.2 KiB
JavaScript
import { execFile } from "node:child_process";
|
|
import { readFile } from "node:fs/promises";
|
|
import { extname, resolve } from "node:path";
|
|
import { promisify } from "node:util";
|
|
|
|
const execFileAsync = promisify(execFile);
|
|
|
|
const textExtensions = new Set([
|
|
".css",
|
|
".html",
|
|
".js",
|
|
".json",
|
|
".md",
|
|
".mjs",
|
|
".rs",
|
|
".sh",
|
|
".toml",
|
|
".ts",
|
|
".tsx",
|
|
".yaml",
|
|
".yml",
|
|
]);
|
|
|
|
const selfExcludedFiles = new Set([
|
|
"scripts/check-numbered-milestones.test.mjs",
|
|
"scripts/lib/numbered-milestones.mjs",
|
|
]);
|
|
|
|
export const defaultPackageRoot = resolve(
|
|
new URL("../..", import.meta.url).pathname,
|
|
);
|
|
|
|
export function findNumberedMilestoneNames(value) {
|
|
const matcher = new RegExp(String.raw`\bphases?[\s._/-]*0*\d+[a-z]?`, "gi");
|
|
return [...value.matchAll(matcher)].map((match) => ({
|
|
index: match.index ?? 0,
|
|
value: match[0],
|
|
}));
|
|
}
|
|
|
|
export async function listTrackedPackageFiles(packageRoot = defaultPackageRoot) {
|
|
const { stdout } = await execFileAsync("git", ["ls-files", "-z", "--", "."], {
|
|
cwd: packageRoot,
|
|
encoding: "utf8",
|
|
maxBuffer: 16 * 1024 * 1024,
|
|
});
|
|
return stdout.split("\0").filter(Boolean);
|
|
}
|
|
|
|
export async function checkNumberedMilestones({
|
|
packageRoot = defaultPackageRoot,
|
|
trackedFiles,
|
|
} = {}) {
|
|
const files = trackedFiles ?? await listTrackedPackageFiles(packageRoot);
|
|
const violations = [];
|
|
|
|
for (const relativePath of files) {
|
|
for (const match of findNumberedMilestoneNames(relativePath)) {
|
|
violations.push({
|
|
kind: "path",
|
|
file: relativePath,
|
|
line: 1,
|
|
value: match.value,
|
|
});
|
|
}
|
|
|
|
if (!textExtensions.has(extname(relativePath)) || selfExcludedFiles.has(relativePath)) {
|
|
continue;
|
|
}
|
|
|
|
const contents = await readFile(resolve(packageRoot, relativePath), "utf8");
|
|
for (const match of findNumberedMilestoneNames(contents)) {
|
|
violations.push({
|
|
kind: "content",
|
|
file: relativePath,
|
|
line: contents.slice(0, match.index).split("\n").length,
|
|
value: match.value,
|
|
});
|
|
}
|
|
}
|
|
|
|
return violations;
|
|
}
|
|
|
|
export function formatNumberedMilestoneViolation(violation) {
|
|
return `${violation.file}:${violation.line} contains numbered milestone label ${JSON.stringify(violation.value)}`;
|
|
}
|