@callstack/licenses 0.2.1
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/LICENSE +21 -0
- package/README.md +74 -0
- package/build/common.d.ts +71 -0
- package/build/common.js +308 -0
- package/build/index.d.ts +3 -0
- package/build/index.js +42 -0
- package/build/types/AboutLibrariesLibraryJsonPayload.d.ts +13 -0
- package/build/types/AboutLibrariesLibraryJsonPayload.js +2 -0
- package/build/types/AboutLibrariesLicenseJsonPayload.d.ts +6 -0
- package/build/types/AboutLibrariesLicenseJsonPayload.js +2 -0
- package/build/types/AboutLibrariesLikePackageInfo.d.ts +7 -0
- package/build/types/AboutLibrariesLikePackageInfo.js +2 -0
- package/build/types/AggregatedLicensesObj.d.ts +6 -0
- package/build/types/AggregatedLicensesObj.js +2 -0
- package/build/types/DependencyType.d.ts +3 -0
- package/build/types/DependencyType.js +2 -0
- package/build/types/LicenseObj.d.ts +24 -0
- package/build/types/LicenseObj.js +2 -0
- package/build/types/LicensePlistPayload.d.ts +7 -0
- package/build/types/LicensePlistPayload.js +2 -0
- package/build/types/ScanPackageCallContext.d.ts +25 -0
- package/build/types/ScanPackageCallContext.js +2 -0
- package/build/types/ScanPackageOptions.d.ts +24 -0
- package/build/types/ScanPackageOptions.js +2 -0
- package/build/types/index.d.ts +9 -0
- package/build/types/index.js +25 -0
- package/build/utils/index.d.ts +4 -0
- package/build/utils/index.js +40 -0
- package/build/utils/miscUtils.d.ts +6 -0
- package/build/utils/miscUtils.js +39 -0
- package/build/utils/packageUtils.d.ts +26 -0
- package/build/utils/packageUtils.js +100 -0
- package/build/utils/repositoryUtils.d.ts +1 -0
- package/build/utils/repositoryUtils.js +18 -0
- package/build/utils/yamlUtils.d.ts +4 -0
- package/build/utils/yamlUtils.js +64 -0
- package/package.json +67 -0
- package/src/common.ts +384 -0
- package/src/index.ts +3 -0
- package/src/types/AboutLibrariesLibraryJsonPayload.ts +10 -0
- package/src/types/AboutLibrariesLicenseJsonPayload.ts +6 -0
- package/src/types/AboutLibrariesLikePackageInfo.ts +8 -0
- package/src/types/AggregatedLicensesObj.ts +7 -0
- package/src/types/DependencyType.ts +3 -0
- package/src/types/LicenseObj.ts +34 -0
- package/src/types/LicensePlistPayload.ts +7 -0
- package/src/types/ScanPackageCallContext.ts +30 -0
- package/src/types/ScanPackageOptions.ts +29 -0
- package/src/types/index.ts +9 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/miscUtils.ts +42 -0
- package/src/utils/packageUtils.ts +103 -0
- package/src/utils/repositoryUtils.ts +17 -0
- package/src/utils/yamlUtils.ts +73 -0
package/src/common.ts
ADDED
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
import glob from 'glob';
|
|
5
|
+
|
|
6
|
+
import type {
|
|
7
|
+
AboutLibrariesLibraryJsonPayload,
|
|
8
|
+
AboutLibrariesLicenseJsonPayload,
|
|
9
|
+
AboutLibrariesLikePackageInfo,
|
|
10
|
+
AggregatedLicensesObj,
|
|
11
|
+
DependencyType,
|
|
12
|
+
LicensePlistPayload,
|
|
13
|
+
ScanPackageCallContext,
|
|
14
|
+
ScanPackageOptionsFactory,
|
|
15
|
+
} from './types';
|
|
16
|
+
import { PackageUtils, YamlUtils } from './utils';
|
|
17
|
+
|
|
18
|
+
type InternalScanGroupSpecifier = { packages: [depName: string, depVersion: string][]; dependencyType: DependencyType };
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Scans a single package and its dependencies for license information
|
|
22
|
+
*
|
|
23
|
+
* @param packageName Name of the package to scan
|
|
24
|
+
* @param requiredVersion Version of the package to scan; this is the version specifier from package.json, e.g. `^1.0.0`, `~2.3.4`, etc.
|
|
25
|
+
* @param processedPackages Set of already processed packages (avoids cycles)
|
|
26
|
+
* @param result Aggregated licenses object to store the results; the keys will be in the format of `packageName@version` where version is the resolved version of the package
|
|
27
|
+
* @param scanOptionsFactory Factory function to create scan options for dependencies; defaults to {@link PackageUtils.legacyDefaultScanPackageOptionsFactory}
|
|
28
|
+
* @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`
|
|
29
|
+
* @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`
|
|
30
|
+
*/
|
|
31
|
+
function scanPackage(
|
|
32
|
+
packageName: string,
|
|
33
|
+
requiredVersion: string,
|
|
34
|
+
processedPackages: Set<string>,
|
|
35
|
+
result: AggregatedLicensesObj,
|
|
36
|
+
scanOptionsFactory: ScanPackageOptionsFactory = PackageUtils.legacyDefaultScanPackageOptionsFactory,
|
|
37
|
+
{
|
|
38
|
+
parentPackageRoot,
|
|
39
|
+
parentPackageName,
|
|
40
|
+
dependencyType,
|
|
41
|
+
parentPackageRequiredVersion,
|
|
42
|
+
parentPackageResolvedVersion,
|
|
43
|
+
}: ScanPackageCallContext,
|
|
44
|
+
) {
|
|
45
|
+
const requiredVersionPackageKey = `${packageName}@${requiredVersion}`;
|
|
46
|
+
|
|
47
|
+
// Skip if already processed to avoid circular dependencies
|
|
48
|
+
if (processedPackages.has(requiredVersionPackageKey)) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// If the package is a file: dependency, warn about lack of support
|
|
53
|
+
if (requiredVersion.startsWith('file:')) {
|
|
54
|
+
console.warn(
|
|
55
|
+
`[react-native-legal] ${packageName} (${requiredVersion}) is 'file:' dependency. Such packages are not supported yet (see https://callstackincubator.github.io/react-native-legal/docs/programmatic-usage.html#known-limitations).`,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
processedPackages.add(requiredVersionPackageKey);
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const localPackageJsonPath = PackageUtils.getPackageJsonPath(packageName, parentPackageRoot);
|
|
63
|
+
|
|
64
|
+
if (!localPackageJsonPath) {
|
|
65
|
+
// do not warn if the package is an optional dependency, it's normal it may not be installed
|
|
66
|
+
if (!dependencyType.toLowerCase().includes('optional')) {
|
|
67
|
+
console.warn(`[react-native-legal] skipping ${requiredVersionPackageKey} could not find package.json`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const localPackageJson = require(path.resolve(localPackageJsonPath));
|
|
74
|
+
|
|
75
|
+
if (localPackageJson.private !== true) {
|
|
76
|
+
const licenseFiles = glob.sync('LICEN{S,C}E{.md,}', {
|
|
77
|
+
cwd: path.dirname(localPackageJsonPath),
|
|
78
|
+
absolute: true,
|
|
79
|
+
nocase: true,
|
|
80
|
+
nodir: true,
|
|
81
|
+
ignore: '**/{__tests__,__fixtures__,__mocks__}/**',
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
const resolvedVersionPackageKey = `${packageName}@${localPackageJson.version}`;
|
|
85
|
+
|
|
86
|
+
result[resolvedVersionPackageKey] = {
|
|
87
|
+
name: packageName,
|
|
88
|
+
author: PackageUtils.parseAuthorField(localPackageJson),
|
|
89
|
+
content: licenseFiles?.[0] ? fs.readFileSync(licenseFiles[0], { encoding: 'utf-8' }) : undefined,
|
|
90
|
+
file: licenseFiles?.[0] ? licenseFiles[0] : undefined,
|
|
91
|
+
description: localPackageJson.description,
|
|
92
|
+
type: PackageUtils.parseLicenseField(localPackageJson),
|
|
93
|
+
url: PackageUtils.parseRepositoryFieldToUrl(localPackageJson),
|
|
94
|
+
version: localPackageJson.version,
|
|
95
|
+
requiredVersion,
|
|
96
|
+
parentPackageName,
|
|
97
|
+
parentPackageRequiredVersion,
|
|
98
|
+
parentPackageResolvedVersion,
|
|
99
|
+
dependencyType,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const dependencies: MaybeDependencyMapping = localPackageJson.dependencies;
|
|
104
|
+
const devDependencies: MaybeDependencyMapping = localPackageJson.devDependencies;
|
|
105
|
+
const optionalDependencies: MaybeDependencyMapping = localPackageJson.optionalDependencies;
|
|
106
|
+
const isWorkspacePackage = requiredVersion.startsWith('workspace:');
|
|
107
|
+
|
|
108
|
+
const scanOptions = scanOptionsFactory({
|
|
109
|
+
isRoot: false,
|
|
110
|
+
isWorkspacePackage,
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// check if transitive dependencies should be scanned
|
|
114
|
+
if (!scanOptions.includeTransitiveDependencies) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// helper used for finding nested dependencies installed with different versions for a given package, see docstring of scanPackage
|
|
119
|
+
const currentPackageRoot = path.dirname(localPackageJsonPath);
|
|
120
|
+
|
|
121
|
+
for (const { dependencyType, packages } of [
|
|
122
|
+
{
|
|
123
|
+
dependencyType: 'transitiveDependency',
|
|
124
|
+
packages: dependencies ? Object.entries(dependencies) : [],
|
|
125
|
+
},
|
|
126
|
+
{
|
|
127
|
+
dependencyType: 'transitiveDevDependency',
|
|
128
|
+
packages: devDependencies && scanOptions.includeDevDependencies ? Object.entries(devDependencies) : [],
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
dependencyType: 'transitiveOptionalDependency',
|
|
132
|
+
packages:
|
|
133
|
+
optionalDependencies && scanOptions.includeOptionalDependencies ? Object.entries(optionalDependencies) : [],
|
|
134
|
+
},
|
|
135
|
+
] as InternalScanGroupSpecifier[]) {
|
|
136
|
+
for (const [depName, depVersion] of packages) {
|
|
137
|
+
scanPackage(depName, depVersion as string, processedPackages, result, scanOptionsFactory, {
|
|
138
|
+
dependencyType,
|
|
139
|
+
parentPackageRoot: currentPackageRoot,
|
|
140
|
+
parentPackageName: packageName,
|
|
141
|
+
parentPackageRequiredVersion: requiredVersion,
|
|
142
|
+
parentPackageResolvedVersion: localPackageJson.version,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.warn(`[react-native-legal] could not process package.json for ${packageName}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
type DependencyMapping = Record<string, string>;
|
|
152
|
+
type MaybeDependencyMapping = DependencyMapping | undefined;
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Scans `package.json` and searches for all packages under `dependencies` field. Supports monorepo projects.
|
|
156
|
+
*
|
|
157
|
+
* @param appPackageJsonPath Path to the `package.json` file of the application
|
|
158
|
+
* @param scanOptionsFactory Factory function to create scan options for dependencies; defaults to {@link PackageUtils.legacyDefaultScanPackageOptionsFactory}
|
|
159
|
+
* @returns Aggregated licenses object containing all scanned dependencies and their license information
|
|
160
|
+
*/
|
|
161
|
+
export function scanDependencies(
|
|
162
|
+
appPackageJsonPath: string,
|
|
163
|
+
scanOptionsFactory: ScanPackageOptionsFactory = PackageUtils.legacyDefaultScanPackageOptionsFactory,
|
|
164
|
+
): AggregatedLicensesObj {
|
|
165
|
+
const appPackageJson = require(path.resolve(appPackageJsonPath));
|
|
166
|
+
const dependencies: MaybeDependencyMapping = appPackageJson.dependencies;
|
|
167
|
+
const devDependencies: MaybeDependencyMapping = appPackageJson.devDependencies;
|
|
168
|
+
const optionalDependencies: MaybeDependencyMapping = appPackageJson.optionalDependencies;
|
|
169
|
+
const result: AggregatedLicensesObj = {};
|
|
170
|
+
const processedPackages = new Set<string>();
|
|
171
|
+
|
|
172
|
+
const rootScanOptions = scanOptionsFactory({ isRoot: true, isWorkspacePackage: false });
|
|
173
|
+
|
|
174
|
+
for (const { dependencyType, packages } of [
|
|
175
|
+
{
|
|
176
|
+
dependencyType: 'dependency',
|
|
177
|
+
packages: dependencies ? Object.entries(dependencies) : [],
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
dependencyType: 'devDependency',
|
|
181
|
+
packages: devDependencies && rootScanOptions.includeDevDependencies ? Object.entries(devDependencies) : [],
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
dependencyType: 'optionalDependency',
|
|
185
|
+
packages:
|
|
186
|
+
optionalDependencies && rootScanOptions.includeOptionalDependencies ? Object.entries(optionalDependencies) : [],
|
|
187
|
+
},
|
|
188
|
+
] as InternalScanGroupSpecifier[]) {
|
|
189
|
+
for (const [depName, depVersion] of packages) {
|
|
190
|
+
scanPackage(depName, depVersion as string, processedPackages, result, scanOptionsFactory, {
|
|
191
|
+
dependencyType,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Generates LicensePlist-compatible metadata for NPM dependencies as a YAML string.
|
|
201
|
+
*
|
|
202
|
+
* To write a file directly, use `writeLicensePlistNPMOutput` function.
|
|
203
|
+
*
|
|
204
|
+
* @param licenses Scanned NPM licenses
|
|
205
|
+
* @param iosProjectPath Path to the iOS project directory
|
|
206
|
+
* @see {@link writeLicensePlistNPMOutput}
|
|
207
|
+
*/
|
|
208
|
+
export function generateLicensePlistNPMOutput(licenses: AggregatedLicensesObj, iosProjectPath: string): string {
|
|
209
|
+
const renames: Record<string, string> = {};
|
|
210
|
+
const licenseEntries = Object.entries(licenses).map(([packageKey, licenseObj]) => {
|
|
211
|
+
const normalizedPackageNameWithVersion = PackageUtils.normalizePackageName(packageKey);
|
|
212
|
+
|
|
213
|
+
if (licenseObj.name !== normalizedPackageNameWithVersion) {
|
|
214
|
+
renames[normalizedPackageNameWithVersion] = licenseObj.name;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const relativeLicenseFile = licenseObj.file ? path.relative(iosProjectPath, licenseObj.file) : undefined;
|
|
218
|
+
|
|
219
|
+
return {
|
|
220
|
+
name: normalizedPackageNameWithVersion,
|
|
221
|
+
version: licenseObj.version,
|
|
222
|
+
...(licenseObj.url && { source: licenseObj.url }),
|
|
223
|
+
...(licenseObj.file
|
|
224
|
+
? { file: relativeLicenseFile }
|
|
225
|
+
: { body: licenseObj.content ?? licenseObj.type ?? 'UNKNOWN' }),
|
|
226
|
+
} as LicensePlistPayload;
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const yamlDoc = {
|
|
230
|
+
...(Object.keys(renames).length > 0 && { rename: renames }),
|
|
231
|
+
manual: licenseEntries,
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const yamlContent = [
|
|
235
|
+
'# BEGIN Generated NPM license entries',
|
|
236
|
+
YamlUtils.toYaml(yamlDoc),
|
|
237
|
+
'# END Generated NPM license entries',
|
|
238
|
+
].join('\n');
|
|
239
|
+
|
|
240
|
+
return yamlContent;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Writes LicensePlist-compatible metadata for NPM dependencies to a file
|
|
245
|
+
*
|
|
246
|
+
* This will take scanned NPM licenses and produce following output inside iOS project's directory:
|
|
247
|
+
*
|
|
248
|
+
* ```
|
|
249
|
+
* | - ios
|
|
250
|
+
* | ---- myawesomeapp
|
|
251
|
+
* | ---- myawesomeapp.xcodeproj
|
|
252
|
+
* | ---- myawesomeapp.xcodeworkspace
|
|
253
|
+
* | ---- license_plist.yml <--- generated LicensePlist config with NPM dependencies
|
|
254
|
+
* | ---- Podfile
|
|
255
|
+
* | ---- Podfile.lock
|
|
256
|
+
* ```
|
|
257
|
+
*
|
|
258
|
+
* @param licenses Scanned NPM licenses
|
|
259
|
+
* @param iosProjectPath Path to the iOS project directory
|
|
260
|
+
* @param plistLikeOutput Optional pre-generated string output to use instead of generating it using `generateLicensePlistNPMOutput`
|
|
261
|
+
* @see {@link generateLicensePlistNPMOutput}
|
|
262
|
+
*/
|
|
263
|
+
export function writeLicensePlistNPMOutput(
|
|
264
|
+
licenses: AggregatedLicensesObj,
|
|
265
|
+
iosProjectPath: string,
|
|
266
|
+
plistLikeOutput?: string,
|
|
267
|
+
) {
|
|
268
|
+
if (!plistLikeOutput) {
|
|
269
|
+
plistLikeOutput = generateLicensePlistNPMOutput(licenses, iosProjectPath);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
fs.writeFileSync(path.join(iosProjectPath, 'license_plist.yml'), plistLikeOutput, { encoding: 'utf-8' });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Generates AboutLibraries-compatible metadata for NPM dependencies
|
|
277
|
+
*
|
|
278
|
+
* This will take scanned NPM licenses and produce output that can be modified and/or written to the Android project files.
|
|
279
|
+
*
|
|
280
|
+
* @param licenses Scanned NPM licenses
|
|
281
|
+
* @returns Array of AboutLibrariesLikePackage objects, each representing a NPM dependency
|
|
282
|
+
* @see {@link writeAboutLibrariesNPMOutput}
|
|
283
|
+
*/
|
|
284
|
+
export function generateAboutLibrariesNPMOutput(licenses: AggregatedLicensesObj): AboutLibrariesLikePackageInfo[] {
|
|
285
|
+
return Object.entries(licenses)
|
|
286
|
+
.map(([packageKey, licenseObj]) => {
|
|
287
|
+
return {
|
|
288
|
+
artifactVersion: licenseObj.version,
|
|
289
|
+
content: licenseObj.content ?? '',
|
|
290
|
+
description: licenseObj.description ?? '',
|
|
291
|
+
developers: [{ name: licenseObj.author ?? '', organisationUrl: '' }],
|
|
292
|
+
licenses: [PackageUtils.prepareAboutLibrariesLicenseField(licenseObj)],
|
|
293
|
+
name: licenseObj.name,
|
|
294
|
+
tag: '',
|
|
295
|
+
type: licenseObj.type,
|
|
296
|
+
uniqueId: PackageUtils.normalizePackageName(packageKey),
|
|
297
|
+
website: licenseObj.url,
|
|
298
|
+
};
|
|
299
|
+
})
|
|
300
|
+
.map((jsonPayload) => {
|
|
301
|
+
const libraryJsonPayload: AboutLibrariesLibraryJsonPayload = {
|
|
302
|
+
artifactVersion: jsonPayload.artifactVersion,
|
|
303
|
+
description: jsonPayload.description,
|
|
304
|
+
developers: jsonPayload.developers,
|
|
305
|
+
licenses: jsonPayload.licenses,
|
|
306
|
+
name: jsonPayload.name,
|
|
307
|
+
tag: jsonPayload.tag,
|
|
308
|
+
uniqueId: jsonPayload.uniqueId,
|
|
309
|
+
website: jsonPayload.website,
|
|
310
|
+
};
|
|
311
|
+
const licenseJsonPayload: AboutLibrariesLicenseJsonPayload = {
|
|
312
|
+
content: jsonPayload.content,
|
|
313
|
+
hash: jsonPayload.licenses[0],
|
|
314
|
+
name: jsonPayload.type ?? '',
|
|
315
|
+
url: '',
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
return {
|
|
319
|
+
normalizedPackageNameWithVersion: jsonPayload.uniqueId,
|
|
320
|
+
libraryJsonPayload,
|
|
321
|
+
licenseJsonPayload,
|
|
322
|
+
};
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Generates AboutLibraries-compatible metadata for NPM dependencies
|
|
328
|
+
*
|
|
329
|
+
* This will take scanned NPM licenses and produce following output inside android project's directory:
|
|
330
|
+
*
|
|
331
|
+
* ```
|
|
332
|
+
* | - android
|
|
333
|
+
* | ---- app
|
|
334
|
+
* | ---- config <--- generated AboutLibraries config directory
|
|
335
|
+
* | ------- libraries <--- generated directory with JSON files list of NPM dependencies
|
|
336
|
+
* | ------- licenses <--- generated directory with JSON files list of used licenses
|
|
337
|
+
* | ---- build.gradle
|
|
338
|
+
* | ---- settings.gradle
|
|
339
|
+
* ```
|
|
340
|
+
*
|
|
341
|
+
* @param licenses Scanned NPM licenses
|
|
342
|
+
* @param androidProjectPath Path to the Android project directory
|
|
343
|
+
* @param aboutLibrariesLikeOutput Optional pre-generated output to use instead of generating it using `generateAboutLibrariesNPMOutput`
|
|
344
|
+
* @see {@link generateAboutLibrariesNPMOutput}
|
|
345
|
+
*/
|
|
346
|
+
export function writeAboutLibrariesNPMOutput(
|
|
347
|
+
licenses: AggregatedLicensesObj,
|
|
348
|
+
androidProjectPath: string,
|
|
349
|
+
aboutLibrariesLikeOutput?: AboutLibrariesLikePackageInfo[],
|
|
350
|
+
) {
|
|
351
|
+
const aboutLibrariesConfigDirPath = path.join(androidProjectPath, 'config');
|
|
352
|
+
const aboutLibrariesConfigLibrariesDirPath = path.join(aboutLibrariesConfigDirPath, 'libraries');
|
|
353
|
+
const aboutLibrariesConfigLicensesDirPath = path.join(aboutLibrariesConfigDirPath, 'licenses');
|
|
354
|
+
|
|
355
|
+
if (!fs.existsSync(aboutLibrariesConfigDirPath)) {
|
|
356
|
+
fs.mkdirSync(aboutLibrariesConfigDirPath);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
if (!fs.existsSync(aboutLibrariesConfigLibrariesDirPath)) {
|
|
360
|
+
fs.mkdirSync(aboutLibrariesConfigLibrariesDirPath);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!fs.existsSync(aboutLibrariesConfigLicensesDirPath)) {
|
|
364
|
+
fs.mkdirSync(aboutLibrariesConfigLicensesDirPath);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (!aboutLibrariesLikeOutput) {
|
|
368
|
+
aboutLibrariesLikeOutput = generateAboutLibrariesNPMOutput(licenses);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
aboutLibrariesLikeOutput.forEach(({ normalizedPackageNameWithVersion, libraryJsonPayload, licenseJsonPayload }) => {
|
|
372
|
+
const libraryJsonFilePath = path.join(
|
|
373
|
+
aboutLibrariesConfigLibrariesDirPath,
|
|
374
|
+
`${normalizedPackageNameWithVersion}.json`,
|
|
375
|
+
);
|
|
376
|
+
const licenseJsonFilePath = path.join(aboutLibrariesConfigLicensesDirPath, `${licenseJsonPayload.hash}.json`);
|
|
377
|
+
|
|
378
|
+
fs.writeFileSync(libraryJsonFilePath, JSON.stringify(libraryJsonPayload));
|
|
379
|
+
|
|
380
|
+
if (!fs.existsSync(licenseJsonFilePath)) {
|
|
381
|
+
fs.writeFileSync(licenseJsonFilePath, JSON.stringify(licenseJsonPayload));
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type AboutLibrariesLibraryJsonPayload = {
|
|
2
|
+
artifactVersion: string;
|
|
3
|
+
description: string;
|
|
4
|
+
developers: { name: string; organisationUrl: string }[];
|
|
5
|
+
licenses: string[];
|
|
6
|
+
name: string;
|
|
7
|
+
tag: string;
|
|
8
|
+
uniqueId: string;
|
|
9
|
+
website: string | undefined;
|
|
10
|
+
};
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AboutLibrariesLibraryJsonPayload } from './AboutLibrariesLibraryJsonPayload';
|
|
2
|
+
import type { AboutLibrariesLicenseJsonPayload } from './AboutLibrariesLicenseJsonPayload';
|
|
3
|
+
|
|
4
|
+
export type AboutLibrariesLikePackageInfo = {
|
|
5
|
+
normalizedPackageNameWithVersion: string;
|
|
6
|
+
libraryJsonPayload: AboutLibrariesLibraryJsonPayload;
|
|
7
|
+
licenseJsonPayload: AboutLibrariesLicenseJsonPayload;
|
|
8
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { DependencyType } from './DependencyType';
|
|
2
|
+
import type { ScanPackageCallContext } from './ScanPackageCallContext';
|
|
3
|
+
|
|
4
|
+
export type LicenseObj = {
|
|
5
|
+
/** Package name */
|
|
6
|
+
name: string;
|
|
7
|
+
|
|
8
|
+
/** Package author */
|
|
9
|
+
author?: string;
|
|
10
|
+
|
|
11
|
+
/** Package license contents */
|
|
12
|
+
content?: string;
|
|
13
|
+
|
|
14
|
+
/** Package description */
|
|
15
|
+
description?: string;
|
|
16
|
+
|
|
17
|
+
/** License file path */
|
|
18
|
+
file?: string;
|
|
19
|
+
|
|
20
|
+
/** License type */
|
|
21
|
+
type?: string;
|
|
22
|
+
|
|
23
|
+
/** Package repository URL */
|
|
24
|
+
url?: string;
|
|
25
|
+
|
|
26
|
+
/** The resolved version that was actually installed */
|
|
27
|
+
version: string;
|
|
28
|
+
|
|
29
|
+
/** The source of how this package has been introduced to the dependency tree */
|
|
30
|
+
dependencyType: DependencyType;
|
|
31
|
+
|
|
32
|
+
/** The required version specified in package.json */
|
|
33
|
+
requiredVersion: string;
|
|
34
|
+
} & Pick<ScanPackageCallContext, 'parentPackageName' | 'parentPackageRequiredVersion' | 'parentPackageResolvedVersion'>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { DependencyType } from './DependencyType';
|
|
2
|
+
|
|
3
|
+
export type ScanPackageCallContext = {
|
|
4
|
+
/** Information on what is the source of the dependency on the package to be scanned */
|
|
5
|
+
dependencyType: DependencyType;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The name of the package that introduced the package to be scanned as a dependency (of any type);
|
|
9
|
+
* undefined if the package to be scanned is a dependency of the root package
|
|
10
|
+
*/
|
|
11
|
+
parentPackageName?: string;
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* The installation directory of the package that introduced the package to be scanned as a dependency (of any type);
|
|
15
|
+
* undefined if the package to be scanned is a dependency of the root package
|
|
16
|
+
*/
|
|
17
|
+
parentPackageRoot?: string;
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The required version, as specified in package.json
|
|
21
|
+
* of the package that introduced the package to be scanned as a dependency (of any type)
|
|
22
|
+
*/
|
|
23
|
+
parentPackageRequiredVersion?: string;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* The resolved (actually installed by the package manager) version
|
|
27
|
+
* of the package that introduced the package to be scanned as a dependency (of any type)
|
|
28
|
+
*/
|
|
29
|
+
parentPackageResolvedVersion?: string;
|
|
30
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Scan options for controlling which dependencies of a package are scanned
|
|
3
|
+
*/
|
|
4
|
+
export type ScanPackageOptions = {
|
|
5
|
+
/** Whether transitive dependencies should be scanned */
|
|
6
|
+
includeTransitiveDependencies: boolean;
|
|
7
|
+
|
|
8
|
+
/** Whether to include devDependencies in the scan; includeTransitiveDependencies option applies */
|
|
9
|
+
includeDevDependencies: boolean;
|
|
10
|
+
|
|
11
|
+
/** Whether to include optionalDependencies in the scan; includeTransitiveDependencies option applies */
|
|
12
|
+
includeOptionalDependencies: boolean;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Information about a single package to be scanned
|
|
17
|
+
*/
|
|
18
|
+
export type ScanPackageOptionsFactoryPackageInfo = {
|
|
19
|
+
/** `true` if the analyzed package.json is the root of the scanned project */
|
|
20
|
+
isRoot: boolean;
|
|
21
|
+
|
|
22
|
+
/** `true` if the analyzed package.json is a dependency related to the project via a `workspace:...` dependency specifier */
|
|
23
|
+
isWorkspacePackage: boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Factory to create a filter for scan options for dependencies of a given package
|
|
28
|
+
*/
|
|
29
|
+
export type ScanPackageOptionsFactory = (packageInfo: ScanPackageOptionsFactoryPackageInfo) => ScanPackageOptions;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export * from './LicenseObj';
|
|
2
|
+
export * from './AggregatedLicensesObj';
|
|
3
|
+
export * from './AboutLibrariesLibraryJsonPayload';
|
|
4
|
+
export * from './AboutLibrariesLicenseJsonPayload';
|
|
5
|
+
export * from './LicensePlistPayload';
|
|
6
|
+
export * from './AboutLibrariesLikePackageInfo';
|
|
7
|
+
export * from './ScanPackageOptions';
|
|
8
|
+
export * from './ScanPackageCallContext';
|
|
9
|
+
export * from './DependencyType';
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import crypto from 'crypto';
|
|
2
|
+
|
|
3
|
+
export function sha512(text: string) {
|
|
4
|
+
return crypto.createHash('sha512').update(text).digest('hex');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function compareObjects(a: unknown, b: unknown): boolean {
|
|
8
|
+
if (a == null || b == null || typeof a !== 'object' || typeof b !== 'object') {
|
|
9
|
+
return a === b;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const entriesA = Object.entries(a);
|
|
13
|
+
const entriesB = Object.entries(b);
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
entriesA.length === entriesB.length &&
|
|
17
|
+
entriesA
|
|
18
|
+
.map(([keyA, valueA]) => {
|
|
19
|
+
const entry = entriesB.find(([keyB]) => keyA === keyB);
|
|
20
|
+
|
|
21
|
+
if (!entry) {
|
|
22
|
+
return valueA === entry;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const [, valueB] = entry;
|
|
26
|
+
|
|
27
|
+
return compareObjects(valueA, valueB);
|
|
28
|
+
})
|
|
29
|
+
.reduce((acc, curr) => acc && curr, true)
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Makes a deep-check between array items and provided object, returns true if array has provided object.
|
|
35
|
+
*/
|
|
36
|
+
export function arrayIncludesObject(array?: unknown[], object?: unknown) {
|
|
37
|
+
if (!Array.isArray(array)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return array.map((item) => compareObjects(item, object)).reduce((acc, curr) => acc || curr, false);
|
|
42
|
+
}
|