diff --git a/README.md b/README.md index 11ba8b3..77ad0c6 100644 --- a/README.md +++ b/README.md @@ -80,7 +80,7 @@ And it returns a promise that resolves to: type DepngnReturn = Record; interface CompatData { - compatible: boolean | undefined; + compatible: boolean | 'invalid' | undefined; range: string; } ``` @@ -95,6 +95,15 @@ const generateReport = async () => { }; ``` +There's also a chance there *is* an `engines` field specified in the package, but the range is invalid in some way. Since RegEx for SemVer can be tricky, we return the folling, if that's the case: + +```javascript +{ + compatible: 'invalid', + range: '1 .2 . 0not-a-valid-range' +} +``` + ## Supported Package Managers For now, this package supports `npm` and `yarn`. If you want support for diff --git a/package-lock.json b/package-lock.json index 3419c27..ed062d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,7 +6,7 @@ "packages": { "": { "name": "depngn", - "version": "0.2.0", + "version": "0.3.0", "license": "MIT", "dependencies": { "arg": "^5.0.2", diff --git a/src/cli/reporter/html.ts b/src/cli/reporter/html.ts index dc243ea..2212f27 100644 --- a/src/cli/reporter/html.ts +++ b/src/cli/reporter/html.ts @@ -1,11 +1,15 @@ import { writeFile } from 'fs/promises'; import { CompatData } from '../../types'; -export async function createHtml(compatData: Record, version: string, path: string = 'compat.html') { +export async function createHtml( + compatData: Record, + version: string, + path: string = 'compat.html' +) { const compatDataKeys = Object.keys(compatData); - const classGreen = "green"; - const classRed = "red"; - const classYellow = "yellow"; + const classGreen = 'green'; + const classRed = 'red'; + const classYellow = 'yellow'; const style = ` h1{ @@ -32,21 +36,26 @@ export async function createHtml(compatData: Record, version } .${classYellow}{ color: #ce8d02; - }` + }`; const tableData = compatDataKeys .map((key) => { const compatible = compatData[key].compatible; - const compatibleClass = compatible === undefined ? classYellow : compatible ? classGreen : classRed; + const compatibleClass = + compatible === undefined || compatible === 'invalid' + ? classYellow + : compatible + ? classGreen + : classRed; return ` ${key} ${compatible} ${compatData[key].range} - ` + `; }) - .join(""); + .join(''); const out = ` @@ -67,8 +76,8 @@ export async function createHtml(compatData: Record, version ${tableData} - ` + `; await writeFile(path, out); console.log(`File generated at ${path}`); -} \ No newline at end of file +} diff --git a/src/cli/reporter/table.ts b/src/cli/reporter/table.ts index e355239..86bfd1d 100644 --- a/src/cli/reporter/table.ts +++ b/src/cli/reporter/table.ts @@ -21,8 +21,8 @@ export function createTable(compatData: Record, version: str console.log(table(out, config)); } -function toColorString(value: boolean | undefined) { - if (value === undefined) return yellow('undefined'); +function toColorString(value: boolean | string | undefined) { + if (value === undefined || value === 'invalid') return yellow(`${value}`); const outputColor = value ? green : red; return outputColor(value.toString()); } diff --git a/src/queries/getPackageData.ts b/src/queries/getPackageData.ts index 8cba77b..8c1f81a 100644 --- a/src/queries/getPackageData.ts +++ b/src/queries/getPackageData.ts @@ -1,7 +1,7 @@ import { satisfies } from 'compare-versions'; -import { EnginesData } from '../types'; +import { CompatData, EnginesData } from '../types'; -export function getPackageData(dep: EnginesData, version: string) { +export function getPackageData(dep: EnginesData, version: string): CompatData { const range = dep.range ? dep.range : 'n/a'; const compatible = isCompatible(version, dep.range); return { compatible, range }; @@ -13,14 +13,38 @@ function isCompatible(nodeVersion: string, depRange: string) { // if a dependency has `*` for the node version, it's always compatible if (['x', '*'].includes(depRange)) return true; - let compatible; - - const logicalOrRegEx = /\|\|/; - if (depRange && logicalOrRegEx.test(depRange)) { - const rangeArray = depRange.split('||').map((range) => range.replaceAll(' ', '')); - compatible = rangeArray.some((range) => satisfies(nodeVersion, range)); - } else { - compatible = satisfies(nodeVersion, depRange.replaceAll(' ', '')); + try { + return depRange + .split('||') + .map((range) => removeWhitespace(range)) + .some((range) => safeSatisfies(nodeVersion, range)); + } catch (error) { + if ((error as Error).message.match(/Invalid argument not valid semver/)) { + return 'invalid'; + } + throw error; } - return compatible; +} + +// accounts for `AND` ranges -- ie, `'>=1.2.9 <2.0.0'` +function safeSatisfies(nodeVersion: string, range: string) { + return ( + range + .split(' ') + // filter out any whitespace we may have missed with the RegEx -- ie, `'>4 <8'` + .filter((r) => !!r) + .every((r) => satisfies(nodeVersion, r)) + ); +} + +// trims leading and trailing whitespace and whitespace +// between the comparator operators and the actual version number +// version number. ie, ' > = 12.0.0 ' becomes '>=12.0.0' +function removeWhitespace(range: string) { + const comparatorWhitespace = /((?<=(<|>))(\s+)(?=(=)))/g; + const comparatorAndVersionWhiteSpace = /(?<=(<|>|=|\^|~))(\s+)(?=\d)/g; + return range + .trim() + .replace(comparatorWhitespace, '') + .replace(comparatorAndVersionWhiteSpace, ''); } diff --git a/src/types.ts b/src/types.ts index ab2bb87..ed7238e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -6,7 +6,7 @@ export interface EnginesData { export type EnginesDataArray = Array; export interface CompatData { - compatible: boolean | undefined; + compatible: boolean | 'invalid' | undefined; range: string; }