Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[bugfix]: removing whitespace from malformed version ranges #37

Merged
merged 11 commits into from
Mar 3, 2023
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ And it returns a promise that resolves to:
type DepngnReturn = Record<string, CompatData>;

interface CompatData {
compatible: boolean | undefined;
compatible: boolean | 'invalid' | undefined;
range: string;
}
```
Expand All @@ -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
Expand Down
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 19 additions & 10 deletions src/cli/reporter/html.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import { writeFile } from 'fs/promises';
import { CompatData } from '../../types';

export async function createHtml(compatData: Record<string, CompatData>, version: string, path: string = 'compat.html') {
export async function createHtml(
compatData: Record<string, CompatData>,
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{
Expand All @@ -32,21 +36,26 @@ export async function createHtml(compatData: Record<string, CompatData>, 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 `
<tr>
<td>${key}</td>
<td class="${compatibleClass}">${compatible}</td>
<td>${compatData[key].range}</td>
</tr>
`
`;
})
.join("");
.join('');

const out = `<!DOCTYPE html>
<html lang="en">
Expand All @@ -67,8 +76,8 @@ export async function createHtml(compatData: Record<string, CompatData>, version
${tableData}
</table>
</body>
</html>`
</html>`;

await writeFile(path, out);
console.log(`File generated at ${path}`);
}
}
4 changes: 2 additions & 2 deletions src/cli/reporter/table.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ export function createTable(compatData: Record<string, CompatData>, 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());
}
46 changes: 35 additions & 11 deletions src/queries/getPackageData.ts
Original file line number Diff line number Diff line change
@@ -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 };
Expand All @@ -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, '');
}
2 changes: 1 addition & 1 deletion src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export interface EnginesData {
export type EnginesDataArray = Array<EnginesData>;

export interface CompatData {
compatible: boolean | undefined;
compatible: boolean | 'invalid' | undefined;
range: string;
}

Expand Down