diff --git a/.changeset/old-doors-admire.md b/.changeset/old-doors-admire.md new file mode 100644 index 000000000..ff7a30a2c --- /dev/null +++ b/.changeset/old-doors-admire.md @@ -0,0 +1,5 @@ +--- +"@rnx-kit/third-party-notices": minor +--- + +Add full license text and format the JSON output diff --git a/packages/third-party-notices/README.md b/packages/third-party-notices/README.md index ce6ed2133..6dbb5f12d 100644 --- a/packages/third-party-notices/README.md +++ b/packages/third-party-notices/README.md @@ -16,10 +16,6 @@ output file. This package works for npm, yarn and pnpm package layouts formats. -At the moment this package only supports webpack based bundles, there is nothing -preventing adding metro support, the current customers of this module are -basedon webpack at the moment. - ## Usage ### Commandline @@ -32,23 +28,25 @@ npx @rnx-kit/third-party-notices \ ``` Options: - --help Show help [boolean] - --version Show version number [boolean] - --rootPath The root of the repo where to start resolving modules from. + --help Show help [boolean] + --version Show version number [boolean] + --rootPath The root of the repo where to start resolving modules from. [string] [required] - --sourceMapFile The sourceMap file to generate licence contents for. + --sourceMapFile The sourceMap file to generate license contents for. [string] [required] - --outputFile The output file to write the licence file to. [string] - --json Output license information as a JSON + --json Output license information as a JSON [boolean] [default: false] - --ignoreScopes Npm scopes to ignore and not emit licence information for + --outputFile The output file to write the license file to. [string] + --ignoreScopes Npm scopes to ignore and not emit license information for [array] - --ignoreModules Modules (js packages) to not emit licence information for + --ignoreModules Modules (js packages) to not emit license information for [array] - --preambleText A list of lines to prepend at the start of the generated - licence file. [array] - --additionalText A list of lines to append at the end of the generated - licence file. [array] + --preambleText A list of lines to prepend at the start of the generated + license file. [array] + --additionalText A list of lines to append at the end of the generated + license file. [array] + --fullLicenseText Include full license text in the JSON output + [boolean] [default: false] ``` ### As a library diff --git a/packages/third-party-notices/src/cli.ts b/packages/third-party-notices/src/cli.ts index 18bc9ef94..92f750471 100644 --- a/packages/third-party-notices/src/cli.ts +++ b/packages/third-party-notices/src/cli.ts @@ -52,6 +52,12 @@ function getArgs(): WriteThirdPartyNoticesOptions { describe: "A list of lines to append at the end of the generated license file.", }, + fullLicenseText: { + type: "boolean", + describe: "Include full license text in the JSON output", + default: false, + implies: "json", + }, }).argv; const writeTpnArgs: WriteThirdPartyNoticesOptions = argv; diff --git a/packages/third-party-notices/src/output/json.ts b/packages/third-party-notices/src/output/json.ts index 42c2c79ab..62684e77b 100644 --- a/packages/third-party-notices/src/output/json.ts +++ b/packages/third-party-notices/src/output/json.ts @@ -1,5 +1,5 @@ import { findPackage, readPackage } from "@rnx-kit/tools-node/package"; -import type { License } from "../types"; +import type { License, LicenseJSONInfo } from "../types"; function getPackageAuthor(modulePath: string): string | undefined { const pkgFile = findPackage(modulePath); @@ -26,20 +26,33 @@ function parseCopyright( return m[0].trim(); } -export function createLicenseJSON(licenses: License[]): string { - return JSON.stringify({ - packages: licenses.map( - ({ name, path: modulePath, version, license, licenseText }) => { - if (!license) { - throw new Error(`No license for ${name}`); +export function createLicenseJSON( + licenses: License[], + fullLicenseText?: boolean +): string { + return JSON.stringify( + { + packages: licenses.map( + ({ name, path: modulePath, version, license, licenseText }) => { + if (!license) { + throw new Error(`No license for ${name}`); + } + const info: LicenseJSONInfo = { + name, + version, + license, + copyright: parseCopyright(modulePath, licenseText), + }; + + if (fullLicenseText) { + info.text = licenseText?.replace(/\r\n|\r|\n/g, "\n").trim(); + } + + return info; } - return { - name, - version, - license, - copyright: parseCopyright(modulePath, licenseText), - }; - } - ), - }); + ), + }, + null, + 2 + ); } diff --git a/packages/third-party-notices/src/types.ts b/packages/third-party-notices/src/types.ts index f450be915..fed5dfee5 100644 --- a/packages/third-party-notices/src/types.ts +++ b/packages/third-party-notices/src/types.ts @@ -13,6 +13,14 @@ export type License = ModuleNamePathPair & { noticeText?: string; }; +export type LicenseJSONInfo = { + name: string; + version: string; + license: string; + copyright: string; + text?: string; +}; + export type SourceMap = { sources: string[]; sections?: SourceSection[]; @@ -31,4 +39,5 @@ export type WriteThirdPartyNoticesOptions = { ignoreModules?: string[]; preambleText?: string[]; additionalText?: string[]; + fullLicenseText?: boolean; }; diff --git a/packages/third-party-notices/src/write-third-party-notices.ts b/packages/third-party-notices/src/write-third-party-notices.ts index 499476005..c3f789c06 100644 --- a/packages/third-party-notices/src/write-third-party-notices.ts +++ b/packages/third-party-notices/src/write-third-party-notices.ts @@ -37,8 +37,14 @@ export async function writeThirdPartyNotices( options: WriteThirdPartyNoticesOptions ): Promise { const fileOptions = { encoding: "utf-8" } as const; - const { additionalText, json, outputFile, preambleText, sourceMapFile } = - options; + const { + additionalText, + json, + outputFile, + preambleText, + sourceMapFile, + fullLicenseText, + } = options; // Parse source map file const sourceMapJson = fs.readFileSync(sourceMapFile, fileOptions); @@ -53,7 +59,7 @@ export async function writeThirdPartyNotices( ); const licenses = await extractLicenses(moduleNameToPathMap); const outputText = json - ? createLicenseJSON(licenses) + ? createLicenseJSON(licenses, fullLicenseText) : createLicenseFileContents(licenses, preambleText, additionalText); if (outputFile) { @@ -67,12 +73,13 @@ export async function writeThirdPartyNoticesFromMap( options: WriteThirdPartyNoticesOptions, moduleNameToPathMap: Map ): Promise { - const { additionalText, json, preambleText, sourceMapFile } = options; + const { additionalText, json, preambleText, sourceMapFile, fullLicenseText } = + options; let { outputFile } = options; const licenses = await extractLicenses(moduleNameToPathMap); const outputText = json - ? createLicenseJSON(licenses) + ? createLicenseJSON(licenses, fullLicenseText) : createLicenseFileContents(licenses, preambleText, additionalText); if (!outputFile) { diff --git a/packages/third-party-notices/test/licence.test.ts b/packages/third-party-notices/test/licence.test.ts index 00de7b2ad..825457f00 100644 --- a/packages/third-party-notices/test/licence.test.ts +++ b/packages/third-party-notices/test/licence.test.ts @@ -116,4 +116,38 @@ describe("license", () => { ], }); }); + + test("createLicenseJSON with fullLicenseText", async () => { + const licenses = await getSampleLicenseData(); + // escape \n so JSON.parse won't transform \n into actual newlines + const licenseText = createLicenseJSON(licenses, true).replace( + /\\n/g, + "\\\\n" + ); + + expect(JSON.parse(licenseText)).toEqual({ + packages: [ + { + copyright: "Microsoft Open Source", + license: "MIT", + name: "@rnx-kit/console", + version: "1.2.3-fixedVersionForTesting", + }, + { + copyright: + "Copyright 2010 James Halliday (mail@substack.net); Modified work Copyright 2014 Contributors (ben@npmjs.com)", + license: "MIT", + name: "yargs", + version: "1.2.3-fixedVersionForTesting", + text: 'MIT License\\n\\nCopyright 2010 James Halliday (mail@substack.net); Modified work Copyright 2014 Contributors (ben@npmjs.com)\\n\\nPermission is hereby granted, free of charge, to any person obtaining a copy\\nof this software and associated documentation files (the "Software"), to deal\\nin the Software without restriction, including without limitation the rights\\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\\ncopies of the Software, and to permit persons to whom the Software is\\nfurnished to do so, subject to the following conditions:\\n\\nThe above copyright notice and this permission notice shall be included in\\nall copies or substantial portions of the Software.\\n\\nTHE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\\nTHE SOFTWARE.', + }, + { + copyright: "Microsoft Open Source", + license: "Unlicensed", + name: "private-package", + version: "1.0.0", + }, + ], + }); + }); });