mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
64 lines
2.1 KiB
JavaScript
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);
|
|
}
|