@angular/build
Version:
Official build system for Angular
158 lines (157 loc) • 6.9 kB
JavaScript
;
/**
* @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 <filename>"
* }
* ```
*/
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;
}