#!/usr/bin/env node /** * Lockfile license gate for every installed package, including workspaces. * * The allowed set is intentionally small and explicit. A new license is a * review event, not a string this script tries to interpret optimistically. * This does not replace third-party notices; it prevents a dependency update * from quietly introducing terms outside Tera's permissive distribution model. */ import { readFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { fileURLToPath } from "node:url"; const root = resolve(dirname(fileURLToPath(import.meta.url)), ".."); const lock = JSON.parse(readFileSync(resolve(root, "package-lock.json"), "utf8")); const allowed = new Set(["Apache-2.0", "BSD-3-Clause", "ISC", "MIT"]); const problems = []; const counts = new Map(); for (const [path, entry] of Object.entries(lock.packages ?? {})) { if (path === "") continue; let license = entry.license; if (!license && entry.link === true && typeof entry.resolved === "string") { try { const workspace = JSON.parse( readFileSync(resolve(root, entry.resolved, "package.json"), "utf8"), ); license = workspace.license; } catch (error) { problems.push(`${path}: could not read linked workspace license (${String(error)})`); continue; } } if (typeof license !== "string" || license.length === 0) { problems.push(`${path}: no license recorded in the lockfile or linked workspace`); continue; } if (!allowed.has(license)) { problems.push(`${path}: ${license} is not in the reviewed allowlist`); continue; } counts.set(license, (counts.get(license) ?? 0) + 1); const resolved = entry.resolved; if (typeof resolved === "string" && /^(?:git\+|github:|gitlab:|bitbucket:)/i.test(resolved)) { problems.push(`${path}: source dependency ${resolved} is not a registry artifact`); } if (path.startsWith("node_modules/") && entry.link !== true) { if (typeof resolved !== "string" || !/^https:\/\/registry\.npmjs\.org\//.test(resolved)) { problems.push(`${path}: dependency is not pinned to the npm registry in package-lock.json`); } if (typeof entry.integrity !== "string" || !/^sha(?:256|384|512)-/.test(entry.integrity)) { problems.push(`${path}: dependency has no recognized lockfile integrity hash`); } } } if (problems.length > 0) { console.error("\ncheck-dependency-licenses: FAIL\n"); for (const problem of problems) console.error(` ${problem}`); console.error(`\nReviewed allowlist: ${[...allowed].join(", ")}\n`); process.exit(1); } const total = [...counts.values()].reduce((sum, count) => sum + count, 0); console.log(`check-dependency-licenses: ok — ${total} locked packages reviewed.`); for (const license of [...counts.keys()].sort()) { console.log(` ${license}: ${counts.get(license)}`); }