From b49da10d38d4bd85f9059736dbf31cc172cffb39 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 2 Dec 2021 19:47:14 +0800 Subject: [PATCH] Include bundled dependencies licenses in `LICENSE` file (#10006) Co-authored-by: Sosuke Suzuki --- package.json | 1 + scripts/build/build.mjs | 20 ++++++++ scripts/build/bundler.mjs | 13 ++++- scripts/build/save-licenses.mjs | 87 ++++++++++++++++++++++++++++++++ yarn.lock | 89 ++++++++++++++++++++++++++++++++- 5 files changed, 207 insertions(+), 3 deletions(-) create mode 100644 scripts/build/save-licenses.mjs diff --git a/package.json b/package.json index 3d86610f5..79921319b 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "pretty-bytes": "5.6.0", "rimraf": "3.0.2", "rollup": "2.60.2", + "rollup-plugin-license": "2.2.0", "rollup-plugin-polyfill-node": "0.7.0", "rollup-plugin-terser": "7.0.2", "snapshot-diff": "0.9.0", diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 97d38cf78..2f5f78083 100644 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -18,6 +18,7 @@ import { import bundler from "./bundler.mjs"; import bundleConfigs from "./config.mjs"; import Cache from "./cache.mjs"; +import saveLicenses from "./save-licenses.mjs"; // Errors in promises should be fatal. const loggedErrors = new Set(); @@ -146,6 +147,12 @@ async function run(params) { const shouldUseCache = params.cache && !params.file && params.minify === null; const shouldPreparePackage = !params.playground && !params.file && params.minify === null; + const shouldSaveBundledPackagesLicenses = + !params.cache && + !params.playground && + !params.file && + params.minify === null; + let configs = bundleConfigs; if (params.file) { configs = configs.filter(({ output }) => output === params.file); @@ -157,6 +164,11 @@ async function run(params) { rimraf.sync(BUILD_CACHE_DIR); } + const licenses = []; + if (shouldSaveBundledPackagesLicenses) { + params.onLicenseFound = (dependencies) => licenses.push(...dependencies); + } + let bundleCache; if (shouldUseCache) { bundleCache = new Cache({ @@ -180,6 +192,14 @@ async function run(params) { if (shouldPreparePackage) { await preparePackage(); } + + if (shouldSaveBundledPackagesLicenses) { + await saveLicenses(licenses); + } else { + console.warn( + chalk.red("Bundled packages licenses not included in `dist/LICENSE`.") + ); + } } run( diff --git a/scripts/build/bundler.mjs b/scripts/build/bundler.mjs index eaacc1809..22860f84c 100644 --- a/scripts/build/bundler.mjs +++ b/scripts/build/bundler.mjs @@ -8,6 +8,7 @@ import rollupPluginJson from "@rollup/plugin-json"; import rollupPluginReplace from "@rollup/plugin-replace"; import { terser as rollupPluginTerser } from "rollup-plugin-terser"; import { babel as rollupPluginBabel } from "@rollup/plugin-babel"; +import rollupPluginLicense from "rollup-plugin-license"; import createEsmUtils from "esm-utils"; import builtinModules from "builtin-modules"; import { PROJECT_ROOT, DIST_DIR } from "../utils/index.mjs"; @@ -75,7 +76,7 @@ function getBabelConfig(bundle) { return config; } -function getRollupConfig(bundle) { +function getRollupConfig(bundle, options) { const config = { input: path.join(PROJECT_ROOT, bundle.input), onwarn(warning) { @@ -203,6 +204,14 @@ function getRollupConfig(bundle) { rollupPluginReplaceModule(replaceModule), bundle.target === "universal" && rollupPluginPolyfillNode(), rollupPluginBabel(babelConfig), + options.onLicenseFound && + rollupPluginLicense({ + cwd: PROJECT_ROOT, + thirdParty: { + includePrivate: true, + output: options.onLicenseFound, + }, + }), ].filter(Boolean); if (bundle.target === "node") { @@ -269,7 +278,7 @@ function getRollupOutputOptions(bundle, buildOptions) { } async function createBundle(bundle, cache, options) { - const inputOptions = getRollupConfig(bundle); + const inputOptions = getRollupConfig(bundle, options); const outputOptions = getRollupOutputOptions(bundle, options); if (!Array.isArray(outputOptions) && outputOptions.skipped) { diff --git a/scripts/build/save-licenses.mjs b/scripts/build/save-licenses.mjs new file mode 100644 index 000000000..12f323fc5 --- /dev/null +++ b/scripts/build/save-licenses.mjs @@ -0,0 +1,87 @@ +import path from "node:path"; +import { promises as fs } from "node:fs"; +import { outdent } from "outdent"; +import { DIST_DIR } from "../utils/index.mjs"; + +const file = path.join(DIST_DIR, "LICENSE"); +const separator = `\n${"-".repeat(40)}\n\n`; + +async function saveLicenses(dependencies) { + // Unique by `name` and `version` + dependencies = dependencies.filter( + (dependency, index) => + index === + dependencies.findIndex( + ({ name, version }) => + dependency.name === name && dependency.version === version + ) + ); + + dependencies.sort( + (dependencyA, dependencyB) => + dependencyA.name.localeCompare(dependencyB.name) || + dependencyA.version.localeCompare(dependencyB.version) + ); + + const prettierLicense = await fs.readFile(file, "utf8"); + + const licenses = [ + ...new Set( + dependencies + .filter(({ license }) => license) + .map(({ license }) => license) + ), + ]; + const text = outdent` + # Prettier license + + Prettier is released under the MIT license: + + ${prettierLicense.trim()} + + ## Licenses of bundled dependencies + + The published Prettier artifact additionally contains code with the following licenses: + ${licenses.join(", ")} + + ## Bundled dependencies + `; + + const content = dependencies + .map((dependency) => { + let text = `### ${dependency.name}@v${dependency.version}\n`; + + const meta = []; + + if (dependency.license) { + meta.push(`License: ${dependency.license}`); + } + if (dependency.author?.name) { + meta.push(`By: ${dependency.author.name}`); + } + if (dependency.repository?.url) { + meta.push(`Repository: <${dependency.repository.url}>`); + } + + if (meta.length > 0) { + text += "\n" + meta.join("\n") + "\n"; + } + + if (dependency.licenseText) { + text += + "\n" + + dependency.licenseText + .trim() + .split("\n") + .map((line) => (line ? `> ${line}` : ">")) + .join("\n") + + "\n"; + } + return text; + }) + .join(separator); + + await fs.writeFile(file, text + "\n\n" + content); +} + +export default saveLicenses; diff --git a/yarn.lock b/yarn.lock index 56acf97cc..f6a8c5b6b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1912,6 +1912,11 @@ argparse@^2.0.1: resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== +array-find-index@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-find-index/-/array-find-index-1.0.2.tgz#df010aa1287e164bbda6f9723b0a96a1ec4187a1" + integrity sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E= + array-includes@^3.1.3, array-includes@^3.1.4: version "3.1.4" resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.1.4.tgz#f5b493162c760f3539631f005ba2bb46acb45ba9" @@ -2335,6 +2340,11 @@ comment-parser@^1.1.2: resolved "https://registry.yarnpkg.com/comment-parser/-/comment-parser-1.3.0.tgz#68beb7dbe0849295309b376406730cd16c719c44" integrity sha512-hRpmWIKgzd81vn0ydoWoyPoALEOnF4wt8yKD35Ib1D6XC2siLiYaiqfGkYrunuKdsXGwpBpHU3+9r+RVw2NZfA== +commenting@1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/commenting/-/commenting-1.1.0.tgz#fae14345c6437b8554f30bc6aa6c1e1633033590" + integrity sha512-YeNK4tavZwtH7jEgK1ZINXzLKm6DZdEMfsaaieOsCAN0S8vsY7UeuO3Q7d/M018EFgE+IeUAuBOKkFccBZsUZA== + commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" @@ -3329,6 +3339,18 @@ glob-parent@^6.0.1: dependencies: is-glob "^4.0.3" +glob@7.1.6: + version "7.1.6" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" + integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -4506,6 +4528,11 @@ lodash.merge@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ== +lodash@4.17.19: + version "4.17.19" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.19.tgz#e48ddedbe30b3321783c5b4301fbd353bc1e4a4b" + integrity sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ== + lodash@4.17.21, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -4533,7 +4560,7 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" -magic-string@^0.25.7: +magic-string@0.25.7, magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" integrity sha512-4CrMT5DOHTDk4HYDlzmwu4FVCcIYI8gauveasrdCu2IKIFOJ3f0v/8MDGJCDL9oD2ppz/Av1b0Nj345H9M+XIA== @@ -4646,6 +4673,16 @@ minimist@1.2.5, minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== +mkdirp@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + +moment@2.27.0: + version "2.27.0" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.27.0.tgz#8bff4e3e26a236220dfe3e36de756b6ebaa0105d" + integrity sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -4942,6 +4979,11 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== +package-name-regex@1.0.8: + version "1.0.8" + resolved "https://registry.yarnpkg.com/package-name-regex/-/package-name-regex-1.0.8.tgz#4b8388cace6cc415f462ab4a269583d9a2d9f131" + integrity sha512-g3vB2J62dLqf4m50VM4tJUC4sixw3JB+Igd0cF3P/gJhAvmvsmFEV2eWZTeLbwfkKEWTf3+gwQ2C6JFFRxWHEQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -5459,6 +5501,21 @@ rimraf@3.0.2, rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" +rollup-plugin-license@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/rollup-plugin-license/-/rollup-plugin-license-2.2.0.tgz#0d19139bbe44dda500fbf15530af07c91949e348" + integrity sha512-xXb1vviEwlJMX+VGUSsglcMA/Rh9d2QzEm94awy4FlnsPqGrXoTYYGOR3UXR6gYIxiJFkr7qmkKF/NXfre/y8g== + dependencies: + commenting "1.1.0" + glob "7.1.6" + lodash "4.17.19" + magic-string "0.25.7" + mkdirp "1.0.4" + moment "2.27.0" + package-name-regex "1.0.8" + spdx-expression-validate "2.0.0" + spdx-satisfies "5.0.0" + rollup-plugin-polyfill-node@0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/rollup-plugin-polyfill-node/-/rollup-plugin-polyfill-node-0.7.0.tgz#938e13278c98a582a4f8814975ddd26f90afddcc" @@ -5681,6 +5738,15 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== +spdx-compare@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/spdx-compare/-/spdx-compare-1.0.0.tgz#2c55f117362078d7409e6d7b08ce70a857cd3ed7" + integrity sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A== + dependencies: + array-find-index "^1.0.2" + spdx-expression-parse "^3.0.0" + spdx-ranges "^2.0.0" + spdx-correct@^3.0.0: version "3.1.1" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.1.1.tgz#dece81ac9c1e6713e5f7d1b6f17d468fa53d89a9" @@ -5702,11 +5768,32 @@ spdx-expression-parse@^3.0.0: spdx-exceptions "^2.1.0" spdx-license-ids "^3.0.0" +spdx-expression-validate@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/spdx-expression-validate/-/spdx-expression-validate-2.0.0.tgz#25c9408e1c63fad94fff5517bb7101ffcd23350b" + integrity sha512-b3wydZLM+Tc6CFvaRDBOF9d76oGIHNCLYFeHbftFXUWjnfZWganmDmvtM5sm1cRwJc/VDBMLyGGrsLFd1vOxbg== + dependencies: + spdx-expression-parse "^3.0.0" + spdx-license-ids@^3.0.0: version "3.0.11" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz#50c0d8c40a14ec1bf449bae69a0ea4685a9d9f95" integrity sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g== +spdx-ranges@^2.0.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/spdx-ranges/-/spdx-ranges-2.1.1.tgz#87573927ba51e92b3f4550ab60bfc83dd07bac20" + integrity sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA== + +spdx-satisfies@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/spdx-satisfies/-/spdx-satisfies-5.0.0.tgz#d740b8f14caeada36fb307629dee87146970a256" + integrity sha512-/hGhwh20BeGmkA+P/lm06RvXD94JduwNxtx/oX3B5ClPt1/u/m5MCaDNo1tV3Y9laLkQr/NRde63b9lLMhlNfw== + dependencies: + spdx-compare "^1.0.0" + spdx-expression-parse "^3.0.0" + spdx-ranges "^2.0.0" + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"