@callstack/licenses 0.3.3 → 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/build/constants/licenses.d.ts +4 -0
- package/build/constants/licenses.js +15 -15
- package/build/licenses/OrPolicy.d.ts +21 -0
- package/build/licenses/OrPolicy.js +16 -0
- package/build/licenses/index.d.ts +3 -0
- package/build/licenses/index.js +3 -0
- package/build/licenses/licenseAnalysis.d.ts +7 -8
- package/build/licenses/licenseAnalysis.js +15 -29
- package/build/licenses/licenseClassification.d.ts +52 -0
- package/build/licenses/licenseClassification.js +136 -0
- package/build/licenses/licenseExpression.d.ts +37 -0
- package/build/licenses/licenseExpression.js +166 -0
- package/build/node/common.d.ts +23 -2
- package/build/node/common.js +157 -54
- package/build/node/index.d.ts +1 -0
- package/build/node/index.js +1 -0
- package/build/node/mergeLicensesMappings.d.ts +8 -0
- package/build/node/mergeLicensesMappings.js +28 -0
- package/build/node/utils/aboutLibrariesLicenses.d.ts +11 -0
- package/build/node/utils/aboutLibrariesLicenses.js +58 -0
- package/build/node/utils/licenseFiles.d.ts +8 -0
- package/build/node/utils/licenseFiles.js +45 -0
- package/build/node/utils/licensePlistBody.d.ts +6 -0
- package/build/node/utils/licensePlistBody.js +20 -0
- package/build/node/utils/packageUtils.d.ts +14 -5
- package/build/node/utils/packageUtils.js +35 -23
- package/build/types/AboutLibrariesLikePackageInfo.d.ts +1 -1
- package/build/types/License.d.ts +14 -6
- package/build/types/LicenseAnalysisResult.d.ts +7 -0
- package/build/types/LicenseExpression.d.ts +39 -0
- package/build/types/LicenseExpression.js +2 -0
- package/build/types/LicenseFile.d.ts +8 -0
- package/build/types/LicenseFile.js +2 -0
- package/build/types/LicenseStats.d.ts +1 -1
- package/build/types/ScanDependenciesOptions.d.ts +12 -0
- package/build/types/ScanDependenciesOptions.js +2 -0
- package/build/types/index.d.ts +3 -0
- package/build/types/index.js +3 -0
- package/package.json +6 -2
- package/src/constants/licenses.ts +15 -15
- package/src/licenses/OrPolicy.ts +23 -0
- package/src/licenses/index.ts +3 -0
- package/src/licenses/licenseAnalysis.ts +19 -33
- package/src/licenses/licenseClassification.ts +173 -0
- package/src/licenses/licenseExpression.ts +200 -0
- package/src/node/common.ts +216 -65
- package/src/node/index.ts +1 -0
- package/src/node/mergeLicensesMappings.ts +32 -0
- package/src/node/utils/aboutLibrariesLicenses.ts +69 -0
- package/src/node/utils/licenseFiles.ts +50 -0
- package/src/node/utils/licensePlistBody.ts +18 -0
- package/src/node/utils/packageUtils.ts +37 -26
- package/src/types/AboutLibrariesLikePackageInfo.ts +1 -1
- package/src/types/License.ts +16 -7
- package/src/types/LicenseAnalysisResult.ts +8 -0
- package/src/types/LicenseExpression.ts +22 -0
- package/src/types/LicenseFile.ts +10 -0
- package/src/types/LicenseStats.ts +1 -1
- package/src/types/ScanDependenciesOptions.ts +12 -0
- package/src/types/index.ts +3 -0
package/build/node/common.d.ts
CHANGED
|
@@ -1,12 +1,33 @@
|
|
|
1
|
-
import type { AboutLibrariesLikePackageInfo, AggregatedLicensesMapping, ScanPackageOptionsFactory } from '../types';
|
|
1
|
+
import type { AboutLibrariesLikePackageInfo, AggregatedLicensesMapping, ScanDependenciesOptions, ScanPackageOptionsFactory } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Finds the root directory of the package that a directory belongs to - the closest directory (itself or a parent)
|
|
4
|
+
* containing a `package.json` with a `name` field. Nested `package.json` files without a name
|
|
5
|
+
* (e.g. `lib/commonjs/package.json` containing only `{ "type": "commonjs" }`) are skipped.
|
|
6
|
+
*
|
|
7
|
+
* Works for packages installed in `node_modules` as well as for workspace / linked packages,
|
|
8
|
+
* which are referenced by their real path (outside `node_modules`)
|
|
9
|
+
*
|
|
10
|
+
* @param startDir Absolute path to the directory to start the search from, e.g. the directory of a module file
|
|
11
|
+
* @returns Path to the package root directory or `null` if the directory does not belong to any named package
|
|
12
|
+
*/
|
|
13
|
+
export declare function findPackageRoot(startDir: string): string | null;
|
|
14
|
+
/**
|
|
15
|
+
* Collects license information for a given list of package directories, e.g. the packages found in the Metro dependency graph.
|
|
16
|
+
* Unlike {@link scanDependencies}, it does not scan dependencies of the packages - the list is expected to be complete.
|
|
17
|
+
*
|
|
18
|
+
* @param packageRoots Paths to the root directories of the packages (directories containing `package.json`)
|
|
19
|
+
* @returns Aggregated licenses object containing the given packages and their license information
|
|
20
|
+
*/
|
|
21
|
+
export declare function scanPackageRoots(packageRoots: string[]): AggregatedLicensesMapping;
|
|
2
22
|
/**
|
|
3
23
|
* Scans `package.json` and searches for all packages under `dependencies` field. Supports monorepo projects.
|
|
4
24
|
*
|
|
5
25
|
* @param appPackageJsonPath Path to the `package.json` file of the application
|
|
6
26
|
* @param scanOptionsFactory Factory function to create scan options for dependencies; defaults to {@link PackageUtils.legacyDefaultScanPackageOptionsFactory}
|
|
27
|
+
* @param options Additional options, see {@link ScanDependenciesOptions}
|
|
7
28
|
* @returns Aggregated licenses object containing all scanned dependencies and their license information
|
|
8
29
|
*/
|
|
9
|
-
export declare function scanDependencies(appPackageJsonPath: string, scanOptionsFactory?: ScanPackageOptionsFactory): AggregatedLicensesMapping;
|
|
30
|
+
export declare function scanDependencies(appPackageJsonPath: string, scanOptionsFactory?: ScanPackageOptionsFactory, options?: ScanDependenciesOptions): AggregatedLicensesMapping;
|
|
10
31
|
/**
|
|
11
32
|
* Generates LicensePlist-compatible metadata for NPM dependencies as a YAML string.
|
|
12
33
|
*
|
package/build/node/common.js
CHANGED
|
@@ -3,6 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.findPackageRoot = findPackageRoot;
|
|
7
|
+
exports.scanPackageRoots = scanPackageRoots;
|
|
6
8
|
exports.scanDependencies = scanDependencies;
|
|
7
9
|
exports.generateLicensePlistNPMOutput = generateLicensePlistNPMOutput;
|
|
8
10
|
exports.writeLicensePlistNPMOutput = writeLicensePlistNPMOutput;
|
|
@@ -11,8 +13,109 @@ exports.writeAboutLibrariesNPMOutput = writeAboutLibrariesNPMOutput;
|
|
|
11
13
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
14
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
15
|
const glob_1 = require("glob");
|
|
16
|
+
const licenses_1 = require("../licenses");
|
|
14
17
|
const utils_1 = require("../utils");
|
|
18
|
+
const mergeLicensesMappings_1 = require("./mergeLicensesMappings");
|
|
15
19
|
const utils_2 = require("./utils");
|
|
20
|
+
/**
|
|
21
|
+
* Finds the root directory of the package that a directory belongs to - the closest directory (itself or a parent)
|
|
22
|
+
* containing a `package.json` with a `name` field. Nested `package.json` files without a name
|
|
23
|
+
* (e.g. `lib/commonjs/package.json` containing only `{ "type": "commonjs" }`) are skipped.
|
|
24
|
+
*
|
|
25
|
+
* Works for packages installed in `node_modules` as well as for workspace / linked packages,
|
|
26
|
+
* which are referenced by their real path (outside `node_modules`)
|
|
27
|
+
*
|
|
28
|
+
* @param startDir Absolute path to the directory to start the search from, e.g. the directory of a module file
|
|
29
|
+
* @returns Path to the package root directory or `null` if the directory does not belong to any named package
|
|
30
|
+
*/
|
|
31
|
+
function findPackageRoot(startDir) {
|
|
32
|
+
let dir = startDir;
|
|
33
|
+
// `path.dirname` of the filesystem root returns the root itself, which ends the loop
|
|
34
|
+
while (node_path_1.default.dirname(dir) !== dir) {
|
|
35
|
+
if (hasNamedPackageJson(dir)) {
|
|
36
|
+
return dir;
|
|
37
|
+
}
|
|
38
|
+
dir = node_path_1.default.dirname(dir);
|
|
39
|
+
}
|
|
40
|
+
return hasNamedPackageJson(dir) ? dir : null;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Checks whether a directory contains a valid `package.json` with a `name` field
|
|
44
|
+
*
|
|
45
|
+
* @param dir Path to the directory to check
|
|
46
|
+
*/
|
|
47
|
+
function hasNamedPackageJson(dir) {
|
|
48
|
+
const packageJsonPath = node_path_1.default.join(dir, 'package.json');
|
|
49
|
+
if (!node_fs_1.default.existsSync(packageJsonPath)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
const packageJson = JSON.parse(node_fs_1.default.readFileSync(packageJsonPath, { encoding: 'utf-8' }));
|
|
54
|
+
return Boolean(packageJson.name);
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
// invalid package.json - treat it as not a package root
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Collects license information for a given list of package directories, e.g. the packages found in the Metro dependency graph.
|
|
63
|
+
* Unlike {@link scanDependencies}, it does not scan dependencies of the packages - the list is expected to be complete.
|
|
64
|
+
*
|
|
65
|
+
* @param packageRoots Paths to the root directories of the packages (directories containing `package.json`)
|
|
66
|
+
* @returns Aggregated licenses object containing the given packages and their license information
|
|
67
|
+
*/
|
|
68
|
+
function scanPackageRoots(packageRoots) {
|
|
69
|
+
const result = {};
|
|
70
|
+
for (const packageRoot of packageRoots) {
|
|
71
|
+
const packageJsonPath = node_path_1.default.join(packageRoot, 'package.json');
|
|
72
|
+
if (!node_fs_1.default.existsSync(packageJsonPath)) {
|
|
73
|
+
console.warn(`[react-native-legal] skipping ${packageRoot} could not find package.json`);
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
try {
|
|
77
|
+
const packageJson = require(node_path_1.default.resolve(packageJsonPath));
|
|
78
|
+
if (!packageJson.name || packageJson.private === true) {
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
const licenseInfo = readPackageLicenseInfo(packageJsonPath);
|
|
82
|
+
result[`${licenseInfo.name}@${licenseInfo.version}`] = {
|
|
83
|
+
...licenseInfo,
|
|
84
|
+
dependencyType: 'dependency',
|
|
85
|
+
requiredVersion: licenseInfo.version,
|
|
86
|
+
parentPackages: [],
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
console.warn(`[react-native-legal] could not process package.json in ${packageRoot}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Reads license information of a single package, based only on its own files
|
|
97
|
+
* (package.json and LICENSE file), without any context of how the package was found
|
|
98
|
+
*
|
|
99
|
+
* @param packageJsonPath Path to the package.json file of the package
|
|
100
|
+
*/
|
|
101
|
+
function readPackageLicenseInfo(packageJsonPath) {
|
|
102
|
+
const packageJson = require(node_path_1.default.resolve(packageJsonPath));
|
|
103
|
+
const rawLicense = utils_2.PackageUtils.parseLicenseField(packageJson) ?? null;
|
|
104
|
+
const license = (0, licenses_1.parseLicenseExpression)(rawLicense);
|
|
105
|
+
const licenseIds = (0, licenses_1.collectLicenseIds)(license);
|
|
106
|
+
const licenseFiles = utils_2.PackageUtils.readLicenseFiles(node_path_1.default.dirname(packageJsonPath), licenseIds);
|
|
107
|
+
return {
|
|
108
|
+
name: packageJson.name,
|
|
109
|
+
author: utils_2.PackageUtils.parseAuthorField(packageJson),
|
|
110
|
+
description: packageJson.description,
|
|
111
|
+
rawLicense,
|
|
112
|
+
license,
|
|
113
|
+
licenseIds,
|
|
114
|
+
licenseFiles,
|
|
115
|
+
url: utils_2.PackageUtils.parseRepositoryFieldToUrl(packageJson),
|
|
116
|
+
version: packageJson.version,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
16
119
|
/**
|
|
17
120
|
* Scans a single package and its dependencies for license information
|
|
18
121
|
*
|
|
@@ -24,7 +127,7 @@ const utils_2 = require("./utils");
|
|
|
24
127
|
* @param isOptionalDependency Whether the package is an optional dependency, in which case a warning will not be logged if the corresponding package.json is not found; defaults to `false`
|
|
25
128
|
* @param parentPackageRoot Optional path to the parent package root, has priority over default root to lock for dependencies in; used to discover different versions of the same package installed in nested node_modules, e.g. suppose `X@1`, `Y@1` where `Y@1` -> `X@2`; then, node_modules would have `X@1`, `Y@1` and `X@2` would be installed to `node_modules/Y/node_modules/X@2`
|
|
26
129
|
*/
|
|
27
|
-
function scanPackage(packageName, requiredVersion, processedPackages, result, scanOptionsFactory = utils_2.PackageUtils.legacyDefaultScanPackageOptionsFactory, { parentPackageRoot, parentPackageName, dependencyType, parentPackageRequiredVersion, parentPackageResolvedVersion, }) {
|
|
130
|
+
function scanPackage(packageName, requiredVersion, processedPackages, result, scanOptionsFactory = utils_2.PackageUtils.legacyDefaultScanPackageOptionsFactory, { parentPackageRoot, parentPackageName, dependencyType, parentPackageRequiredVersion, parentPackageResolvedVersion, projectRoot, }) {
|
|
28
131
|
const requiredVersionPackageKey = `${packageName}@${requiredVersion}`;
|
|
29
132
|
// Skip if already processed to avoid circular dependencies
|
|
30
133
|
if (processedPackages.has(requiredVersionPackageKey)) {
|
|
@@ -36,7 +139,7 @@ function scanPackage(packageName, requiredVersion, processedPackages, result, sc
|
|
|
36
139
|
}
|
|
37
140
|
processedPackages.add(requiredVersionPackageKey);
|
|
38
141
|
try {
|
|
39
|
-
const localPackageJsonPath = utils_2.PackageUtils.getPackageJsonPath(packageName, parentPackageRoot);
|
|
142
|
+
const localPackageJsonPath = utils_2.PackageUtils.getPackageJsonPath(packageName, parentPackageRoot, projectRoot);
|
|
40
143
|
if (!localPackageJsonPath) {
|
|
41
144
|
// do not warn if the package is an optional dependency, it's normal it may not be installed
|
|
42
145
|
if (!dependencyType.toLowerCase().includes('optional')) {
|
|
@@ -46,13 +149,6 @@ function scanPackage(packageName, requiredVersion, processedPackages, result, sc
|
|
|
46
149
|
}
|
|
47
150
|
const localPackageJson = require(node_path_1.default.resolve(localPackageJsonPath));
|
|
48
151
|
if (localPackageJson.private !== true) {
|
|
49
|
-
const licenseFiles = glob_1.glob.sync('LICEN{S,C}E{.md,}', {
|
|
50
|
-
cwd: node_path_1.default.dirname(localPackageJsonPath),
|
|
51
|
-
absolute: true,
|
|
52
|
-
nocase: true,
|
|
53
|
-
nodir: true,
|
|
54
|
-
ignore: '**/{__tests__,__fixtures__,__mocks__}/**',
|
|
55
|
-
});
|
|
56
152
|
const resolvedVersionPackageKey = `${packageName}@${localPackageJson.version}`;
|
|
57
153
|
let parentPackageInfo;
|
|
58
154
|
if (parentPackageName && parentPackageRequiredVersion && parentPackageResolvedVersion) {
|
|
@@ -70,14 +166,8 @@ function scanPackage(packageName, requiredVersion, processedPackages, result, sc
|
|
|
70
166
|
}
|
|
71
167
|
else {
|
|
72
168
|
result[resolvedVersionPackageKey] = {
|
|
169
|
+
...readPackageLicenseInfo(localPackageJsonPath),
|
|
73
170
|
name: packageName,
|
|
74
|
-
author: utils_2.PackageUtils.parseAuthorField(localPackageJson),
|
|
75
|
-
content: licenseFiles?.[0] ? node_fs_1.default.readFileSync(licenseFiles[0], { encoding: 'utf-8' }) : undefined,
|
|
76
|
-
file: licenseFiles?.[0] ? licenseFiles[0] : undefined,
|
|
77
|
-
description: localPackageJson.description,
|
|
78
|
-
type: utils_2.PackageUtils.parseLicenseField(localPackageJson),
|
|
79
|
-
url: utils_2.PackageUtils.parseRepositoryFieldToUrl(localPackageJson),
|
|
80
|
-
version: localPackageJson.version,
|
|
81
171
|
requiredVersion,
|
|
82
172
|
parentPackages: parentPackageInfo ? [parentPackageInfo] : [],
|
|
83
173
|
parentPackageRequiredVersion,
|
|
@@ -121,6 +211,7 @@ function scanPackage(packageName, requiredVersion, processedPackages, result, sc
|
|
|
121
211
|
parentPackageName: packageName,
|
|
122
212
|
parentPackageRequiredVersion: requiredVersion,
|
|
123
213
|
parentPackageResolvedVersion: localPackageJson.version,
|
|
214
|
+
projectRoot,
|
|
124
215
|
});
|
|
125
216
|
}
|
|
126
217
|
}
|
|
@@ -134,13 +225,32 @@ function scanPackage(packageName, requiredVersion, processedPackages, result, sc
|
|
|
134
225
|
*
|
|
135
226
|
* @param appPackageJsonPath Path to the `package.json` file of the application
|
|
136
227
|
* @param scanOptionsFactory Factory function to create scan options for dependencies; defaults to {@link PackageUtils.legacyDefaultScanPackageOptionsFactory}
|
|
228
|
+
* @param options Additional options, see {@link ScanDependenciesOptions}
|
|
137
229
|
* @returns Aggregated licenses object containing all scanned dependencies and their license information
|
|
138
230
|
*/
|
|
139
|
-
function scanDependencies(appPackageJsonPath, scanOptionsFactory = utils_2.PackageUtils.legacyDefaultScanPackageOptionsFactory) {
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
231
|
+
function scanDependencies(appPackageJsonPath, scanOptionsFactory = utils_2.PackageUtils.legacyDefaultScanPackageOptionsFactory, options = {}) {
|
|
232
|
+
const { additionalProjectRoots = [] } = options;
|
|
233
|
+
if (!Array.isArray(additionalProjectRoots) ||
|
|
234
|
+
additionalProjectRoots.some((additionalProjectRoot) => typeof additionalProjectRoot !== 'string' || !additionalProjectRoot)) {
|
|
235
|
+
throw new TypeError('[react-native-legal] additionalProjectRoots must be an array of non-empty paths');
|
|
236
|
+
}
|
|
237
|
+
const appRoot = node_fs_1.default.realpathSync(node_path_1.default.dirname(node_path_1.default.resolve(appPackageJsonPath)));
|
|
238
|
+
const additionalPackageJsonPaths = additionalProjectRoots.map((additionalProjectRoot) => {
|
|
239
|
+
const packageJsonPath = node_path_1.default.join(node_path_1.default.resolve(appRoot, additionalProjectRoot), 'package.json');
|
|
240
|
+
if (!node_fs_1.default.existsSync(packageJsonPath)) {
|
|
241
|
+
throw new Error(`[react-native-legal] additional project root "${additionalProjectRoot}" is not a directory with a package.json (${packageJsonPath} not found)`);
|
|
242
|
+
}
|
|
243
|
+
return packageJsonPath;
|
|
244
|
+
});
|
|
245
|
+
return (0, mergeLicensesMappings_1.mergeLicensesMappings)(scanProjectDependencies(appPackageJsonPath, scanOptionsFactory), ...additionalPackageJsonPaths.map((packageJsonPath) => scanProjectDependencies(packageJsonPath, scanOptionsFactory)));
|
|
246
|
+
}
|
|
247
|
+
function scanProjectDependencies(projectPackageJsonPath, scanOptionsFactory) {
|
|
248
|
+
const projectPackageJson = require(node_path_1.default.resolve(projectPackageJsonPath));
|
|
249
|
+
// real path, so that `node_modules` directories of the project's parent directories are found even if the project is reached through a symlink
|
|
250
|
+
const projectRoot = node_fs_1.default.realpathSync(node_path_1.default.dirname(node_path_1.default.resolve(projectPackageJsonPath)));
|
|
251
|
+
const dependencies = projectPackageJson.dependencies;
|
|
252
|
+
const devDependencies = projectPackageJson.devDependencies;
|
|
253
|
+
const optionalDependencies = projectPackageJson.optionalDependencies;
|
|
144
254
|
const result = {};
|
|
145
255
|
const processedPackages = new Set();
|
|
146
256
|
const rootScanOptions = scanOptionsFactory({ isRoot: true, isWorkspacePackage: false });
|
|
@@ -161,6 +271,7 @@ function scanDependencies(appPackageJsonPath, scanOptionsFactory = utils_2.Packa
|
|
|
161
271
|
for (const [depName, depVersion] of packages) {
|
|
162
272
|
scanPackage(depName, depVersion, processedPackages, result, scanOptionsFactory, {
|
|
163
273
|
dependencyType,
|
|
274
|
+
projectRoot,
|
|
164
275
|
});
|
|
165
276
|
}
|
|
166
277
|
}
|
|
@@ -183,12 +294,16 @@ function generateLicensePlistNPMOutput(licenses, iosProjectPath) {
|
|
|
183
294
|
if (license.name !== normalizedPackageNameWithVersion) {
|
|
184
295
|
renames[normalizedPackageNameWithVersion] = license.name;
|
|
185
296
|
}
|
|
186
|
-
|
|
297
|
+
// LicensePlist holds one `file`/`body` per package: a single license file is referenced directly,
|
|
298
|
+
// otherwise every license text is concatenated into the body under a per-license heading
|
|
299
|
+
const singleLicenseFile = license.licenseFiles.length === 1 ? license.licenseFiles[0].file : undefined;
|
|
187
300
|
return {
|
|
188
301
|
name: normalizedPackageNameWithVersion,
|
|
189
302
|
version: license.version,
|
|
190
303
|
...(license.url && { source: license.url }),
|
|
191
|
-
...(
|
|
304
|
+
...(singleLicenseFile
|
|
305
|
+
? { file: node_path_1.default.relative(iosProjectPath, singleLicenseFile) }
|
|
306
|
+
: { body: utils_2.PackageUtils.buildLicensePlistBody(license) }),
|
|
192
307
|
};
|
|
193
308
|
});
|
|
194
309
|
const excludedEntries = Object.values(licenses).reduce((acc, license) => {
|
|
@@ -270,42 +385,28 @@ function writeLicensePlistNPMOutput(licenses, iosProjectPath, plistLikeOutput) {
|
|
|
270
385
|
* @see {@link writeAboutLibrariesNPMOutput}
|
|
271
386
|
*/
|
|
272
387
|
function generateAboutLibrariesNPMOutput(licenses) {
|
|
273
|
-
return Object.entries(licenses)
|
|
274
|
-
.
|
|
275
|
-
|
|
388
|
+
return Object.entries(licenses).map(([packageKey, license]) => {
|
|
389
|
+
const uniqueId = utils_2.PackageUtils.normalizePackageName(packageKey);
|
|
390
|
+
const licenseJsonPayloads = utils_2.PackageUtils.prepareAboutLibrariesLicenses(license).map(({ name, content }) => ({
|
|
391
|
+
content,
|
|
392
|
+
hash: utils_2.PackageUtils.prepareAboutLibrariesLicenseField(name, content),
|
|
393
|
+
name,
|
|
394
|
+
url: '',
|
|
395
|
+
}));
|
|
396
|
+
const libraryJsonPayload = {
|
|
276
397
|
artifactVersion: license.version,
|
|
277
|
-
content: license.content ?? '',
|
|
278
398
|
description: license.description ?? '',
|
|
279
399
|
developers: [{ name: license.author ?? '', organisationUrl: '' }],
|
|
280
|
-
licenses:
|
|
400
|
+
licenses: licenseJsonPayloads.map(({ hash }) => hash),
|
|
281
401
|
name: license.name,
|
|
282
402
|
tag: '',
|
|
283
|
-
|
|
284
|
-
uniqueId: utils_2.PackageUtils.normalizePackageName(packageKey),
|
|
403
|
+
uniqueId,
|
|
285
404
|
website: license.url,
|
|
286
405
|
};
|
|
287
|
-
})
|
|
288
|
-
.map((jsonPayload) => {
|
|
289
|
-
const libraryJsonPayload = {
|
|
290
|
-
artifactVersion: jsonPayload.artifactVersion,
|
|
291
|
-
description: jsonPayload.description,
|
|
292
|
-
developers: jsonPayload.developers,
|
|
293
|
-
licenses: jsonPayload.licenses,
|
|
294
|
-
name: jsonPayload.name,
|
|
295
|
-
tag: jsonPayload.tag,
|
|
296
|
-
uniqueId: jsonPayload.uniqueId,
|
|
297
|
-
website: jsonPayload.website,
|
|
298
|
-
};
|
|
299
|
-
const licenseJsonPayload = {
|
|
300
|
-
content: jsonPayload.content,
|
|
301
|
-
hash: jsonPayload.licenses[0],
|
|
302
|
-
name: jsonPayload.type ?? '',
|
|
303
|
-
url: '',
|
|
304
|
-
};
|
|
305
406
|
return {
|
|
306
|
-
normalizedPackageNameWithVersion:
|
|
407
|
+
normalizedPackageNameWithVersion: uniqueId,
|
|
307
408
|
libraryJsonPayload,
|
|
308
|
-
|
|
409
|
+
licenseJsonPayloads,
|
|
309
410
|
};
|
|
310
411
|
});
|
|
311
412
|
}
|
|
@@ -345,12 +446,14 @@ function writeAboutLibrariesNPMOutput(licenses, androidProjectPath, aboutLibrari
|
|
|
345
446
|
if (!aboutLibrariesLikeOutput) {
|
|
346
447
|
aboutLibrariesLikeOutput = generateAboutLibrariesNPMOutput(licenses);
|
|
347
448
|
}
|
|
348
|
-
aboutLibrariesLikeOutput.forEach(({ normalizedPackageNameWithVersion, libraryJsonPayload,
|
|
449
|
+
aboutLibrariesLikeOutput.forEach(({ normalizedPackageNameWithVersion, libraryJsonPayload, licenseJsonPayloads }) => {
|
|
349
450
|
const libraryJsonFilePath = node_path_1.default.join(aboutLibrariesConfigLibrariesDirPath, `${normalizedPackageNameWithVersion}.json`);
|
|
350
|
-
const licenseJsonFilePath = node_path_1.default.join(aboutLibrariesConfigLicensesDirPath, `${licenseJsonPayload.hash}.json`);
|
|
351
451
|
node_fs_1.default.writeFileSync(libraryJsonFilePath, JSON.stringify(libraryJsonPayload));
|
|
352
|
-
|
|
353
|
-
|
|
452
|
+
for (const licenseJsonPayload of licenseJsonPayloads) {
|
|
453
|
+
const licenseJsonFilePath = node_path_1.default.join(aboutLibrariesConfigLicensesDirPath, `${licenseJsonPayload.hash}.json`);
|
|
454
|
+
if (!node_fs_1.default.existsSync(licenseJsonFilePath)) {
|
|
455
|
+
node_fs_1.default.writeFileSync(licenseJsonFilePath, JSON.stringify(licenseJsonPayload));
|
|
456
|
+
}
|
|
354
457
|
}
|
|
355
458
|
});
|
|
356
459
|
}
|
package/build/node/index.d.ts
CHANGED
package/build/node/index.js
CHANGED
|
@@ -15,5 +15,6 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./common"), exports);
|
|
18
|
+
__exportStar(require("./mergeLicensesMappings"), exports);
|
|
18
19
|
__exportStar(require("./utils"), exports);
|
|
19
20
|
__exportStar(require("../"), exports);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AggregatedLicensesMapping } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Combines licenses mappings, e.g. from scans of several project roots, into one.
|
|
4
|
+
*
|
|
5
|
+
* @param mappings Licenses mappings to combine; a package version present in several of them keeps the entry from the first one, with the parent packages of all of them
|
|
6
|
+
* @returns A new licenses mapping that shares no objects with the mappings passed in
|
|
7
|
+
*/
|
|
8
|
+
export declare function mergeLicensesMappings(...mappings: AggregatedLicensesMapping[]): AggregatedLicensesMapping;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.mergeLicensesMappings = mergeLicensesMappings;
|
|
4
|
+
const utils_1 = require("./utils");
|
|
5
|
+
/**
|
|
6
|
+
* Combines licenses mappings, e.g. from scans of several project roots, into one.
|
|
7
|
+
*
|
|
8
|
+
* @param mappings Licenses mappings to combine; a package version present in several of them keeps the entry from the first one, with the parent packages of all of them
|
|
9
|
+
* @returns A new licenses mapping that shares no objects with the mappings passed in
|
|
10
|
+
*/
|
|
11
|
+
function mergeLicensesMappings(...mappings) {
|
|
12
|
+
const result = {};
|
|
13
|
+
for (const mapping of mappings) {
|
|
14
|
+
for (const [packageKey, license] of Object.entries(mapping)) {
|
|
15
|
+
const existingLicense = result[packageKey];
|
|
16
|
+
if (!existingLicense) {
|
|
17
|
+
result[packageKey] = structuredClone({ ...license, parentPackages: license.parentPackages ?? [] });
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
for (const parentPackage of license.parentPackages ?? []) {
|
|
21
|
+
if (!utils_1.MiscUtils.arrayIncludesObject(existingLicense.parentPackages, parentPackage)) {
|
|
22
|
+
existingLicense.parentPackages.push(structuredClone(parentPackage));
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return result;
|
|
28
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { License } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* One AboutLibraries license per License Identifier (or the Raw License when it is Unknown). Its name keeps
|
|
4
|
+
* a retained `+` and the License Exception; its content is the linked license file, else every unlinked text
|
|
5
|
+
* when the package has one license or exactly one unlinked text.
|
|
6
|
+
*/
|
|
7
|
+
export declare function prepareAboutLibrariesLicenses(license: License): {
|
|
8
|
+
name: string;
|
|
9
|
+
content: string;
|
|
10
|
+
}[];
|
|
11
|
+
export declare function prepareAboutLibrariesLicenseField(name: string, content?: string): string;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.prepareAboutLibrariesLicenses = prepareAboutLibrariesLicenses;
|
|
4
|
+
exports.prepareAboutLibrariesLicenseField = prepareAboutLibrariesLicenseField;
|
|
5
|
+
const licenses_1 = require("../../licenses");
|
|
6
|
+
const miscUtils_1 = require("./miscUtils");
|
|
7
|
+
function collectLicenseLeaves(expression) {
|
|
8
|
+
switch (expression.kind) {
|
|
9
|
+
case 'license':
|
|
10
|
+
return [expression];
|
|
11
|
+
case 'and':
|
|
12
|
+
case 'or':
|
|
13
|
+
return [...collectLicenseLeaves(expression.left), ...collectLicenseLeaves(expression.right)];
|
|
14
|
+
case 'unknown':
|
|
15
|
+
return [];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function getAboutLibrariesEntries(license) {
|
|
19
|
+
const leavesById = new Map(collectLicenseLeaves(license.license).map((leaf) => [leaf.id, leaf]));
|
|
20
|
+
if (leavesById.size > 0) {
|
|
21
|
+
return [...leavesById.values()].map((leaf) => ({ id: leaf.id, name: (0, licenses_1.renderLicenseExpression)(leaf) }));
|
|
22
|
+
}
|
|
23
|
+
// Unknown License: the Raw License is the best name we have
|
|
24
|
+
if (license.rawLicense) {
|
|
25
|
+
return [{ name: license.rawLicense }];
|
|
26
|
+
}
|
|
27
|
+
// nothing declared, but the package ships a license text, so keep it
|
|
28
|
+
if (license.licenseFiles.length > 0) {
|
|
29
|
+
return [{ name: (0, licenses_1.renderLicenseExpression)(license.license) }];
|
|
30
|
+
}
|
|
31
|
+
return [];
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* One AboutLibraries license per License Identifier (or the Raw License when it is Unknown). Its name keeps
|
|
35
|
+
* a retained `+` and the License Exception; its content is the linked license file, else every unlinked text
|
|
36
|
+
* when the package has one license or exactly one unlinked text.
|
|
37
|
+
*/
|
|
38
|
+
function prepareAboutLibrariesLicenses(license) {
|
|
39
|
+
const entries = getAboutLibrariesEntries(license);
|
|
40
|
+
const unlinkedTexts = license.licenseFiles.filter((file) => !file.licenseId).map((file) => file.content);
|
|
41
|
+
// unlinked texts go to every license when there is only one text (it may cover all of them),
|
|
42
|
+
// or when there is only one license (it owns every text)
|
|
43
|
+
const sharedContent = entries.length === 1 || unlinkedTexts.length === 1 ? unlinkedTexts.join('\n\n') : '';
|
|
44
|
+
return entries.map(({ id, name }) => ({
|
|
45
|
+
name,
|
|
46
|
+
content: (id ? license.licenseFiles.find((file) => file.licenseId === id)?.content : undefined) ?? sharedContent,
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
function prepareAboutLibrariesLicenseField(name, content) {
|
|
50
|
+
// The returned value is used as a filename under `android/config/licenses/`.
|
|
51
|
+
// Legacy/compound SPDX expressions like `MIT/X11` or `(MIT OR Apache-2.0)` would
|
|
52
|
+
// otherwise produce paths containing `/`, `(` or spaces, which causes the writer
|
|
53
|
+
// to either fail with ENOENT (when a `/` is interpreted as a subdirectory) or
|
|
54
|
+
// to produce names that are invalid on some filesystems.
|
|
55
|
+
const sanitizedName = name.replace(/[^A-Za-z0-9._-]/g, '_');
|
|
56
|
+
// hash the unsanitized name when there is no text, so that names differing only in sanitized characters stay distinct
|
|
57
|
+
return `${sanitizedName}_${(0, miscUtils_1.sha512)(content || name)}`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { LicenseFile } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Reads every license file (`LICEN{S,C}E*`, `COPYING*`, case-insensitive) from the package root.
|
|
4
|
+
*
|
|
5
|
+
* A file is linked to a License Identifier by its name suffix (e.g. `LICENSE-APACHE` → `Apache-2.0`),
|
|
6
|
+
* but only when exactly one of `licenseIds` matches; otherwise it is listed without an identifier.
|
|
7
|
+
*/
|
|
8
|
+
export declare function readLicenseFiles(packageDir: string, licenseIds: string[]): LicenseFile[];
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.readLicenseFiles = readLicenseFiles;
|
|
7
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const glob_1 = require("glob");
|
|
10
|
+
// `LICEN{S,C}E*` also matches code and data files such as `license.js` or `licenses.json`
|
|
11
|
+
const NON_LICENSE_EXTENSIONS = new Set(['.js', '.cjs', '.mjs', '.ts', '.cts', '.mts', '.tsx', '.jsx', '.json', '.map']);
|
|
12
|
+
/**
|
|
13
|
+
* Reads every license file (`LICEN{S,C}E*`, `COPYING*`, case-insensitive) from the package root.
|
|
14
|
+
*
|
|
15
|
+
* A file is linked to a License Identifier by its name suffix (e.g. `LICENSE-APACHE` → `Apache-2.0`),
|
|
16
|
+
* but only when exactly one of `licenseIds` matches; otherwise it is listed without an identifier.
|
|
17
|
+
*/
|
|
18
|
+
function readLicenseFiles(packageDir, licenseIds) {
|
|
19
|
+
return glob_1.glob
|
|
20
|
+
.sync('{LICEN{S,C}E*,COPYING*}', { cwd: packageDir, absolute: true, nocase: true, nodir: true })
|
|
21
|
+
.sort()
|
|
22
|
+
.filter((file) => !NON_LICENSE_EXTENSIONS.has(node_path_1.default.extname(file).toLowerCase()))
|
|
23
|
+
.map((file) => {
|
|
24
|
+
const licenseId = linkLicenseId(node_path_1.default.basename(file), licenseIds);
|
|
25
|
+
return {
|
|
26
|
+
file,
|
|
27
|
+
content: node_fs_1.default.readFileSync(file, { encoding: 'utf-8' }),
|
|
28
|
+
...(licenseId && { licenseId }),
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function linkLicenseId(fileName, licenseIds) {
|
|
33
|
+
const suffix = fileName
|
|
34
|
+
.replace(/\.(txt|md)$/i, '')
|
|
35
|
+
.replace(/^(licen[sc]e|copying)[-_. ]*/i, '')
|
|
36
|
+
.toLowerCase();
|
|
37
|
+
if (!suffix) {
|
|
38
|
+
return undefined;
|
|
39
|
+
}
|
|
40
|
+
const matching = licenseIds.filter((id) => {
|
|
41
|
+
const lowerId = id.toLowerCase();
|
|
42
|
+
return lowerId === suffix || lowerId.startsWith(`${suffix}-`);
|
|
43
|
+
});
|
|
44
|
+
return matching.length === 1 ? matching[0] : undefined;
|
|
45
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { License } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* LicensePlist body: every license text under a per-license heading (plain text, as LicensePlist shows
|
|
4
|
+
* bodies verbatim); the rendered License Expression when there is no text.
|
|
5
|
+
*/
|
|
6
|
+
export declare function buildLicensePlistBody(license: License): string;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.buildLicensePlistBody = buildLicensePlistBody;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const licenses_1 = require("../../licenses");
|
|
9
|
+
/**
|
|
10
|
+
* LicensePlist body: every license text under a per-license heading (plain text, as LicensePlist shows
|
|
11
|
+
* bodies verbatim); the rendered License Expression when there is no text.
|
|
12
|
+
*/
|
|
13
|
+
function buildLicensePlistBody(license) {
|
|
14
|
+
if (license.licenseFiles.length === 0) {
|
|
15
|
+
return (0, licenses_1.renderLicenseExpression)(license.license);
|
|
16
|
+
}
|
|
17
|
+
return license.licenseFiles
|
|
18
|
+
.map(({ file, content, licenseId }) => `=== ${licenseId ?? path_1.default.basename(file)} ===\n\n${content.trim()}`)
|
|
19
|
+
.join('\n\n');
|
|
20
|
+
}
|
|
@@ -1,18 +1,27 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
export
|
|
3
|
-
export
|
|
1
|
+
import type { ScanPackageOptionsFactory } from '../../types';
|
|
2
|
+
export { prepareAboutLibrariesLicenseField, prepareAboutLibrariesLicenses } from './aboutLibrariesLicenses';
|
|
3
|
+
export { buildLicensePlistBody } from './licensePlistBody';
|
|
4
|
+
export { readLicenseFiles } from './licenseFiles';
|
|
5
|
+
export declare function getPackageJsonPath(dependency: string, root?: string, projectRoot?: string): string | null;
|
|
6
|
+
export declare function resolvePackageJsonFromEntry(dependency: string, paths?: string[]): string | null;
|
|
4
7
|
export declare function findPackageRoot(entryPath: string): string | undefined;
|
|
5
8
|
export declare function normalizePackageName(packageName: string): string;
|
|
6
|
-
export declare function prepareAboutLibrariesLicenseField(license: License): string;
|
|
7
9
|
export declare function parseAuthorField(json: {
|
|
8
10
|
author: string | {
|
|
9
11
|
name: string;
|
|
10
12
|
};
|
|
11
13
|
}): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Reads the Raw License from package.json: the `license` string, the legacy `license: { type }` object,
|
|
16
|
+
* or the legacy `licenses: [{ type }, …]` array, whose types are joined with ` OR `.
|
|
17
|
+
*/
|
|
12
18
|
export declare function parseLicenseField(json: {
|
|
13
|
-
license
|
|
19
|
+
license?: string | {
|
|
14
20
|
type: string;
|
|
15
21
|
};
|
|
22
|
+
licenses?: Array<{
|
|
23
|
+
type?: string;
|
|
24
|
+
}>;
|
|
16
25
|
}): string | undefined;
|
|
17
26
|
export declare function parseRepositoryFieldToUrl(json: {
|
|
18
27
|
repository: string | {
|