mirror of
https://github.com/Alfresco/alfresco-ng2-components.git
synced 2026-09-09 18:03:21 +00:00
193 lines
6.0 KiB
JavaScript
193 lines
6.0 KiB
JavaScript
/*!
|
|
* @license
|
|
* Copyright © 2005-2026 Hyland Software, Inc. and its affiliates. All rights reserved.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
const fs = require('node:fs');
|
|
const path = require('node:path');
|
|
|
|
function readJson(filePath) {
|
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
}
|
|
|
|
function resolvePackageJson(packageName, fromDirectory) {
|
|
try {
|
|
return require.resolve(`${packageName}/package.json`, {
|
|
paths: [fromDirectory]
|
|
});
|
|
} catch {
|
|
return '';
|
|
}
|
|
}
|
|
|
|
function getRepositoryUrl(repository) {
|
|
if (typeof repository === 'string') {
|
|
return repository;
|
|
}
|
|
|
|
if (repository && typeof repository === 'object' && typeof repository.url === 'string') {
|
|
return repository.url;
|
|
}
|
|
|
|
return '';
|
|
}
|
|
|
|
function getRawLicenseExpression(packageJson) {
|
|
if (typeof packageJson.license === 'string' && packageJson.license.trim()) {
|
|
return packageJson.license.trim();
|
|
}
|
|
|
|
if (packageJson.license && typeof packageJson.license === 'object' && typeof packageJson.license.type === 'string') {
|
|
return packageJson.license.type.trim();
|
|
}
|
|
|
|
if (Array.isArray(packageJson.licenses)) {
|
|
const values = packageJson.licenses
|
|
.map((entry) => {
|
|
if (typeof entry === 'string') {
|
|
return entry.trim();
|
|
}
|
|
|
|
if (entry && typeof entry === 'object' && typeof entry.type === 'string') {
|
|
return entry.type.trim();
|
|
}
|
|
|
|
return '';
|
|
})
|
|
.filter(Boolean);
|
|
|
|
if (values.length > 0) {
|
|
return values.join(' OR ');
|
|
}
|
|
}
|
|
|
|
return 'UNKNOWN';
|
|
}
|
|
|
|
function extractLicenseTokens(rawExpression) {
|
|
const tokens = (rawExpression || '').match(/[A-Za-z0-9-.+]+/g) || [];
|
|
const operators = new Set(['AND', 'OR', 'WITH']);
|
|
|
|
return tokens.map((token) => token.toUpperCase()).filter((token) => !operators.has(token));
|
|
}
|
|
|
|
function hasDeniedLicense(rawExpression, denyList) {
|
|
const licenseTokens = extractLicenseTokens(rawExpression);
|
|
|
|
return denyList.some((rule) => {
|
|
const normalizedRule = (rule || '').trim().toUpperCase();
|
|
if (!normalizedRule) {
|
|
return false;
|
|
}
|
|
|
|
// Treat common copyleft family keywords as SPDX id prefixes.
|
|
if (normalizedRule === 'GPL' || normalizedRule === 'LGPL' || normalizedRule === 'AGPL') {
|
|
return licenseTokens.some((token) => token === normalizedRule || token.startsWith(`${normalizedRule}-`));
|
|
}
|
|
|
|
// Support explicit wildcard rules like "GPL-*".
|
|
if (normalizedRule.endsWith('*')) {
|
|
const prefix = normalizedRule.slice(0, -1);
|
|
return prefix.length > 0 && licenseTokens.some((token) => token.startsWith(prefix));
|
|
}
|
|
|
|
// Otherwise require exact SPDX token match.
|
|
return licenseTokens.includes(normalizedRule);
|
|
});
|
|
}
|
|
|
|
function getDependencyNames(packageJson) {
|
|
return Array.from(new Set([...Object.keys(packageJson.dependencies || {}), ...Object.keys(packageJson.optionalDependencies || {})]));
|
|
}
|
|
|
|
function collectProductionLicenses(packagePath, options = {}) {
|
|
const packageJsonFile = readJson(packagePath);
|
|
const mainDependencies = getDependencyNames(packageJsonFile);
|
|
const packageDirectory = path.dirname(packagePath);
|
|
const missingRepositories = options.missingRepositories || {};
|
|
const denyList = options.denyList || [];
|
|
|
|
const queue = mainDependencies.map((name) => ({
|
|
name,
|
|
fromDirectory: packageDirectory
|
|
}));
|
|
|
|
const visited = new Set();
|
|
const packages = [];
|
|
const deniedPackages = [];
|
|
|
|
for (const current of queue) {
|
|
const packageJsonPath = resolvePackageJson(current.name, current.fromDirectory);
|
|
|
|
if (!packageJsonPath || !fs.existsSync(packageJsonPath)) {
|
|
continue;
|
|
}
|
|
|
|
const currentPackage = readJson(packageJsonPath);
|
|
const name = currentPackage.name || current.name;
|
|
const version = currentPackage.version || 'unknown';
|
|
const packageFolder = path.dirname(packageJsonPath);
|
|
const uniqueKey = `${name}@${version}:${packageFolder}`;
|
|
|
|
if (visited.has(uniqueKey)) {
|
|
continue;
|
|
}
|
|
visited.add(uniqueKey);
|
|
|
|
const rawLicenseExpression = getRawLicenseExpression(currentPackage);
|
|
const packageKey = `${name}@${version}`;
|
|
const repository = getRepositoryUrl(currentPackage.repository) || missingRepositories[name] || '';
|
|
|
|
const record = {
|
|
key: packageKey,
|
|
name,
|
|
version,
|
|
repository,
|
|
rawLicenseExpression,
|
|
dependencies: currentPackage.dependencies || {},
|
|
optionalDependencies: currentPackage.optionalDependencies || {}
|
|
};
|
|
|
|
packages.push(record);
|
|
|
|
if (hasDeniedLicense(rawLicenseExpression, denyList)) {
|
|
deniedPackages.push(`${packageKey}: ${rawLicenseExpression}`);
|
|
}
|
|
|
|
const dependencies = getDependencyNames(currentPackage);
|
|
for (const dependencyName of dependencies) {
|
|
queue.push({
|
|
name: dependencyName,
|
|
fromDirectory: packageFolder
|
|
});
|
|
}
|
|
}
|
|
|
|
packages.sort((a, b) => {
|
|
const nameCompare = a.name.localeCompare(b.name);
|
|
if (nameCompare !== 0) {
|
|
return nameCompare;
|
|
}
|
|
|
|
return a.version.localeCompare(b.version);
|
|
});
|
|
|
|
return { packages, deniedPackages };
|
|
}
|
|
|
|
module.exports = {
|
|
collectProductionLicenses
|
|
};
|