chore: add local release and dependency guards
This commit is contained in:
63
scripts/check-pinned-deps.mjs
Normal file
63
scripts/check-pinned-deps.mjs
Normal file
@@ -0,0 +1,63 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user