Files
pi/scripts/check-pinned-deps.mjs
2026-05-20 13:06:28 +02:00

64 lines
2.1 KiB
JavaScript

import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
const dependencySections = ["dependencies", "devDependencies", "optionalDependencies"];
const exactVersionPattern = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
const ignoredDirectories = new Set([".git", "dist", "node_modules"]);
const packageJsonFiles = [];
function collectPackageJsonFiles(directory) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (!ignoredDirectories.has(entry.name)) {
collectPackageJsonFiles(join(directory, entry.name));
}
continue;
}
if (entry.isFile() && entry.name === "package.json") {
packageJsonFiles.push(join(directory, entry.name));
}
}
}
function isInternalWorkspaceDependency(name) {
return name.startsWith("@earendil-works/pi-");
}
function isNonRegistrySpecifier(specifier) {
return /^(?:workspace:|file:|link:|portal:|git\+|github:|git:|https?:|ssh:|git:\/\/)/.test(specifier);
}
function getVersionSpecifier(specifier) {
if (!specifier.startsWith("npm:")) return specifier;
const aliasTarget = specifier.slice("npm:".length);
const versionSeparator = aliasTarget.lastIndexOf("@");
if (versionSeparator <= 0) return specifier;
return aliasTarget.slice(versionSeparator + 1);
}
const failures = [];
collectPackageJsonFiles(".");
for (const file of packageJsonFiles.sort()) {
const packageJson = JSON.parse(readFileSync(file, "utf8"));
for (const section of dependencySections) {
const dependencies = packageJson[section];
if (!dependencies) continue;
for (const [name, specifier] of Object.entries(dependencies)) {
if (isInternalWorkspaceDependency(name) || isNonRegistrySpecifier(specifier)) continue;
if (exactVersionPattern.test(getVersionSpecifier(specifier))) continue;
failures.push(`${file}: ${section}.${name} must be pinned, found ${specifier}`);
}
}
}
if (failures.length > 0) {
console.error("Direct external dependencies must use exact versions:");
for (const failure of failures) console.error(` ${failure}`);
process.exit(1);
}