@kingironman2011/license-checker 1.0.1 → 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 +90 -0
  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
@@ -1,66 +1,188 @@
1
- import { readFile, realpath } from "node:fs/promises";
1
+ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- async function resolveDependency(parentDir, name) {
4
- let currentDir = parentDir;
5
- while (true) {
6
- const candidate = path.join(currentDir, "node_modules", name, "package.json");
3
+ const clonePackageJson = (packageJson) => JSON.parse(JSON.stringify(packageJson ?? {}));
4
+ const readPackageJsonFromDisk = async (packagePath) => {
5
+ try {
6
+ const content = await fs.readFile(path.join(packagePath, "package.json"), "utf8");
7
+ return clonePackageJson(JSON.parse(content));
8
+ }
9
+ catch {
10
+ try {
11
+ const source = await fs.readFile(path.join(packagePath, "index.js"), "utf8");
12
+ const manifest = /\/\*\*package(?:\s|$)([\s\S]*?)\*\*\//m.exec(source)?.[1];
13
+ if (!manifest)
14
+ return {};
15
+ const json = manifest.replace(/^\s*\*/gm, "");
16
+ return clonePackageJson(JSON.parse(json));
17
+ }
18
+ catch {
19
+ return {};
20
+ }
21
+ }
22
+ };
23
+ const getDependencySpecs = (packageJson) => ({
24
+ ...packageJson.dependencies,
25
+ ...packageJson.optionalDependencies,
26
+ });
27
+ const getRootDependencySpecs = (packageJson) => ({
28
+ ...getDependencySpecs(packageJson),
29
+ });
30
+ async function packageDirectories(directory) {
31
+ let entries;
32
+ try {
33
+ entries = await fs.readdir(directory, {
34
+ withFileTypes: true,
35
+ encoding: "utf8",
36
+ });
37
+ }
38
+ catch {
39
+ return [];
40
+ }
41
+ const packages = [];
42
+ for (const entry of entries) {
43
+ if (entry.name.startsWith("."))
44
+ continue;
45
+ if (entry.name.startsWith("@")) {
46
+ packages.push(...(await packageDirectories(path.join(directory, entry.name))));
47
+ continue;
48
+ }
49
+ const packagePath = path.join(directory, entry.name);
7
50
  try {
8
- await readFile(candidate, "utf8");
9
- return path.dirname(candidate);
51
+ if ((await fs.stat(packagePath)).isDirectory())
52
+ packages.push(packagePath);
10
53
  }
11
54
  catch {
12
- const parent = path.dirname(currentDir);
13
- if (parent === currentDir)
14
- return undefined;
15
- currentDir = parent;
55
+ // Broken symlinks are not installed packages.
16
56
  }
17
57
  }
58
+ return packages;
18
59
  }
19
- export async function readInstalledPackages(projectDir, scope) {
20
- const root = JSON.parse(await readFile(path.join(projectDir, "package.json"), "utf8"));
21
- const roots = scope === "production"
22
- ? { ...root.dependencies, ...root.optionalDependencies }
23
- : (root.devDependencies ?? {});
24
- const found = new Map();
25
- const visited = new Set();
26
- async function visit(packagePath, dependencyName) {
27
- let realPackagePath;
60
+ async function resolveInstalledPackage(fromPath, dependencyName) {
61
+ let directory = fromPath;
62
+ while (true) {
63
+ const candidate = path.join(directory, "node_modules", dependencyName);
28
64
  try {
29
- realPackagePath = await realpath(packagePath);
65
+ if ((await fs.stat(candidate)).isDirectory())
66
+ return candidate;
30
67
  }
31
68
  catch {
32
- return;
69
+ // Keep searching through parent node_modules directories.
33
70
  }
34
- if (visited.has(realPackagePath))
35
- return;
36
- visited.add(realPackagePath);
37
- const pkg = JSON.parse(await readFile(path.join(packagePath, "package.json"), "utf8"));
38
- const name = pkg.name ?? dependencyName;
39
- const version = pkg.version ?? "unknown";
40
- const key = `${name}@${version}`;
41
- found.set(key, {
42
- name,
43
- version,
44
- license: pkg.license && typeof pkg.license === "string"
45
- ? pkg.license
46
- : ((typeof pkg.license === "object" ? pkg.license.type : undefined) ??
47
- pkg.licenses?.map(({ type }) => type ?? "Unknown").join(" OR ") ??
48
- "Unknown"),
49
- scope,
50
- packagePath,
51
- licenseFiles: [],
52
- });
53
- const dependencies = { ...pkg.dependencies, ...pkg.optionalDependencies };
54
- for (const childName of Object.keys(dependencies).toSorted()) {
55
- const childPath = await resolveDependency(packagePath, childName);
56
- if (childPath)
57
- await visit(childPath, childName);
71
+ const parent = path.dirname(directory);
72
+ if (parent === directory)
73
+ return undefined;
74
+ directory = parent;
75
+ }
76
+ }
77
+ const findDependency = (packageJson, dependencyName) => {
78
+ let currentPackageJson = packageJson;
79
+ while (currentPackageJson) {
80
+ const dependency = currentPackageJson.dependencies?.[dependencyName];
81
+ if (dependency && typeof dependency === "object") {
82
+ return dependency;
58
83
  }
84
+ if (currentPackageJson.realName === dependencyName) {
85
+ return currentPackageJson;
86
+ }
87
+ currentPackageJson = currentPackageJson.link
88
+ ? null
89
+ : (currentPackageJson.parent ?? null);
59
90
  }
60
- for (const name of Object.keys(roots).toSorted()) {
61
- const packagePath = await resolveDependency(projectDir, name);
62
- if (packagePath)
63
- await visit(packagePath, name);
91
+ };
92
+ const unmarkExtraneous = (packageJson, options) => {
93
+ packageJson.extraneous = false;
94
+ const dependencies = packageJson["_dependencies"] ?? {};
95
+ if (options.dev &&
96
+ packageJson.devDependencies &&
97
+ (packageJson.root || packageJson.link)) {
98
+ Object.keys(packageJson.devDependencies).forEach((dependencyName) => {
99
+ dependencies[dependencyName] = String(packageJson.devDependencies?.[dependencyName]);
100
+ });
64
101
  }
65
- return [...found.values()];
66
- }
102
+ if (!options.nopeer && packageJson.peerDependencies) {
103
+ Object.keys(packageJson.peerDependencies).forEach((dependencyName) => {
104
+ dependencies[dependencyName] = String(packageJson.peerDependencies?.[dependencyName]);
105
+ });
106
+ }
107
+ Object.keys(dependencies).forEach((dependencyName) => {
108
+ const dependency = findDependency(packageJson, dependencyName);
109
+ if (dependency?.extraneous) {
110
+ unmarkExtraneous(dependency, options);
111
+ }
112
+ });
113
+ };
114
+ const readInstalledPackages = async (folder, options = {}) => {
115
+ const rootPath = path.resolve(folder);
116
+ const convertedNodes = new Map();
117
+ const convertNode = async (installedPath, parent = null, depth = 0, dependencyName) => {
118
+ const existing = convertedNodes.get(installedPath);
119
+ if (existing)
120
+ return existing;
121
+ const realPath = await fs
122
+ .realpath(installedPath)
123
+ .catch(() => installedPath);
124
+ const packageJson = {
125
+ ...clonePackageJson(await readPackageJsonFromDisk(realPath)),
126
+ };
127
+ const isRoot = installedPath === rootPath;
128
+ const dependencySpecs = isRoot
129
+ ? getRootDependencySpecs(packageJson)
130
+ : getDependencySpecs(packageJson);
131
+ const node = {
132
+ ...packageJson,
133
+ name: packageJson.name ?? dependencyName ?? path.basename(installedPath),
134
+ version: packageJson.version,
135
+ path: installedPath,
136
+ realPath,
137
+ realName: dependencyName ?? packageJson.name ?? path.basename(installedPath),
138
+ _dependencies: Object.fromEntries(Object.entries(dependencySpecs).map(([key, value]) => [
139
+ key,
140
+ String(value),
141
+ ])),
142
+ dependencies: {},
143
+ extraneous: true,
144
+ depth,
145
+ };
146
+ if (realPath !== installedPath)
147
+ node.link = realPath;
148
+ if (node.realName && node.name !== node.realName)
149
+ node.invalid = true;
150
+ if (parent && !node.link)
151
+ node.parent = parent;
152
+ convertedNodes.set(installedPath, node);
153
+ const convertedDependencies = (node.dependencies ??= {});
154
+ for (const childPath of await packageDirectories(path.join(installedPath, "node_modules"))) {
155
+ const childName = path
156
+ .relative(path.join(installedPath, "node_modules"), childPath)
157
+ .split(path.sep)
158
+ .join("/");
159
+ convertedDependencies[childName] = await convertNode(childPath, node, depth + 1, childName);
160
+ }
161
+ const specs = { ...dependencySpecs };
162
+ if (options.dev && (isRoot || node.link)) {
163
+ Object.assign(specs, packageJson.devDependencies);
164
+ }
165
+ if (!options.nopeer)
166
+ Object.assign(specs, packageJson.peerDependencies);
167
+ for (const [name, spec] of Object.entries(specs)) {
168
+ if (options.nopeer &&
169
+ packageJson.peerDependencies?.[name] !== undefined) {
170
+ continue;
171
+ }
172
+ const installed = await resolveInstalledPackage(installedPath, name);
173
+ if (!installed) {
174
+ if (packageJson.optionalDependencies?.[name] === undefined) {
175
+ convertedDependencies[name] = String(spec);
176
+ }
177
+ continue;
178
+ }
179
+ convertedDependencies[name] = await convertNode(installed, node, depth + 1, name);
180
+ }
181
+ return node;
182
+ };
183
+ const convertedRoot = await convertNode(rootPath);
184
+ convertedRoot.root = true;
185
+ unmarkExtraneous(convertedRoot, options);
186
+ return convertedRoot;
187
+ };
188
+ export default readInstalledPackages;
@@ -0,0 +1,19 @@
1
+ import type { DependencyPackage } from "../types.js";
2
+ /**
3
+ * Walks a dependency tree depth-first and calls `visit` for every accepted node.
4
+ *
5
+ * A maxDepth of 0 still visits direct dependencies, matching the license checker's historic `direct: 0` behavior.
6
+ *
7
+ * @param {DependencyTreeNode} rootDependency
8
+ * @param {{
9
+ * maxDepth?: number | boolean | string | null,
10
+ * shouldVisit?: (dependency: DependencyTreeNode, currentDepth: number) => boolean,
11
+ * visit: (dependency: DependencyTreeNode, currentDepth: number) => void | boolean
12
+ * }} options
13
+ * @returns {void}
14
+ */
15
+ export declare function walkDependencyTree(rootDependency: DependencyPackage, { maxDepth, shouldVisit, visit, }: {
16
+ maxDepth?: number | boolean | string | null;
17
+ shouldVisit?: (dependency: DependencyPackage, currentDepth: number) => boolean;
18
+ visit: (dependency: DependencyPackage, currentDepth: number) => void | boolean;
19
+ }): void;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Walks a dependency tree depth-first and calls `visit` for every accepted node.
3
+ *
4
+ * A maxDepth of 0 still visits direct dependencies, matching the license checker's historic `direct: 0` behavior.
5
+ *
6
+ * @param {DependencyTreeNode} rootDependency
7
+ * @param {{
8
+ * maxDepth?: number | boolean | string | null,
9
+ * shouldVisit?: (dependency: DependencyTreeNode, currentDepth: number) => boolean,
10
+ * visit: (dependency: DependencyTreeNode, currentDepth: number) => void | boolean
11
+ * }} options
12
+ * @returns {void}
13
+ */
14
+ export function walkDependencyTree(rootDependency, { maxDepth = Number.POSITIVE_INFINITY, shouldVisit = () => true, visit, }) {
15
+ const depthLimit = typeof maxDepth === "boolean"
16
+ ? maxDepth
17
+ ? Number.POSITIVE_INFINITY
18
+ : 0
19
+ : typeof maxDepth === "string"
20
+ ? Number(maxDepth)
21
+ : (maxDepth ?? Number.POSITIVE_INFINITY);
22
+ const walk = (dependency, currentDepth) => {
23
+ if (typeof dependency === "string")
24
+ return;
25
+ if (!dependency || !shouldVisit(dependency, currentDepth)) {
26
+ return;
27
+ }
28
+ const visitResult = visit(dependency, currentDepth);
29
+ const dependencies = dependency.dependencies;
30
+ if (visitResult === false || currentDepth > depthLimit || !dependencies) {
31
+ return;
32
+ }
33
+ Object.keys(dependencies).forEach((dependencyName) => {
34
+ walk(dependencies[dependencyName], currentDepth + 1);
35
+ });
36
+ };
37
+ walk(rootDependency, 0);
38
+ }
@@ -0,0 +1 @@
1
+ export declare const readJson: <T = Record<string, unknown>>(jsonPath: string) => T;
@@ -0,0 +1,8 @@
1
+ import fs from "node:fs";
2
+ export const readJson = (jsonPath) => {
3
+ if (typeof jsonPath !== "string") {
4
+ throw new Error("The path was not specified for the JSON file to parse.");
5
+ }
6
+ const jsonFileContents = fs.readFileSync(jsonPath, { encoding: "utf8" });
7
+ return JSON.parse(jsonFileContents);
8
+ };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,88 @@
1
- import type { DependencyLicense, LicenseCheckOptions } from "./types.js";
2
- export type { DependencyLicense, DependencyScope, LicenseCheckOptions, } from "./types.js";
3
- export declare function runLicenseCheck(options?: LicenseCheckOptions): Promise<DependencyLicense[]>;
1
+ import type { LicenseCheckOptions, ModuleInfos } from "./types.js";
2
+ /**
3
+ * Custom output field default value or toggle.
4
+ *
5
+ * For CSV output, custom format keys define the output columns after module_name.
6
+ * For JSON output, custom format keys add or populate fields in addition to the usual result fields.
7
+ * A value of false disables optional population for that field where supported, but does not remove core fields such as
8
+ * licenses.
9
+ *
10
+ * Use limitAttributes to restrict formatted JSON output to a specific set of fields.
11
+ *
12
+ * @typedef {(string | boolean | undefined)} CustomFormatValue
13
+ */
14
+ /**
15
+ * @typedef {Record<string, CustomFormatValue>} CustomFormat
16
+ */
17
+ /**
18
+ * @typedef {Object} LicenseCheckOptions Options struct for the init() and runLicenseCheck() functions.
19
+ * @property {string} start Path to start checking dependencies from.
20
+ * @property {boolean=} production Only show production dependencies.
21
+ * @property {boolean=} development Only show development dependencies.
22
+ * @property {boolean=} unknown Report guessed licenses as unknown licenses.
23
+ * @property {boolean=} onlyunknown Only list packages with unknown or guessed licenses.
24
+ * @property {boolean=} json Output in JSON format.
25
+ * @property {boolean=} csv Output in CSV format.
26
+ * @property {string=} csvComponentPrefix Prefix column for component in CSV format.
27
+ * @property {string=} out Write the data to a specific file.
28
+ * @property {string=} customPath Path to a JSON file that defines a custom output format.
29
+ * @property {string=} excludeLicenses Exclude modules which licenses are in the comma-separated list from the output.
30
+ * @property {boolean=} relativeLicensePath Output the location of the license files as relative paths.
31
+ * @property {boolean=} relativeModulePath Output module locations as relative paths.
32
+ * @property {boolean=} summary Output a summary of the license usage.
33
+ * @property {string=} failOn Fail (exit with code 1) on the first occurrence of the licenses of the semicolon-separated list.
34
+ * @property {string=} onlyAllow Fail (exit with code 1) on the first occurrence of the licenses not in the semicolon-separated list.
35
+ * @property {string=} includePackages Restrict output to the packages (package@version) in the semicolon-separated list.
36
+ * @property {string=} excludePackages Restrict output to the packages (package@version) not in the semicolon-separated list.
37
+ * @property {boolean=} excludePrivatePackages Restrict output to not include any package marked as private.
38
+ * @property {string=} excludePackagesStartingWith Exclude modules starting with a specific string.
39
+ * @property {(boolean | number)=} direct Look for direct dependencies only.
40
+ * @property {number=} depth Recurse dependencies up to a specific depth, overrides "direct".
41
+ * @property {boolean=} color Colorize output.
42
+ * @property {CustomFormat=} customFormat Custom output fields and default values.
43
+ * @property {boolean=} nopeer Ignore peerDependencies.
44
+ * @property {string=} clarificationsFile A file that contains license clarifications for malformed or non-standard packages.
45
+ * @property {boolean=} clarificationsMatchAll Require every clarification to match a package.
46
+ * @property {string=} includeLicenses Include modules which licenses are in the comma-separated list in the output.
47
+ * @property {string=} files Path to write output files to.
48
+ */
49
+ /**
50
+ * @typedef {Object} KnownModuleInfo Information about one dependency.
51
+ * @property {string=} name Module name.
52
+ * @property {string=} version Module version.
53
+ * @property {string=} description Module description.
54
+ * @property {string=} repository Repository URL.
55
+ * @property {string=} publisher Publisher name.
56
+ * @property {string=} email Publisher e-mail.
57
+ * @property {string=} url Publisher URL.
58
+ * @property {(string | string[])=} licenses Detected license(s).
59
+ * @property {string=} licenseFile Path to license file, if available.
60
+ * @property {string=} licenseText Contents of the license.
61
+ * @property {string=} licenseModified Whether the license is modified.
62
+ * @property {boolean=} private Private module.
63
+ * @property {string=} path Path to module.
64
+ * @property {boolean=} relativeModulePath Output module locations as relative paths.
65
+ * @property {string=} copyright Copyright statements.
66
+ * @property {string=} noticeFile Path of NOTICE file.
67
+ */
68
+ /**
69
+ * @typedef {KnownModuleInfo & Record<string, string | string[] | boolean | undefined>} ModuleInfo
70
+ */
71
+ /**
72
+ * @typedef {Record<string, ModuleInfo>} ModuleInfos
73
+ */
74
+ /**
75
+ * Checks licenses based on the given options.
76
+ *
77
+ * @param {LicenseCheckOptions} options Controls the license checker's behavior.
78
+ * @returns {Promise<ModuleInfos>} All information on dependencies found by the license checker.
79
+ */
80
+ export declare const runLicenseCheck: (options: LicenseCheckOptions) => Promise<ModuleInfos>;
81
+ /**
82
+ * Runs the license check for the given args.
83
+ *
84
+ * @param {LicenseCheckOptions} args Specifies the path to the module to check dependencies of.
85
+ * @param {(err: Error | null, ret: ModuleInfos) => void} callback Called after the checker finished.
86
+ * @deprecated Will be removed in a future version. Please switch to {@link runLicenseCheck} instead.
87
+ */
88
+ export declare const init: (args: LicenseCheckOptions, callback: (error: Error | null, results: ModuleInfos) => void) => void;