import { cp, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises"; import path from "node:path"; import { fileURLToPath } from "node:url"; const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); const publicDirectory = path.join(root, "public"); const required = [ "LICENSE", "README.md", "CHANGELOG.md", "SOURCE.md", "THIRD_PARTY_NOTICES.md", ]; await mkdir(publicDirectory, { recursive: true }); for (const name of required) { const source = path.join(root, name); await readFile(source); await cp(source, path.join(publicDirectory, name)); } const publicLicenses = path.join(publicDirectory, "LICENSES"); await rm(publicLicenses, { recursive: true, force: true }); await cp(path.join(root, "LICENSES"), publicLicenses, { recursive: true }); const publicDocs = path.join(publicDirectory, "docs"); await rm(publicDocs, { recursive: true, force: true }); await cp(path.join(root, "docs"), publicDocs, { recursive: true }); const lock = JSON.parse( await readFile(path.join(root, "package-lock.json"), "utf8"), ); const runtimeLicenseSections = []; for (const [location, locked] of Object.entries(lock.packages ?? {}).sort( ([left], [right]) => (left < right ? -1 : left > right ? 1 : 0), )) { if (!location.includes("node_modules/") || locked.dev === true) continue; const packageDirectory = path.join(root, location); const details = JSON.parse( await readFile(path.join(packageDirectory, "package.json"), "utf8"), ); const candidates = (await readdir(packageDirectory)) .filter((name) => /^(?:licen[cs]e|copying|notice)(?:\.|$)/iu.test(name)) .sort(); const texts = []; for (const candidate of candidates) { try { texts.push( `--- ${candidate} ---\n${await readFile(path.join(packageDirectory, candidate), "utf8")}`, ); } catch { // Ignore directories or non-text aliases; another matching file may exist. } } runtimeLicenseSections.push( [ "=".repeat(78), `${details.name}@${details.version}`, `Declared licence: ${details.license ?? locked.license ?? "See upstream package"}`, `Installed from: ${location}`, "=".repeat(78), texts.length ? texts.join("\n\n") : "No package-local licence file was present; see THIRD_PARTY_NOTICES.md and the upstream source.", ].join("\n"), ); } await writeFile( path.join(publicLicenses, "npm-runtime-licenses.txt"), `${runtimeLicenseSections.join("\n\n")}\n`, ); const packageJson = JSON.parse( await readFile(path.join(root, "package.json"), "utf8"), ); const rows = [ "# Runtime dependency licences", "", "Generated from the exact lock used for this build.", "", "| Package | Version | Licence |", "| --- | --- | --- |", ]; for (const name of Object.keys(packageJson.dependencies).sort()) { const manifestPath = path.join(root, "node_modules", name, "package.json"); const details = JSON.parse(await readFile(manifestPath, "utf8")); rows.push( `| \`${details.name}\` | ${details.version} | ${details.license ?? "See packaged notices"} |`, ); } await writeFile( path.join(publicDirectory, "THIRD_PARTY_LICENSES.txt"), `${rows.join("\n")}\n`, ); console.log("Prepared static release notices");