@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
@@ -0,0 +1,112 @@
1
+ import fs from "node:fs";
2
+ import { detectLicenseTitle } from "../licenses/detect-license-title.js";
3
+ function renderChildren(value, ancestorLastStates) {
4
+ const keys = Object.keys(value);
5
+ return keys.flatMap((key, index) => {
6
+ const child = value[key];
7
+ const isLast = index === keys.length - 1;
8
+ const prefix = ancestorLastStates
9
+ .map((wasLast) => (wasLast ? " " : "│ "))
10
+ .join("");
11
+ const isContainer = child !== null && typeof child === "object";
12
+ const line = `${prefix}${isLast ? "└" : "├"}─ ${key}${isContainer ? "" : `: ${String(child)}`}`;
13
+ return [
14
+ line,
15
+ ...(isContainer
16
+ ? renderChildren(child, [
17
+ ...ancestorLastStates,
18
+ isLast,
19
+ ])
20
+ : []),
21
+ ];
22
+ });
23
+ }
24
+ function asTreeObject(value) {
25
+ const lines = renderChildren(value, []);
26
+ return lines.length ? `${lines.join("\n")}\n` : "";
27
+ }
28
+ export const asTree = (sorted) => asTreeObject(sorted);
29
+ export const asSummary = (sorted) => {
30
+ const licenseCountMap = new global.Map();
31
+ const licenseCountArray = [];
32
+ const sortedLicenseCountObj = {};
33
+ Object.values(sorted).forEach(({ licenses }) => {
34
+ if (typeof licenses === "string") {
35
+ licenseCountMap.set(licenses, (licenseCountMap.get(licenses) ?? 0) + 1);
36
+ }
37
+ });
38
+ licenseCountMap.forEach((count, license) => {
39
+ licenseCountArray.push({ license, count });
40
+ });
41
+ licenseCountArray
42
+ .toSorted((a, b) => b.count - a.count)
43
+ .forEach(({ license, count }) => {
44
+ sortedLicenseCountObj[license] = String(count);
45
+ });
46
+ return asTreeObject(sortedLicenseCountObj);
47
+ };
48
+ /**
49
+ * Exports data as Markdown (*.md) which has its own syntax.
50
+ *
51
+ * @param sorted The sorted JSON data from all packages.
52
+ * @param {object} [customFormat] The custom format with information about the needed keys.
53
+ * @return {string} The returning plain text.
54
+ */
55
+ export const asMarkDown = (sorted, customFormat) => {
56
+ const text = [];
57
+ if (customFormat && Object.keys(customFormat).length > 0) {
58
+ Object.keys(sorted).forEach((sortedItem) => {
59
+ text.push(`- **[${sortedItem}](${sorted[sortedItem].repository})**`);
60
+ Object.keys(customFormat).forEach((customItem) => {
61
+ text.push(` - ${customItem}: ${sorted[sortedItem][customItem]}`);
62
+ });
63
+ });
64
+ }
65
+ else {
66
+ Object.keys(sorted).forEach((key) => {
67
+ const module = sorted[key];
68
+ text.push(`- [${key}](${module.repository}) - ${module.licenses}`);
69
+ });
70
+ }
71
+ return text.join("\n");
72
+ };
73
+ const getModuleNameForLicenseTextHeader = (moduleName = "") => {
74
+ const lastIndexOfAtCharacter = moduleName.lastIndexOf("@");
75
+ return `${moduleName.substring(0, lastIndexOfAtCharacter)} ${moduleName.substring(lastIndexOfAtCharacter + 1)}\n`;
76
+ };
77
+ /**
78
+ * Output data in plain vertical format like Angular CLI does: https://angular.io/3rdpartylicenses.txt
79
+ */
80
+ export const asPlainVertical = (sorted) => Object.entries(sorted)
81
+ .map(([moduleName, moduleData]) => {
82
+ let licenseText = getModuleNameForLicenseTextHeader(moduleName);
83
+ if (Array.isArray(moduleData.licenses) &&
84
+ moduleData.licenses.length > 0) {
85
+ // biome-ignore lint/suspicious/useIterableCallbackReturn: TODO we'll have to check if "moduleData.licenses" can contain something that might not be handled inside the map callback
86
+ licenseText += moduleData.licenses.map((moduleLicense) => {
87
+ if (typeof moduleLicense === "object") {
88
+ return moduleLicense.type || moduleLicense.name;
89
+ }
90
+ if (typeof moduleLicense === "string") {
91
+ return moduleLicense;
92
+ }
93
+ });
94
+ }
95
+ else if (!Array.isArray(moduleData.licenses) &&
96
+ moduleData.licenses !== undefined &&
97
+ typeof moduleData.licenses === "object" &&
98
+ (moduleData.licenses.type || moduleData.licenses.name)) {
99
+ licenseText += detectLicenseTitle(moduleData.licenses.type || moduleData.licenses.name);
100
+ }
101
+ else if (typeof moduleData.licenses === "string") {
102
+ licenseText += detectLicenseTitle(moduleData.licenses);
103
+ }
104
+ licenseText += "\n";
105
+ if (typeof moduleData.licenseFile === "string") {
106
+ licenseText += fs.readFileSync(moduleData.licenseFile, {
107
+ encoding: "utf8",
108
+ });
109
+ }
110
+ return licenseText;
111
+ })
112
+ .join("\n\n");
@@ -1,2 +1,3 @@
1
- import type { DependencyLicense } from "../types.js";
2
- export declare function writeIndividualLicenseFilesToDir(outputDir: string, items: DependencyLicense[]): Promise<void>;
1
+ import type { ModuleInfos } from "../types.js";
2
+ export declare function writeOutputToFile(outputFile: string, formattedOutput: string): Promise<void>;
3
+ export declare function writeIndividualLicenseFilesToDir(outDir: string, resultJson: ModuleInfos): Promise<void>;
@@ -1,15 +1,23 @@
1
- import { cp, mkdir, writeFile } from "node:fs/promises";
1
+ import fs from "node:fs";
2
2
  import path from "node:path";
3
- export async function writeIndividualLicenseFilesToDir(outputDir, items) {
4
- for (const item of items) {
5
- const safeName = item.name.replaceAll("/", "+");
6
- const destination = path.join(outputDir, item.scope, `${safeName}@${item.version}`);
7
- await mkdir(destination, { recursive: true });
8
- for (const file of item.licenseFiles) {
9
- await cp(path.join(item.packagePath, file), path.join(destination, file));
3
+ export async function writeOutputToFile(outputFile, formattedOutput) {
4
+ const dir = path.dirname(outputFile);
5
+ await fs.promises.mkdir(dir, { recursive: true });
6
+ await fs.promises.writeFile(outputFile, formattedOutput, "utf-8");
7
+ }
8
+ export async function writeIndividualLicenseFilesToDir(outDir, resultJson) {
9
+ await fs.promises.mkdir(outDir, { recursive: true });
10
+ for (const key of Object.keys(resultJson)) {
11
+ const { licenseFile } = resultJson[key];
12
+ if (licenseFile && fs.existsSync(licenseFile)) {
13
+ const outPath = path.join(outDir, `${key}-LICENSE.txt`);
14
+ // key contains versioned packages, potentially scoped, e.g. @foo/bar@1.2.3
15
+ const effectiveOutDir = path.dirname(outPath);
16
+ await fs.promises.mkdir(effectiveOutDir, { recursive: true });
17
+ await fs.promises.copyFile(licenseFile, outPath);
10
18
  }
11
- if (item.licenseFiles.length === 0) {
12
- await writeFile(path.join(destination, "LICENSE-INFO.txt"), `License metadata: ${item.license}\n`);
19
+ else {
20
+ console.warn(`No license file found for module '${key}'`);
13
21
  }
14
22
  }
15
23
  }
@@ -0,0 +1,20 @@
1
+ import type { LicenseCheckOptions, LicenseValue } from "../types.js";
2
+ export declare function getLicensePolicy(options: Pick<LicenseCheckOptions, "failOn" | "onlyAllow">): {
3
+ failOnLicenses: string[];
4
+ onlyAllowLicenses: string[];
5
+ };
6
+ export declare function checkForFailOn(currentLicense: LicenseValue | LicenseValue[], failOnLicenses: string[]): void;
7
+ /**
8
+ * Check if the current license contains (eventually among others) at least one of the allowed licenses.
9
+ */
10
+ export declare function checkForOnlyAllow(currentLicense: LicenseValue | LicenseValue[], packageName: string, onlyAllowLicenses: string[]): void;
11
+ export declare function throwIfLicensePolicyFails({ currentLicense, failOnLicenses, onlyAllowLicenses, packageName, }: {
12
+ currentLicense?: LicenseValue | LicenseValue[];
13
+ failOnLicenses: string[];
14
+ onlyAllowLicenses: string[];
15
+ packageName: string;
16
+ }): void;
17
+ export declare function getLicenseMatch(licensesArr: LicenseValue[], compareLicenses: string[]): {
18
+ hasUnknownLicense: boolean;
19
+ match: boolean;
20
+ };
@@ -0,0 +1,112 @@
1
+ import { correctSpdxExpression, spdxExpressionSatisfies, } from "../licenses/spdx.js";
2
+ const LICENSE_TITLE_UNKNOWN = "UNKNOWN";
3
+ const bsdLicenses = ["0BSD", "BSD-2-Clause", "BSD-3-Clause", "BSD-4-Clause"];
4
+ const transformBSD = (spdx) => spdx === "BSD" ? `(${bsdLicenses.join(" OR ")})` : spdx;
5
+ const expandBSD = (spdx) => spdx === "BSD" ? bsdLicenses : [spdx];
6
+ const invertResultOf = (fn) => (spdx) => !fn(spdx);
7
+ const spdxIsValid = (spdx) => correctSpdxExpression(spdx) === spdx;
8
+ const parsePolicyList = (value) => {
9
+ const licenses = [];
10
+ value.split(";").forEach((license) => {
11
+ const trimmed = license.trim();
12
+ if (trimmed.length > 0) {
13
+ licenses.push(trimmed);
14
+ }
15
+ });
16
+ return licenses;
17
+ };
18
+ export function getLicensePolicy(options) {
19
+ if (options.failOn) {
20
+ return {
21
+ failOnLicenses: parsePolicyList(options.failOn),
22
+ onlyAllowLicenses: [],
23
+ };
24
+ }
25
+ if (options.onlyAllow) {
26
+ return {
27
+ failOnLicenses: [],
28
+ onlyAllowLicenses: parsePolicyList(options.onlyAllow),
29
+ };
30
+ }
31
+ return {
32
+ failOnLicenses: [],
33
+ onlyAllowLicenses: [],
34
+ };
35
+ }
36
+ export function checkForFailOn(currentLicense, failOnLicenses) {
37
+ if (!Array.isArray(failOnLicenses) || failOnLicenses.length === 0) {
38
+ return;
39
+ }
40
+ const licenses = Array.isArray(currentLicense)
41
+ ? currentLicense
42
+ : [currentLicense];
43
+ if (licenses.some((license) => typeof license === "string" && failOnLicenses.includes(license))) {
44
+ throw new Error(`Found license defined by the --failOn flag: "${currentLicense}". Exiting.`);
45
+ }
46
+ }
47
+ /**
48
+ * Check if the current license contains (eventually among others) at least one of the allowed licenses.
49
+ */
50
+ export function checkForOnlyAllow(currentLicense, packageName, onlyAllowLicenses) {
51
+ if (onlyAllowLicenses.length > 0) {
52
+ let containsOneOfAllowedPackages = false;
53
+ for (const allowedLicense of onlyAllowLicenses) {
54
+ // "currentLicense" is a longer string that may contain several license names,
55
+ // and we check if one of those is a license listed in the "onlyAllowLicenses"
56
+ // licenses array:
57
+ if ((typeof currentLicense === "string" &&
58
+ currentLicense.includes(allowedLicense)) ||
59
+ (Array.isArray(currentLicense) &&
60
+ currentLicense.includes(allowedLicense)) ||
61
+ (typeof currentLicense === "object" &&
62
+ currentLicense !== null &&
63
+ !Array.isArray(currentLicense) &&
64
+ (currentLicense.type === allowedLicense ||
65
+ currentLicense.name === allowedLicense))) {
66
+ containsOneOfAllowedPackages = true;
67
+ break;
68
+ }
69
+ }
70
+ if (!containsOneOfAllowedPackages) {
71
+ throw new Error(`Package "${packageName}" is licensed under "${currentLicense}" which is not permitted by the --onlyAllow flag. Exiting.`);
72
+ }
73
+ }
74
+ }
75
+ export function throwIfLicensePolicyFails({ currentLicense, failOnLicenses, onlyAllowLicenses, packageName, }) {
76
+ if (currentLicense) {
77
+ checkForFailOn(currentLicense, failOnLicenses);
78
+ checkForOnlyAllow(currentLicense, packageName, onlyAllowLicenses);
79
+ }
80
+ }
81
+ export function getLicenseMatch(licensesArr, compareLicenses) {
82
+ const expandedCompareLicenses = compareLicenses.flatMap(expandBSD);
83
+ const validSPDXLicenses = expandedCompareLicenses.filter(spdxIsValid);
84
+ const invalidSPDXLicenses = expandedCompareLicenses
85
+ .map(transformBSD)
86
+ .filter(invertResultOf(spdxIsValid));
87
+ let hasUnknownLicense = false;
88
+ let match = false;
89
+ licensesArr.forEach((licenseValue) => {
90
+ const license = typeof licenseValue === "string"
91
+ ? licenseValue
92
+ : (licenseValue.type ?? licenseValue.name ?? "");
93
+ if (license.indexOf(LICENSE_TITLE_UNKNOWN) >= 0) {
94
+ // Necessary due to colorization and preserves the historic include/exclude behavior for unknown licenses.
95
+ hasUnknownLicense = true;
96
+ }
97
+ else {
98
+ const withoutTrailingAsterisk = license.endsWith("*")
99
+ ? license.slice(0, -1)
100
+ : license;
101
+ const transformed = transformBSD(withoutTrailingAsterisk);
102
+ const correctedSpdx = correctSpdxExpression(transformed);
103
+ if (invalidSPDXLicenses.indexOf(transformed) >= 0 ||
104
+ (correctedSpdx &&
105
+ validSPDXLicenses.length > 0 &&
106
+ spdxExpressionSatisfies(correctedSpdx, validSPDXLicenses))) {
107
+ match = true;
108
+ }
109
+ }
110
+ });
111
+ return { hasUnknownLicense, match };
112
+ }
@@ -0,0 +1,6 @@
1
+ import type { ModuleInfos } from "../types.js";
2
+ export declare const getOptionArray: (option: string | string[] | undefined) => string[] | false;
3
+ export declare const includePackages: (whitelist: string[], packages: ModuleInfos) => ModuleInfos;
4
+ export declare const excludePackages: (blacklist: string[], packages: ModuleInfos) => ModuleInfos;
5
+ export declare const excludePackagesStartingWith: (blacklist: string[], packages: ModuleInfos) => ModuleInfos;
6
+ export declare const excludePrivatePackages: (packages: ModuleInfos) => ModuleInfos;
@@ -0,0 +1,56 @@
1
+ export const getOptionArray = (option) => {
2
+ if (Array.isArray(option)) {
3
+ return option;
4
+ }
5
+ if (typeof option === "string") {
6
+ return option.split(";");
7
+ }
8
+ return false;
9
+ };
10
+ const matchesPackageSelector = (packageName, selector) => packageName.startsWith(selector.lastIndexOf("@") > 0 ? selector : `${selector}@`);
11
+ export const includePackages = (whitelist, packages) => {
12
+ const resultJson = {};
13
+ Object.keys(packages).forEach((packageName) => {
14
+ // Whitelist packages by declaring:
15
+ // 1. the package full name. Ex: `react` (we suffix an '@' to ensure we don't match packages like `react-native`)
16
+ // 2. the package full name and the major version. Ex: `react@16`
17
+ // 3. the package full name and full version. Ex: `react@16.0.2`
18
+ if (whitelist.findIndex((whitelistPackage) => matchesPackageSelector(packageName, whitelistPackage)) !== -1) {
19
+ resultJson[packageName] = packages[packageName];
20
+ }
21
+ });
22
+ return resultJson;
23
+ };
24
+ export const excludePackages = (blacklist, packages) => {
25
+ const resultJson = {};
26
+ Object.keys(packages).forEach((packageName) => {
27
+ // Blacklist packages by declaring:
28
+ // 1. the package full name. Ex: `react` (we suffix an '@' to ensure we don't match packages like `react-native`)
29
+ // 2. the package full name and the major version. Ex: `react@16`
30
+ // 3. the package full name and full version. Ex: `react@16.0.2`
31
+ if (blacklist.findIndex((blacklistPackage) => matchesPackageSelector(packageName, blacklistPackage)) === -1) {
32
+ resultJson[packageName] = packages[packageName];
33
+ }
34
+ });
35
+ return resultJson;
36
+ };
37
+ export const excludePackagesStartingWith = (blacklist, packages) => {
38
+ const resultJson = { ...packages };
39
+ for (const packageName in resultJson) {
40
+ for (const denyPrefix of blacklist) {
41
+ if (packageName.startsWith(denyPrefix)) {
42
+ delete resultJson[packageName];
43
+ }
44
+ }
45
+ }
46
+ return resultJson;
47
+ };
48
+ export const excludePrivatePackages = (packages) => {
49
+ const resultJson = { ...packages };
50
+ Object.keys(resultJson).forEach((packageName) => {
51
+ if (resultJson[packageName]?.private) {
52
+ delete resultJson[packageName];
53
+ }
54
+ });
55
+ return resultJson;
56
+ };
@@ -0,0 +1 @@
1
+ export declare function createDebugLogger(namespace: string, output?: (...args: unknown[]) => void): (...args: unknown[]) => void;
@@ -0,0 +1,25 @@
1
+ import { format } from "node:util";
2
+ function namespaceMatches(pattern, namespace) {
3
+ const expression = new RegExp(`^${pattern.split("*").map(escapeRegExp).join(".*?")}$`);
4
+ return expression.test(namespace);
5
+ }
6
+ function escapeRegExp(value) {
7
+ return value.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
8
+ }
9
+ function isEnabled(namespace) {
10
+ const patterns = (process.env.DEBUG ?? "").split(/[\s,]+/).filter(Boolean);
11
+ let enabled = false;
12
+ for (const pattern of patterns) {
13
+ const excluded = pattern.startsWith("-");
14
+ if (namespaceMatches(excluded ? pattern.slice(1) : pattern, namespace)) {
15
+ enabled = !excluded;
16
+ }
17
+ }
18
+ return enabled;
19
+ }
20
+ export function createDebugLogger(namespace, output = console.error) {
21
+ return (...args) => {
22
+ if (isEnabled(namespace))
23
+ output(`${namespace} ${format(...args)}`);
24
+ };
25
+ }
@@ -0,0 +1 @@
1
+ export declare function firstDefined<T>(...values: Array<T | undefined>): T | undefined;
@@ -0,0 +1,7 @@
1
+ export function firstDefined(...values) {
2
+ for (const value of values) {
3
+ if (typeof value !== "undefined") {
4
+ return value;
5
+ }
6
+ }
7
+ }
@@ -0,0 +1 @@
1
+ export declare function semverSatisfies(versionString: string, range: string): boolean;
@@ -0,0 +1,269 @@
1
+ const FULL_VERSION = /^=?v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*)(?:\.(?:0|[1-9]\d*|\d*[A-Za-z-][0-9A-Za-z-]*))*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
2
+ const NUMERIC_IDENTIFIER = /^(0|[1-9]\d*)$/;
3
+ function parsePrerelease(value) {
4
+ if (!value)
5
+ return [];
6
+ return value.split(".").map((identifier) => identifier);
7
+ }
8
+ function parseVersion(value) {
9
+ const match = FULL_VERSION.exec(value);
10
+ if (!match)
11
+ return null;
12
+ return {
13
+ major: match[1],
14
+ minor: match[2],
15
+ patch: match[3],
16
+ prerelease: parsePrerelease(match[4]),
17
+ };
18
+ }
19
+ function compareNumeric(left, right) {
20
+ if (left.length !== right.length)
21
+ return left.length < right.length ? -1 : 1;
22
+ return left === right ? 0 : left < right ? -1 : 1;
23
+ }
24
+ function comparePrerelease(left, right) {
25
+ if (left.length === 0 || right.length === 0) {
26
+ return left.length === right.length ? 0 : left.length === 0 ? 1 : -1;
27
+ }
28
+ const length = Math.min(left.length, right.length);
29
+ for (let index = 0; index < length; index += 1) {
30
+ const leftPart = left[index];
31
+ const rightPart = right[index];
32
+ if (leftPart === rightPart)
33
+ continue;
34
+ const leftNumeric = NUMERIC_IDENTIFIER.test(leftPart);
35
+ const rightNumeric = NUMERIC_IDENTIFIER.test(rightPart);
36
+ if (leftNumeric && !rightNumeric)
37
+ return -1;
38
+ if (!leftNumeric && rightNumeric)
39
+ return 1;
40
+ if (leftNumeric && rightNumeric)
41
+ return compareNumeric(leftPart, rightPart);
42
+ return String(leftPart) < String(rightPart) ? -1 : 1;
43
+ }
44
+ return left.length === right.length ? 0 : left.length < right.length ? -1 : 1;
45
+ }
46
+ function compareVersions(left, right) {
47
+ for (const component of ["major", "minor", "patch"]) {
48
+ const compared = compareNumeric(left[component], right[component]);
49
+ if (compared !== 0)
50
+ return compared;
51
+ }
52
+ return comparePrerelease(left.prerelease, right.prerelease);
53
+ }
54
+ function parsePartialVersion(source) {
55
+ const value = source.replace(/^=?v?/, "");
56
+ const [withoutBuild] = value.split("+");
57
+ const hyphenIndex = withoutBuild.indexOf("-");
58
+ const core = hyphenIndex < 0 ? withoutBuild : withoutBuild.slice(0, hyphenIndex);
59
+ const prereleaseText = hyphenIndex < 0 ? undefined : withoutBuild.slice(hyphenIndex + 1);
60
+ const parts = core.split(".");
61
+ if (parts.length > 3 || parts.some((part) => part.length === 0)) {
62
+ throw new Error(`Invalid semantic version range token: ${source}`);
63
+ }
64
+ const values = [];
65
+ let wildcardSeen = false;
66
+ for (const part of parts) {
67
+ if (/^(?:x|\*)$/i.test(part)) {
68
+ wildcardSeen = true;
69
+ values.push(null);
70
+ }
71
+ else if (/^(?:0|[1-9]\d*)$/.test(part) && !wildcardSeen) {
72
+ values.push(part);
73
+ }
74
+ else {
75
+ throw new Error(`Invalid semantic version range token: ${source}`);
76
+ }
77
+ }
78
+ if (wildcardSeen && prereleaseText !== undefined) {
79
+ throw new Error(`A wildcard range cannot include a prerelease: ${source}`);
80
+ }
81
+ if (prereleaseText !== undefined &&
82
+ values.filter((part) => part !== null).length !== 3) {
83
+ throw new Error(`A prerelease requires a complete version: ${source}`);
84
+ }
85
+ const specified = values.filter((part) => part !== null).length;
86
+ if (specified !== values.length) {
87
+ for (let index = values.findIndex((part) => part === null); index < values.length; index += 1) {
88
+ if (values[index] !== null) {
89
+ throw new Error(`Invalid numeric component after wildcard: ${source}`);
90
+ }
91
+ }
92
+ }
93
+ const prerelease = prereleaseText === undefined ? [] : parsePrerelease(prereleaseText);
94
+ if (prereleaseText !== undefined && prerelease.length === 0) {
95
+ throw new Error(`Invalid prerelease in semantic version range: ${source}`);
96
+ }
97
+ return {
98
+ major: values["0"] ?? null,
99
+ minor: values[1] ?? null,
100
+ patch: values[2] ?? null,
101
+ prerelease,
102
+ specified,
103
+ wildcard: specified < 3,
104
+ };
105
+ }
106
+ function makeVersion(major, minor = "0", patch = "0", prerelease = []) {
107
+ return { major, minor, patch, prerelease };
108
+ }
109
+ function increment(value) {
110
+ return (BigInt(value) + 1n).toString();
111
+ }
112
+ function lowerBound(partial) {
113
+ if (partial.major === null)
114
+ return null;
115
+ return makeVersion(partial.major, partial.minor ?? "0", partial.patch ?? "0", partial.prerelease);
116
+ }
117
+ function upperBound(partial) {
118
+ if (partial.major === null)
119
+ return null;
120
+ if (partial.minor === null)
121
+ return makeVersion(increment(partial.major), "0", "0", ["0"]);
122
+ if (partial.patch === null) {
123
+ return makeVersion(partial.major, increment(partial.minor), "0", ["0"]);
124
+ }
125
+ return makeVersion(partial.major, partial.minor, increment(partial.patch), [
126
+ "0",
127
+ ]);
128
+ }
129
+ function comparator(operator, version) {
130
+ return version ? [{ operator, version }] : [];
131
+ }
132
+ function expandPartial(partial, operator) {
133
+ const lower = lowerBound(partial);
134
+ const upper = upperBound(partial);
135
+ if (operator === "^" || operator === "~" || operator === "~>") {
136
+ if (!lower)
137
+ return [];
138
+ let ceiling;
139
+ if (operator === "~" || operator === "~>") {
140
+ ceiling =
141
+ partial.minor === null
142
+ ? makeVersion(increment(partial.major), "0", "0", ["0"])
143
+ : makeVersion(partial.major, increment(partial.minor), "0", ["0"]);
144
+ }
145
+ else if (partial.major !== "0") {
146
+ ceiling = makeVersion(increment(partial.major), "0", "0", [
147
+ "0",
148
+ ]);
149
+ }
150
+ else if (partial.minor === null) {
151
+ ceiling = makeVersion("1", "0", "0", ["0"]);
152
+ }
153
+ else if (partial.minor !== "0") {
154
+ ceiling = makeVersion("0", increment(partial.minor), "0", ["0"]);
155
+ }
156
+ else if (partial.patch === null) {
157
+ ceiling = makeVersion("0", "1", "0", ["0"]);
158
+ }
159
+ else {
160
+ ceiling = makeVersion("0", "0", increment(partial.patch), ["0"]);
161
+ }
162
+ return [...comparator(">=", lower), ...comparator("<", ceiling)];
163
+ }
164
+ if (!lower) {
165
+ if (operator === "" ||
166
+ operator === "=" ||
167
+ operator === ">=" ||
168
+ operator === "<=")
169
+ return [];
170
+ throw new Error(`Invalid wildcard range operator: ${operator}`);
171
+ }
172
+ if (operator === "" || operator === "=") {
173
+ if (!partial.wildcard)
174
+ return comparator("=", lower);
175
+ return [...comparator(">=", lower), ...comparator("<", upper)];
176
+ }
177
+ if (!partial.wildcard) {
178
+ return comparator(operator, lower);
179
+ }
180
+ switch (operator) {
181
+ case ">=":
182
+ return comparator(">=", lower);
183
+ case ">":
184
+ return comparator(">=", upper);
185
+ case "<":
186
+ return comparator("<", lower);
187
+ case "<=":
188
+ return comparator("<", upper);
189
+ default:
190
+ throw new Error(`Invalid semantic version range operator: ${operator}`);
191
+ }
192
+ }
193
+ function tokenizeComparators(range) {
194
+ const result = [];
195
+ let offset = 0;
196
+ while (offset < range.length) {
197
+ while (/\s/.test(range[offset] ?? ""))
198
+ offset += 1;
199
+ if (offset >= range.length)
200
+ break;
201
+ const operatorMatch = /^(~>|<=|>=|<|>|=|~|\^)/.exec(range.slice(offset));
202
+ const operator = operatorMatch?.[0] ?? "";
203
+ offset += operator.length;
204
+ while (/\s/.test(range[offset] ?? ""))
205
+ offset += 1;
206
+ const start = offset;
207
+ while (offset < range.length && !/\s/.test(range[offset] ?? ""))
208
+ offset += 1;
209
+ if (start === offset)
210
+ throw new Error("Missing version after range operator");
211
+ result.push({ operator, version: range.slice(start, offset) });
212
+ }
213
+ return result;
214
+ }
215
+ function expandHyphenRange(leftSource, rightSource) {
216
+ const left = parsePartialVersion(leftSource);
217
+ const right = parsePartialVersion(rightSource);
218
+ return [
219
+ ...comparator(">=", lowerBound(left)),
220
+ ...(right.wildcard
221
+ ? comparator("<", upperBound(right))
222
+ : comparator("<=", lowerBound(right))),
223
+ ];
224
+ }
225
+ function expandComparatorSet(source) {
226
+ const hyphen = /^(\S+)\s+-\s+(\S+)$/.exec(source.trim());
227
+ if (hyphen)
228
+ return expandHyphenRange(hyphen[1], hyphen[2]);
229
+ return tokenizeComparators(source).flatMap(({ operator, version }) => expandPartial(parsePartialVersion(version), operator));
230
+ }
231
+ function comparatorMatches(version, test) {
232
+ const compared = compareVersions(version, test.version);
233
+ switch (test.operator) {
234
+ case "=":
235
+ return compared === 0;
236
+ case ">":
237
+ return compared > 0;
238
+ case ">=":
239
+ return compared >= 0;
240
+ case "<":
241
+ return compared < 0;
242
+ case "<=":
243
+ return compared <= 0;
244
+ }
245
+ }
246
+ function prereleaseAllowed(version, comparators) {
247
+ if (version.prerelease.length === 0)
248
+ return true;
249
+ return comparators.some(({ version: comparatorVersion }) => comparatorVersion.prerelease.length > 0 &&
250
+ comparatorVersion.major === version.major &&
251
+ comparatorVersion.minor === version.minor &&
252
+ comparatorVersion.patch === version.patch);
253
+ }
254
+ export function semverSatisfies(versionString, range) {
255
+ const version = parseVersion(versionString);
256
+ if (!version)
257
+ return false;
258
+ const alternatives = range.split("||");
259
+ if (alternatives.some((alternative) => alternative.trim() === "")) {
260
+ if (alternatives.length === 1)
261
+ return true;
262
+ throw new Error(`Invalid semantic version range: ${range}`);
263
+ }
264
+ return alternatives.some((alternative) => {
265
+ const comparators = expandComparatorSet(alternative);
266
+ return (prereleaseAllowed(version, comparators) &&
267
+ comparators.every((test) => comparatorMatches(version, test)));
268
+ });
269
+ }