80 lines
3.9 KiB
JavaScript
80 lines
3.9 KiB
JavaScript
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const packagePath = path.join(root, "web/package.json");
|
|
const lockPath = path.join(root, "web/package-lock.json");
|
|
const outputPath = path.resolve(process.argv[2] ?? path.join(root, "tests/golden/M13-05C/dependency-inventory.json"));
|
|
const packageJson = JSON.parse(fs.readFileSync(packagePath, "utf8"));
|
|
const lock = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
|
const sha256 = (bytes) => crypto.createHash("sha256").update(bytes).digest("hex");
|
|
const packageBytes = fs.readFileSync(packagePath);
|
|
const lockBytes = fs.readFileSync(lockPath);
|
|
const entries = new Map(Object.entries(lock.packages));
|
|
const packageName = (packageKey) => {
|
|
const marker = packageKey.lastIndexOf("node_modules/");
|
|
return marker < 0 ? packageKey : packageKey.slice(marker + "node_modules/".length);
|
|
};
|
|
function resolveDependency(fromKey, dependency) {
|
|
let base = fromKey;
|
|
while (true) {
|
|
const candidate = base ? `${base}/node_modules/${dependency}` : `node_modules/${dependency}`;
|
|
if (entries.has(candidate)) return candidate;
|
|
const marker = base.lastIndexOf("/node_modules/");
|
|
if (marker < 0) return entries.has(`node_modules/${dependency}`) ? `node_modules/${dependency}` : null;
|
|
base = base.slice(0, marker);
|
|
}
|
|
}
|
|
function closure(roots) {
|
|
const visited = new Set();
|
|
const queue = roots.map((name) => resolveDependency("", name)).filter(Boolean);
|
|
while (queue.length) {
|
|
const key = queue.shift();
|
|
if (!key || visited.has(key)) continue;
|
|
visited.add(key);
|
|
const entry = entries.get(key);
|
|
for (const dependency of Object.keys({ ...(entry.dependencies ?? {}), ...(entry.optionalDependencies ?? {}) })) {
|
|
const next = resolveDependency(key, dependency);
|
|
if (next) queue.push(next);
|
|
}
|
|
}
|
|
return visited;
|
|
}
|
|
const roots = {
|
|
production: Object.keys(packageJson.dependencies ?? {}),
|
|
build: ["@eslint/js", "@types/react", "@types/react-dom", "@types/three", "@vitejs/plugin-react", "eslint", "typescript", "typescript-eslint", "vite"],
|
|
test: ["@axe-core/playwright", "@playwright/test"],
|
|
};
|
|
const categories = Object.fromEntries(Object.entries(roots).map(([category, names]) => [category, closure(names)]));
|
|
const allKeys = new Set([...categories.production, ...categories.build, ...categories.test]);
|
|
const packages = [...allKeys].sort().map((key) => {
|
|
const entry = entries.get(key);
|
|
const category = Object.entries(categories).filter(([, keys]) => keys.has(key)).map(([name]) => name);
|
|
return {
|
|
path: key,
|
|
name: entry.name ?? packageName(key),
|
|
version: entry.version,
|
|
resolved: entry.resolved ?? null,
|
|
integrity: entry.integrity ?? null,
|
|
categories: category,
|
|
dependencies: Object.keys({ ...(entry.dependencies ?? {}), ...(entry.optionalDependencies ?? {}) }).sort(),
|
|
};
|
|
});
|
|
const inventory = {
|
|
schemaVersion: 1,
|
|
task: "M13-05C",
|
|
operation: "NPM_DEPENDENCY_INVENTORY",
|
|
package: { path: "web/package.json", sha256: sha256(packageBytes), name: lock.name, version: lock.version, lockfileVersion: lock.lockfileVersion },
|
|
lockfile: { path: "web/package-lock.json", sha256: sha256(lockBytes), packageCount: entries.size - 1 },
|
|
roots,
|
|
categoryCounts: Object.fromEntries(Object.entries(categories).map(([name, keys]) => [name, keys.size])),
|
|
sharedCount: packages.filter((item) => item.categories.length > 1).length,
|
|
packages,
|
|
nextTask: "M13-05D",
|
|
};
|
|
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
fs.writeFileSync(outputPath, `${JSON.stringify(inventory, null, 2)}\n`);
|
|
process.stdout.write(`dependency-inventory-generated production=${inventory.categoryCounts.production} build=${inventory.categoryCounts.build} test=${inventory.categoryCounts.test} shared=${inventory.sharedCount} next=${inventory.nextTask}\n`);
|