"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.extractLicenses = extractLicenses; const promises_1 = require("node:fs/promises"); const node_path_1 = __importDefault(require("node:path")); /** * The path segment used to signify that a file is part of a package. */ const NODE_MODULE_SEGMENT = 'node_modules'; /** * String constant for the NPM recommended custom license wording. * * See: https://docs.npmjs.com/cli/v9/configuring-npm/package-json#license * * Example: * ``` * { * "license" : "SEE LICENSE IN " * } * ``` */ const CUSTOM_LICENSE_TEXT = 'SEE LICENSE IN '; /** * A list of commonly named license files found within packages. */ const LICENSE_FILES = ['LICENSE', 'LICENSE.txt', 'LICENSE.md']; /** * Header text that will be added to the top of the output license extraction file. */ const EXTRACTION_FILE_HEADER = ''; /** * The package entry separator to use within the output license extraction file. */ const EXTRACTION_FILE_SEPARATOR = '-'.repeat(80) + '\n'; /** * Extracts license information for each node module package included in the output * files of the built code. This includes JavaScript and CSS output files. The esbuild * metafile generated during the bundling steps is used as the source of information * regarding what input files where included and where they are located. A path segment * of `node_modules` is used to indicate that a file belongs to a package and its license * should be include in the output licenses file. * * The package name and license field are extracted from the `package.json` file for the * package. If a license file (e.g., `LICENSE`) is present in the root of the package, it * will also be included in the output licenses file. * * @param metafile An esbuild metafile object. * @param rootDirectory The root directory of the workspace. * @returns A string containing the content of the output licenses file. */ async function extractLicenses(metafile, rootDirectory) { let extractedLicenseContent = `${EXTRACTION_FILE_HEADER}\n${EXTRACTION_FILE_SEPARATOR}`; const seenPaths = new Set(); const seenPackages = new Set(); for (const entry of Object.values(metafile.outputs)) { for (const [inputPath, { bytesInOutput }] of Object.entries(entry.inputs)) { // Skip if not included in output if (bytesInOutput <= 0) { continue; } // Skip already processed paths if (seenPaths.has(inputPath)) { continue; } seenPaths.add(inputPath); // Skip non-package paths if (!inputPath.includes(NODE_MODULE_SEGMENT)) { continue; } // Extract the package name from the path let baseDirectory = node_path_1.default.join(rootDirectory, inputPath); let nameOrScope, nameOrFile; let found = false; while (baseDirectory !== node_path_1.default.dirname(baseDirectory)) { const segment = node_path_1.default.basename(baseDirectory); if (segment === NODE_MODULE_SEGMENT) { found = true; break; } nameOrFile = nameOrScope; nameOrScope = segment; baseDirectory = node_path_1.default.dirname(baseDirectory); } // Skip non-package path edge cases that are not caught in the includes check above if (!found || !nameOrScope) { continue; } const packageName = nameOrScope.startsWith('@') ? `${nameOrScope}/${nameOrFile}` : nameOrScope; const packageDirectory = node_path_1.default.join(baseDirectory, packageName); // Load the package's metadata to find the package's name, version, and license type const packageJsonPath = node_path_1.default.join(packageDirectory, 'package.json'); let packageJson; try { packageJson = JSON.parse(await (0, promises_1.readFile)(packageJsonPath, 'utf-8')); } catch { // Invalid package continue; } // Skip already processed packages const packageId = `${packageName}@${packageJson.version}`; if (seenPackages.has(packageId)) { continue; } seenPackages.add(packageId); // Attempt to find license text inside package let licenseText = ''; if (typeof packageJson.license === 'string' && packageJson.license.toLowerCase().startsWith(CUSTOM_LICENSE_TEXT)) { // Attempt to load the package's custom license let customLicensePath; const customLicenseFile = node_path_1.default.normalize(packageJson.license.slice(CUSTOM_LICENSE_TEXT.length + 1).trim()); if (customLicenseFile.startsWith('..') || node_path_1.default.isAbsolute(customLicenseFile)) { // Path is attempting to access files outside of the package // TODO: Issue warning? } else { customLicensePath = node_path_1.default.join(packageDirectory, customLicenseFile); try { licenseText = await (0, promises_1.readFile)(customLicensePath, 'utf-8'); break; } catch { } } } else { // Search for a license file within the root of the package for (const potentialLicense of LICENSE_FILES) { const packageLicensePath = node_path_1.default.join(packageDirectory, potentialLicense); try { licenseText = await (0, promises_1.readFile)(packageLicensePath, 'utf-8'); break; } catch { } } } // Generate the package's license entry in the output content extractedLicenseContent += `Package: ${packageJson.name}\n`; extractedLicenseContent += `License: ${JSON.stringify(packageJson.license, null, 2)}\n`; extractedLicenseContent += `\n${licenseText}\n`; extractedLicenseContent += EXTRACTION_FILE_SEPARATOR; } } return extractedLicenseContent; }