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
JavaScript
// 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
};