@mr_hugo/boredom
Version:
Another boring JavaScript framework.
180 lines (159 loc) • 5.63 kB
text/typescript
import fs from "node:fs";
import license from "rollup-plugin-license";
import type { Dependency } from "rollup-plugin-license";
import colors from "picocolors";
import type { Plugin, PluginContext } from "rollup";
export default function licensePlugin(
licenseFilePath: string,
licenseTitle: string,
packageName: string,
additionalSection?: string,
): Plugin {
const originalPlugin = license({
thirdParty(dependencies) {
// https://github.com/rollup/rollup/blob/master/build-plugins/generate-license-file.js
// MIT Licensed https://github.com/rollup/rollup/blob/master/LICENSE-CORE.md
const coreLicense = fs.readFileSync(
new URL("../LICENSE", import.meta.url),
);
const deps = sortDependencies(dependencies);
const licenses = sortLicenses(
new Set(
dependencies.map((dep) => dep.license).filter(Boolean) as string[],
),
);
let dependencyLicenseTexts = "";
for (let i = 0; i < deps.length; i++) {
// Find dependencies with the same license text so it can be shared
const licenseText = deps[i].licenseText;
const sameDeps = [deps[i]];
if (licenseText) {
for (let j = i + 1; j < deps.length; j++) {
if (licenseText === deps[j].licenseText) {
sameDeps.push(...deps.splice(j, 1));
j--;
}
}
}
let text = `## ${sameDeps.map((d) => d.name).join(", ")}\n`;
const depInfos = sameDeps.map((d) => getDependencyInformation(d));
// If all same dependencies have the same license and contributor names, show them only once
if (
depInfos.length > 1 &&
depInfos.every(
(info) =>
info.license === depInfos[0].license &&
info.names === depInfos[0].names,
)
) {
const { license, names } = depInfos[0];
const repositoryText = depInfos
.map((info) => info.repository)
.filter(Boolean)
.join(", ");
if (license) text += `License: ${license}\n`;
if (names) text += `By: ${names}\n`;
if (repositoryText) text += `Repositories: ${repositoryText}\n`;
} // Else show each dependency separately
else {
for (let j = 0; j < depInfos.length; j++) {
const { license, names, repository } = depInfos[j];
if (license) text += `License: ${license}\n`;
if (names) text += `By: ${names}\n`;
if (repository) text += `Repository: ${repository}\n`;
if (j !== depInfos.length - 1) text += "\n";
}
}
if (licenseText) {
text += "\n" +
licenseText
.trim()
.replace(/\r\n|\r/g, "\n")
.split("\n")
.map((line) => `> ${line}`)
.join("\n") +
"\n";
}
if (i !== deps.length - 1) {
text += "\n---------------------------------------\n\n";
}
dependencyLicenseTexts += text;
}
const licenseText = `# ${licenseTitle}\n` +
`${packageName} is released under the MIT license:\n\n` +
coreLicense +
`\n` +
(additionalSection || "") +
`# Licenses of bundled dependencies\n` +
`The published ${packageName} artifact additionally contains code with the following licenses:\n` +
`${licenses.join(", ")}\n\n` +
`# Bundled dependencies:\n` +
dependencyLicenseTexts;
const existingLicenseText = fs.readFileSync(licenseFilePath, "utf-8");
if (existingLicenseText !== licenseText) {
fs.writeFileSync(licenseFilePath, licenseText);
console.warn(
colors.yellow(
"\nLICENSE.md updated. You should commit the updated file.\n",
),
);
}
},
});
// skip for watch mode
for (const hook of ["renderChunk", "generateBundle"] as const) {
const originalHook = originalPlugin[hook]!;
originalPlugin[hook] = function (this: PluginContext, ...args: unknown[]) {
if (this.meta.watchMode) return;
return (originalHook as Function).apply(this, args);
};
}
return originalPlugin;
}
function sortDependencies(dependencies: Dependency[]) {
return dependencies.sort(({ name: nameA }, { name: nameB }) => {
return nameA! > nameB! ? 1 : nameB! > nameA! ? -1 : 0;
});
}
function sortLicenses(licenses: Set<string>) {
let withParenthesis: string[] = [];
let noParenthesis: string[] = [];
licenses.forEach((license) => {
if (license[0] === "(") {
withParenthesis.push(license);
} else {
noParenthesis.push(license);
}
});
withParenthesis = withParenthesis.sort();
noParenthesis = noParenthesis.sort();
return [...noParenthesis, ...withParenthesis];
}
interface DependencyInfo {
license?: string;
names?: string;
repository?: string;
}
function getDependencyInformation(dep: Dependency): DependencyInfo {
const info: DependencyInfo = {};
const { license, author, maintainers, contributors, repository } = dep;
if (license) {
info.license = license;
}
const names = new Set<string>();
for (const person of [author, ...maintainers, ...contributors]) {
const name = typeof person === "string" ? person : person?.name;
if (name) {
names.add(name);
}
}
if (names.size > 0) {
info.names = Array.from(names).join(", ");
}
if (repository) {
info.repository = typeof repository === "string"
? repository
: repository.url;
}
return info;
}