@kingironman2011/license-checker 1.0.2 → 1.1.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.
Files changed (58) hide show
  1. package/README.md +0 -1
  2. package/dist/cli/options.d.ts +60 -4
  3. package/dist/cli/options.js +234 -44
  4. package/dist/cli/preflight.d.ts +8 -0
  5. package/dist/cli/preflight.js +32 -0
  6. package/dist/cli/usage.d.ts +1 -0
  7. package/dist/cli/usage.js +39 -0
  8. package/dist/cli.js +17 -33
  9. package/dist/dependencies/direct-dependencies.d.ts +10 -0
  10. package/dist/dependencies/direct-dependencies.js +28 -0
  11. package/dist/dependencies/read-installed-packages.d.ts +9 -2
  12. package/dist/dependencies/read-installed-packages.js +174 -52
  13. package/dist/dependencies/walk-dependency-tree.d.ts +19 -0
  14. package/dist/dependencies/walk-dependency-tree.js +38 -0
  15. package/dist/files/read-json.d.ts +1 -0
  16. package/dist/files/read-json.js +8 -0
  17. package/dist/index.d.ts +88 -3
  18. package/dist/index.js +272 -24
  19. package/dist/licenses/clarifications.d.ts +3 -0
  20. package/dist/licenses/clarifications.js +33 -0
  21. package/dist/licenses/collect-license-results.d.ts +26 -2
  22. package/dist/licenses/collect-license-results.js +321 -12
  23. package/dist/licenses/copyright.d.ts +1 -0
  24. package/dist/licenses/copyright.js +13 -0
  25. package/dist/licenses/detect-license-title.d.ts +1 -0
  26. package/dist/licenses/detect-license-title.js +145 -0
  27. package/dist/licenses/find-license-files.d.ts +1 -0
  28. package/dist/licenses/find-license-files.js +26 -0
  29. package/dist/licenses/package-metadata.d.ts +20 -0
  30. package/dist/licenses/package-metadata.js +41 -0
  31. package/dist/licenses/spdx-data.d.ts +4 -0
  32. package/dist/licenses/spdx-data.js +922 -0
  33. package/dist/licenses/spdx.d.ts +17 -0
  34. package/dist/licenses/spdx.js +394 -0
  35. package/dist/output/ansi.d.ts +4 -0
  36. package/dist/output/ansi.js +94 -0
  37. package/dist/output/csv.d.ts +4 -0
  38. package/dist/output/csv.js +50 -0
  39. package/dist/output/filter-attributes.d.ts +2 -0
  40. package/dist/output/filter-attributes.js +10 -0
  41. package/dist/output/format-output.d.ts +4 -0
  42. package/dist/output/format-output.js +81 -0
  43. package/dist/output/renderers.d.ts +15 -0
  44. package/dist/output/renderers.js +112 -0
  45. package/dist/output/write-output.d.ts +3 -2
  46. package/dist/output/write-output.js +18 -10
  47. package/dist/policies/license-policy.d.ts +20 -0
  48. package/dist/policies/license-policy.js +112 -0
  49. package/dist/policies/package-filters.d.ts +6 -0
  50. package/dist/policies/package-filters.js +56 -0
  51. package/dist/shared/debug.d.ts +1 -0
  52. package/dist/shared/debug.js +25 -0
  53. package/dist/shared/first-defined.d.ts +1 -0
  54. package/dist/shared/first-defined.js +7 -0
  55. package/dist/shared/semver.d.ts +1 -0
  56. package/dist/shared/semver.js +269 -0
  57. package/dist/types.d.ts +132 -20
  58. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,28 +1,276 @@
1
1
  import path from "node:path";
2
- import { readInstalledPackages } from "./dependencies/read-installed-packages.js";
2
+ import { deleteNonDirectDependencies } from "./dependencies/direct-dependencies.js";
3
+ import readInstalledPackages from "./dependencies/read-installed-packages.js";
4
+ import { readJson } from "./files/read-json.js";
5
+ import { assertAllClarificationsWereUsed, readClarifications, } from "./licenses/clarifications.js";
3
6
  import { collectLicenseResults } from "./licenses/collect-license-results.js";
4
- import { writeIndividualLicenseFilesToDir } from "./output/write-output.js";
5
- export async function runLicenseCheck(options = {}) {
6
- const projectDir = path.resolve(options.start ?? process.cwd());
7
- const scopes = options.production
8
- ? ["production"]
9
- : options.development
10
- ? ["development"]
11
- : ["production", "development"];
12
- const results = new Map();
13
- for (const scope of scopes) {
14
- const packages = await collectLicenseResults(await readInstalledPackages(projectDir, scope));
15
- for (const item of packages) {
16
- const key = `${item.name}@${item.version}`;
17
- const existing = results.get(key);
18
- if (existing)
19
- existing.scope = "both";
20
- else
21
- results.set(key, item);
7
+ import { boldRed, supportsBasicColor } from "./output/ansi.js";
8
+ import { getFormattedOutput } from "./output/format-output.js";
9
+ import { writeIndividualLicenseFilesToDir, writeOutputToFile, } from "./output/write-output.js";
10
+ import { getLicenseMatch, getLicensePolicy, throwIfLicensePolicyFails, } from "./policies/license-policy.js";
11
+ import { excludePackages, excludePackagesStartingWith, excludePrivatePackages, getOptionArray, includePackages, } from "./policies/package-filters.js";
12
+ import { createDebugLogger } from "./shared/debug.js";
13
+ const LICENSE_TITLE_UNKNOWN = "UNKNOWN";
14
+ const LICENSE_TITLE_UNLICENSED = "UNLICENSED";
15
+ const debugError = createDebugLogger("license-checker-rseidelsohn:error");
16
+ const debugLog = createDebugLogger("license-checker-rseidelsohn:log", console.log.bind(console));
17
+ /**
18
+ * Custom output field default value or toggle.
19
+ *
20
+ * For CSV output, custom format keys define the output columns after module_name.
21
+ * For JSON output, custom format keys add or populate fields in addition to the usual result fields.
22
+ * A value of false disables optional population for that field where supported, but does not remove core fields such as
23
+ * licenses.
24
+ *
25
+ * Use limitAttributes to restrict formatted JSON output to a specific set of fields.
26
+ *
27
+ * @typedef {(string | boolean | undefined)} CustomFormatValue
28
+ */
29
+ /**
30
+ * @typedef {Record<string, CustomFormatValue>} CustomFormat
31
+ */
32
+ /**
33
+ * @typedef {Object} LicenseCheckOptions Options struct for the init() and runLicenseCheck() functions.
34
+ * @property {string} start Path to start checking dependencies from.
35
+ * @property {boolean=} production Only show production dependencies.
36
+ * @property {boolean=} development Only show development dependencies.
37
+ * @property {boolean=} unknown Report guessed licenses as unknown licenses.
38
+ * @property {boolean=} onlyunknown Only list packages with unknown or guessed licenses.
39
+ * @property {boolean=} json Output in JSON format.
40
+ * @property {boolean=} csv Output in CSV format.
41
+ * @property {string=} csvComponentPrefix Prefix column for component in CSV format.
42
+ * @property {string=} out Write the data to a specific file.
43
+ * @property {string=} customPath Path to a JSON file that defines a custom output format.
44
+ * @property {string=} excludeLicenses Exclude modules which licenses are in the comma-separated list from the output.
45
+ * @property {boolean=} relativeLicensePath Output the location of the license files as relative paths.
46
+ * @property {boolean=} relativeModulePath Output module locations as relative paths.
47
+ * @property {boolean=} summary Output a summary of the license usage.
48
+ * @property {string=} failOn Fail (exit with code 1) on the first occurrence of the licenses of the semicolon-separated list.
49
+ * @property {string=} onlyAllow Fail (exit with code 1) on the first occurrence of the licenses not in the semicolon-separated list.
50
+ * @property {string=} includePackages Restrict output to the packages (package@version) in the semicolon-separated list.
51
+ * @property {string=} excludePackages Restrict output to the packages (package@version) not in the semicolon-separated list.
52
+ * @property {boolean=} excludePrivatePackages Restrict output to not include any package marked as private.
53
+ * @property {string=} excludePackagesStartingWith Exclude modules starting with a specific string.
54
+ * @property {(boolean | number)=} direct Look for direct dependencies only.
55
+ * @property {number=} depth Recurse dependencies up to a specific depth, overrides "direct".
56
+ * @property {boolean=} color Colorize output.
57
+ * @property {CustomFormat=} customFormat Custom output fields and default values.
58
+ * @property {boolean=} nopeer Ignore peerDependencies.
59
+ * @property {string=} clarificationsFile A file that contains license clarifications for malformed or non-standard packages.
60
+ * @property {boolean=} clarificationsMatchAll Require every clarification to match a package.
61
+ * @property {string=} includeLicenses Include modules which licenses are in the comma-separated list in the output.
62
+ * @property {string=} files Path to write output files to.
63
+ */
64
+ /**
65
+ * @typedef {Object} KnownModuleInfo Information about one dependency.
66
+ * @property {string=} name Module name.
67
+ * @property {string=} version Module version.
68
+ * @property {string=} description Module description.
69
+ * @property {string=} repository Repository URL.
70
+ * @property {string=} publisher Publisher name.
71
+ * @property {string=} email Publisher e-mail.
72
+ * @property {string=} url Publisher URL.
73
+ * @property {(string | string[])=} licenses Detected license(s).
74
+ * @property {string=} licenseFile Path to license file, if available.
75
+ * @property {string=} licenseText Contents of the license.
76
+ * @property {string=} licenseModified Whether the license is modified.
77
+ * @property {boolean=} private Private module.
78
+ * @property {string=} path Path to module.
79
+ * @property {boolean=} relativeModulePath Output module locations as relative paths.
80
+ * @property {string=} copyright Copyright statements.
81
+ * @property {string=} noticeFile Path of NOTICE file.
82
+ */
83
+ /**
84
+ * @typedef {KnownModuleInfo & Record<string, string | string[] | boolean | undefined>} ModuleInfo
85
+ */
86
+ /**
87
+ * @typedef {Record<string, ModuleInfo>} ModuleInfos
88
+ */
89
+ /**
90
+ * Checks licenses based on the given options.
91
+ *
92
+ * @param {LicenseCheckOptions} options Controls the license checker's behavior.
93
+ * @returns {Promise<ModuleInfos>} All information on dependencies found by the license checker.
94
+ */
95
+ export const runLicenseCheck = async (options) => {
96
+ debugLog("scanning %s", options.start);
97
+ // customPath is a path to a JSON file that defined a custom format
98
+ if (options.customPath) {
99
+ options.customFormat = readJson(options.customPath);
100
+ }
101
+ const optionsForReadingInstalledPackages = {
102
+ depth: options.direct, // How deep to traverse the dependency tree
103
+ nopeer: options.nopeer, // Whether to skip peerDependencies in output
104
+ dev: true, // Whether to include devDependencies
105
+ log: debugLog, // A function to log debug info
106
+ };
107
+ if (options.production || options.development) {
108
+ optionsForReadingInstalledPackages.dev = false;
109
+ }
110
+ const { failOnLicenses, onlyAllowLicenses } = getLicensePolicy(options);
111
+ // An object mapping from Package name -> list of what contents it should have, including a semver range for each entry
112
+ const clarifications = readClarifications(options.clarificationsFile);
113
+ let installedPackagesJson;
114
+ try {
115
+ installedPackagesJson = await readInstalledPackages(options.start, optionsForReadingInstalledPackages);
116
+ }
117
+ catch (error) {
118
+ debugError(error);
119
+ throw error;
120
+ }
121
+ // Good to know:
122
+ // The json object returned by readInstalledPackages stores all direct dependencies from
123
+ // the package.json file in the property '_dependencies'. The property 'dependencies' contains all dependencies,
124
+ // including the ones that are only required by other dependencies.
125
+ if (optionsForReadingInstalledPackages.depth === 0) {
126
+ deleteNonDirectDependencies(installedPackagesJson, options);
127
+ }
128
+ // 'results' might be longer than 'installedPackagesJson.dependencies', as it appends the version numbers to each key (package name),
129
+ // e.g. 'grunt@1' instead of 'grunt', and this way contains all different installed versions of each package:
130
+ const results = collectLicenseResults({
131
+ args: options,
132
+ basePath: options.relativeLicensePath ? installedPackagesJson.path : null,
133
+ clarifications,
134
+ customFormat: options.customFormat,
135
+ development: options.development,
136
+ direct: options.direct,
137
+ production: options.production,
138
+ rootPackage: installedPackagesJson,
139
+ unknown: options.unknown,
140
+ });
141
+ if (options.clarificationsMatchAll) {
142
+ assertAllClarificationsWereUsed(clarifications);
143
+ }
144
+ const colorize = options.color;
145
+ const sorted = {}; // 'sorted' will store the same items as results, but sorted by package name and version
146
+ let resultJson = {};
147
+ const excludeLicenses = options.excludeLicenses
148
+ ?.match(/([^\\\][^,]|\\,)+/g)
149
+ ?.map((license) => license.replace(/\\,/g, ",").replace(/^\s+|\s+$/g, ""));
150
+ const includeLicenses = options.includeLicenses
151
+ ?.match(/([^\\\][^,]|\\,)+/g)
152
+ ?.map((license) => license.replace(/\\,/g, ",").replace(/^\s+|\s+$/g, ""));
153
+ const colorizeString = (string) => colorize ? boldRed(string, supportsBasicColor()) : string;
154
+ // This following block stores the licenses in the sorted object (before, the sorted object is the empty object):
155
+ Object.keys(results)
156
+ .toSorted()
157
+ .forEach((item) => {
158
+ if (results[item].private) {
159
+ results[item].licenses = colorizeString(LICENSE_TITLE_UNLICENSED);
160
+ }
161
+ if (!results[item].licenses) {
162
+ results[item].licenses = colorizeString(LICENSE_TITLE_UNKNOWN);
163
+ }
164
+ if (options.unknown &&
165
+ results[item].licenses &&
166
+ results[item].licenses !== LICENSE_TITLE_UNKNOWN &&
167
+ String(results[item].licenses).indexOf("*") > -1) {
168
+ results[item].licenses = colorizeString(LICENSE_TITLE_UNKNOWN);
169
+ }
170
+ if (results[item]) {
171
+ if (options.relativeModulePath &&
172
+ results[item].path !== null &&
173
+ results[item].path !== undefined) {
174
+ results[item].path = path.relative(options.start, results[item].path);
175
+ }
176
+ if (options.onlyunknown) {
177
+ if (String(results[item].licenses).indexOf("*") > -1 ||
178
+ String(results[item].licenses).indexOf(LICENSE_TITLE_UNKNOWN) > -1) {
179
+ sorted[item] = results[item];
180
+ }
181
+ }
182
+ else {
183
+ sorted[item] = results[item];
184
+ }
185
+ }
186
+ });
187
+ let noPackagesFoundError;
188
+ if (!Object.keys(sorted).length) {
189
+ noPackagesFoundError = new Error("No packages found in this path...");
190
+ }
191
+ // This following block stores the entries from the 'sorted' object in the
192
+ // resultJson object (before, the resultJson object is the empty object):
193
+ if ((!Array.isArray(excludeLicenses) || excludeLicenses.length === 0) &&
194
+ (!Array.isArray(includeLicenses) || includeLicenses.length === 0)) {
195
+ resultJson = { ...sorted };
196
+ }
197
+ else {
198
+ if (Array.isArray(excludeLicenses) && excludeLicenses.length > 0) {
199
+ Object.entries(sorted).forEach(([packageName, packageData]) => {
200
+ const { licenses } = packageData;
201
+ if (!licenses) {
202
+ resultJson[packageName] = packageData;
203
+ }
204
+ else {
205
+ const licensesArr = Array.isArray(licenses) ? licenses : [licenses];
206
+ const licenseMatch = getLicenseMatch(licensesArr, excludeLicenses);
207
+ if (licenseMatch.hasUnknownLicense || !licenseMatch.match) {
208
+ resultJson[packageName] = packageData;
209
+ }
210
+ }
211
+ });
212
+ }
213
+ if (Array.isArray(includeLicenses) && includeLicenses.length > 0) {
214
+ Object.entries(sorted).forEach(([packageName, packageData]) => {
215
+ const { licenses } = packageData;
216
+ if (!licenses) {
217
+ resultJson[packageName] = packageData;
218
+ }
219
+ else {
220
+ const licensesArr = Array.isArray(licenses) ? licenses : [licenses];
221
+ const licenseMatch = getLicenseMatch(licensesArr, includeLicenses);
222
+ if (licenseMatch.hasUnknownLicense || licenseMatch.match) {
223
+ resultJson[packageName] = packageData;
224
+ }
225
+ }
226
+ });
22
227
  }
23
228
  }
24
- const items = [...results.values()].toSorted((a, b) => a.name.localeCompare(b.name) || a.version.localeCompare(b.version));
25
- if (options.files)
26
- await writeIndividualLicenseFilesToDir(path.resolve(options.files), items);
27
- return items;
28
- }
229
+ // package whitelist
230
+ const whitelist = getOptionArray(options.includePackages);
231
+ if (whitelist) {
232
+ resultJson = includePackages(whitelist, resultJson);
233
+ }
234
+ // package blacklist
235
+ const blacklist = getOptionArray(options.excludePackages);
236
+ if (blacklist) {
237
+ resultJson = excludePackages(blacklist, resultJson);
238
+ }
239
+ // exclude by package name starting with a string
240
+ const excludeStartStringsArr = getOptionArray(options.excludePackagesStartingWith);
241
+ if (excludeStartStringsArr) {
242
+ resultJson = excludePackagesStartingWith(excludeStartStringsArr, resultJson);
243
+ }
244
+ if (options.excludePrivatePackages) {
245
+ resultJson = excludePrivatePackages(resultJson);
246
+ }
247
+ Object.keys(resultJson).forEach((packageName) => {
248
+ throwIfLicensePolicyFails({
249
+ currentLicense: resultJson[packageName]?.licenses,
250
+ failOnLicenses,
251
+ onlyAllowLicenses,
252
+ packageName,
253
+ });
254
+ });
255
+ if (noPackagesFoundError) {
256
+ debugError(noPackagesFoundError);
257
+ throw noPackagesFoundError;
258
+ }
259
+ if (options.out) {
260
+ await writeOutputToFile(options.out, getFormattedOutput(resultJson, options));
261
+ }
262
+ if (options.files) {
263
+ await writeIndividualLicenseFilesToDir(options.files, resultJson);
264
+ }
265
+ return resultJson;
266
+ };
267
+ /**
268
+ * Runs the license check for the given args.
269
+ *
270
+ * @param {LicenseCheckOptions} args Specifies the path to the module to check dependencies of.
271
+ * @param {(err: Error | null, ret: ModuleInfos) => void} callback Called after the checker finished.
272
+ * @deprecated Will be removed in a future version. Please switch to {@link runLicenseCheck} instead.
273
+ */
274
+ export const init = (args, callback) => {
275
+ runLicenseCheck(args).then((result) => callback(null, result), (error) => callback(error instanceof Error ? error : new Error(String(error)), {}));
276
+ };
@@ -0,0 +1,3 @@
1
+ import type { Clarifications } from "../types.js";
2
+ export declare function readClarifications(clarificationsFile?: string): Clarifications;
3
+ export declare function assertAllClarificationsWereUsed(clarifications: Clarifications): void;
@@ -0,0 +1,33 @@
1
+ import { readJson } from "../files/read-json.js";
2
+ export function readClarifications(clarificationsFile) {
3
+ const clarifications = {};
4
+ if (!clarificationsFile) {
5
+ return clarifications;
6
+ }
7
+ const clarificationsFromFile = readJson(clarificationsFile);
8
+ for (const [versionString, clarification] of Object.entries(clarificationsFromFile)) {
9
+ const versionSplit = versionString.lastIndexOf("@");
10
+ if (versionSplit !== -1) {
11
+ const name = versionString.slice(0, versionSplit);
12
+ const semverRange = versionString.slice(versionSplit + 1);
13
+ clarifications[name] = clarifications[name] || [];
14
+ // keep track for each clarification if it was used, optionally error when not
15
+ clarifications[name].push({ ...clarification, semverRange, used: false });
16
+ }
17
+ }
18
+ return clarifications;
19
+ }
20
+ export function assertAllClarificationsWereUsed(clarifications) {
21
+ const unusedClarifications = [];
22
+ for (const [packageName, entries] of Object.entries(clarifications)) {
23
+ for (const clarification of entries) {
24
+ if (!clarification.used) {
25
+ unusedClarifications.push(`${packageName}@${clarification.semverRange}`);
26
+ }
27
+ }
28
+ }
29
+ if (unusedClarifications.length) {
30
+ const list = unusedClarifications.join(", ");
31
+ throw new Error(`Some clarifications (${list}) were unused and --clarificationsMatchAll was specified. Exiting.`);
32
+ }
33
+ }
@@ -1,2 +1,26 @@
1
- import type { DependencyLicense } from "../types.js";
2
- export declare function collectLicenseResults(packages: DependencyLicense[]): Promise<DependencyLicense[]>;
1
+ import type { Clarifications, DependencyPackage, LicenseCheckOptions, ModuleInfos } from "../types.js";
2
+ /**
3
+ * @param {{
4
+ * args?: Record<string, unknown>,
5
+ * basePath?: string | null,
6
+ * clarifications?: Record<string, Array<Record<string, unknown>>>,
7
+ * customFormat?: Record<string, unknown>,
8
+ * development?: boolean,
9
+ * direct?: number | boolean | string | null,
10
+ * production?: boolean,
11
+ * rootPackage: Record<string, unknown>,
12
+ * unknown?: boolean
13
+ * }} options
14
+ * @returns {Record<string, Record<string, unknown>>}
15
+ */
16
+ export declare const collectLicenseResults: ({ args, basePath, clarifications, customFormat, development, direct, production, rootPackage, unknown, }: {
17
+ args?: LicenseCheckOptions;
18
+ basePath?: string | null;
19
+ clarifications?: Clarifications;
20
+ customFormat?: LicenseCheckOptions["customFormat"];
21
+ development?: boolean;
22
+ direct?: number | boolean | string | null;
23
+ production?: boolean;
24
+ rootPackage: DependencyPackage;
25
+ unknown?: boolean;
26
+ }) => ModuleInfos;