UNPKG

rollup-license-plugin

Version:

Extracts OSS license information of the npm packages in your rollup or vite output

174 lines (173 loc) 6.58 kB
// src/index.ts import { readdir, readFile } from "fs/promises"; import { join } from "path"; import validateSpdx from "spdx-expression-validate"; import getNpmTarballUrl from "get-npm-tarball-url"; import fetch from "node-fetch"; var defaultLicenseTextCache = {}; async function getDefaultLicenseText(license) { if (!defaultLicenseTextCache[license]) { const response = await fetch( `https://raw.githubusercontent.com/spdx/license-list-data/main/text/${license}.txt` ); const responseText = await response.text(); defaultLicenseTextCache[license] = responseText; } return defaultLicenseTextCache[license]; } async function readPackageMeta(moduleDir) { const path = join(moduleDir, "package.json"); const contents = await readFile(path, "utf-8"); return JSON.parse(contents); } function getRepository(meta) { if (meta.repository && meta.repository.url) { return meta.repository.url; } else if (typeof meta.repository === "string") { return meta.repository; } return null; } async function getLicenseFileName(moduleDir) { return (await readdir(moduleDir, { withFileTypes: true })).filter((dirent) => !dirent.isDirectory()).map((dirent) => dirent.name).find((name) => !!/^licen[cs]e/i.test(name)); } async function readLicenseFileContents(path) { return await readFile(path, "utf-8"); } async function getLicenseText(packageId, license, moduleDir, pluginOptions = {}) { const getLicenseFileNameFn = pluginOptions.__mocks__?.getLicenseFileName ?? getLicenseFileName; const readLicenseFileContentsFn = pluginOptions.__mocks__?.readLicenseFileContents ?? readLicenseFileContents; if (license && license.indexOf("SEE LICENSE IN ") === 0) { const filename = license.split(" ")[3]; try { return await readLicenseFileContentsFn(join(moduleDir, filename)); } catch (e) { throw new Error( `Could not find file specified in package.json license field of ${packageId}` ); } } const licenseFilename = await getLicenseFileNameFn(moduleDir); if (licenseFilename !== void 0) { return await readLicenseFileContentsFn(join(moduleDir, licenseFilename)); } if (pluginOptions.replenishDefaultLicenseTexts) { return await getDefaultLicenseText(license); } return null; } function findPreferredLicense(licenses, preferredLicenses) { for (const preferredLicense of preferredLicenses) { for (const license of licenses) { if (preferredLicense === license) { return preferredLicense; } } } return null; } function getLicense(packageId, meta, pluginOptions = {}) { let license; if (pluginOptions.licenseOverrides && pluginOptions.licenseOverrides[packageId]) { license = pluginOptions.licenseOverrides[packageId]; } else if (typeof meta.license === "object") { license = meta.license.type; } else if (meta.license) { license = meta.license; } else if (Array.isArray(meta.licenses) && meta.licenses.length > 0) { license = findPreferredLicense( meta.licenses.map((l) => l.type), pluginOptions.preferredLicenses ?? [] ) ?? meta.licenses[0].type; } else if (typeof meta.licenses === "string") { license = meta.licenses; } if (!license) { throw new Error(`Could not find license info for ${packageId}`); } else if (pluginOptions.unacceptableLicenseTest && pluginOptions.unacceptableLicenseTest(license)) { throw new Error(`Found unacceptable license "${license}" for ${packageId}`); } else if (!validateSpdx(license)) { throw new Error( `License "${license}" for ${packageId} is not a valid SPDX expression!` ); } return license; } function createRollupLicensePlugin(pluginOptions = {}, pluginName = "rollup-license-plugin") { const moduleDirs = /* @__PURE__ */ new Set(); return { name: pluginName, renderChunk(_code, chunk) { Object.entries(chunk.modules).filter( ([path, module]) => Boolean(path.match(/node_modules/)) && module.renderedLength > 0 ).map( ([path]) => (path.startsWith("\0") ? path.replace(/^\0/, "") : path).replace( /\\/g, "/" ) ).map((path) => [path, path.split("node_modules").pop()]).map(([path, filePath]) => { const segments = filePath.replace(/^\//, "").split("/"); const packageName = segments[0].startsWith("@") ? `${segments[0]}/${segments[1]}` : segments[0]; return path.replace(filePath, "") + `/${packageName}`; }).forEach((moduleDir) => moduleDirs.add(moduleDir)); return null; }, async generateBundle() { const licenseMeta = []; const getLicenseFn = pluginOptions.__mocks__?.getLicense ?? getLicense; if (pluginOptions.includePackages) { for (const moduleDir of await pluginOptions.includePackages()) { moduleDirs.add(moduleDir); } } for (const moduleDir of Array.from(moduleDirs)) { const meta = await (pluginOptions.__mocks__?.readPackageMeta ? pluginOptions.__mocks__.readPackageMeta(moduleDir, readPackageMeta) : readPackageMeta(moduleDir)); const packageId = `${meta.name}@${meta.version}`; if (pluginOptions.excludedPackageTest && pluginOptions.excludedPackageTest(meta.name, meta.version)) { continue; } const license = getLicenseFn(packageId, meta, pluginOptions); const licenseText = await getLicenseText( packageId, license, moduleDir, pluginOptions ); const repository = getRepository(meta); const source = getNpmTarballUrl(meta.name, meta.version); licenseMeta.push({ name: meta.name, version: meta.version, author: meta.author, repository, source, license, licenseText }); } this.emitFile({ type: "asset", source: JSON.stringify(licenseMeta, null, 2), fileName: pluginOptions.outputFilename ?? "oss-licenses.json" }); if (pluginOptions.additionalFiles) { for (const fileName of Object.keys(pluginOptions.additionalFiles)) { const source = await pluginOptions.additionalFiles[fileName](licenseMeta); this.emitFile({ type: "asset", source, fileName }); } } } }; } function createViteLicensePlugin(pluginOptions = {}) { const pluginName = "vite-license-plugin"; return { ...createRollupLicensePlugin(pluginOptions, pluginName), name: pluginName, apply: "build" }; } export { createRollupLicensePlugin, createViteLicensePlugin };