Skip to content

Commit

Permalink
fix(satisfies): support leading zero versions and pre-releases
Browse files Browse the repository at this point in the history
  • Loading branch information
omichelsen committed Dec 13, 2022
1 parent 35bf689 commit 29250c8
Show file tree
Hide file tree
Showing 2 changed files with 110 additions and 56 deletions.
31 changes: 24 additions & 7 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,14 +100,31 @@ export const satisfies = (version: string, range: string) => {
return compare(version, range, op as CompareOperator);

// else range of either "~" or "^" is assumed
const [v1, v2, v3] = validateAndParse(version);
const [r1, r2, r3] = validateAndParse(range);
if (compareStrings(v1, r1) !== 0) return false;
if (op === '^') {
return compareSegments([v2, v3], [r2, r3]) >= 0;
const [v1, v2, v3, , vp] = validateAndParse(version);
const [r1, r2, r3, , rp] = validateAndParse(range);
const v = [v1, v2, v3];
const r = [r1, r2 ?? 'x', r3 ?? 'x'];

// validate pre-release
if (rp) {
if (!vp) return false;
if (compareSegments(v, r) !== 0) return false;
if (compareSegments(vp.split('.'), rp.split('.')) === -1) return false;
}
if (compareStrings(v2, r2) !== 0) return false;
return compareStrings(v3, r3) >= 0;

// first non-zero number
const nonZero = r.findIndex((v) => v !== '0') + 1;

// pointer to where segments can be >=
const i = op === '~' ? 2 : nonZero > 1 ? nonZero : 1;

// all before pointer has to equal
if (compareSegments(v.slice(0, i), r.slice(0, i)) !== 0) return false;

// after pointer must be >=
if (compareSegments(v.slice(i), r.slice(i)) === -1) return false;

return true;
};

const semver =
Expand Down
135 changes: 86 additions & 49 deletions test/satisfies.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,37 @@
import assert from 'assert';
import { satisfies } from '../src/index';

describe('validate versions', () => {
(
[
describe('satisfies versions', () => {
const runTests = (dataSet: Array<[string, string, boolean]>) => {
dataSet.forEach(([v, m, expected]) => {
it(`${v} ${expected ? 'satisfies' : 'violates'} ${m}`, () => {
assert.strictEqual(satisfies(v, m), expected);
});
});
};

describe('tilde - https://docs.npmjs.com/cli/v6/using-npm/semver#tilde-ranges-123-12-1', () => {
runTests([
['1.0.0', '~1.0.1', false],
['1.0.1', '~1.0.1', true],
['1.2.5', '~1.2.3', true],
['1.3.0', '~1.2.3', false],
['1.2.5', '~1.2', true], // (Same as 1.2.x)
['1.3.0', '~1.2', false], // (Same as 1.2.x)
['1.0.0', '~1', true], // (Same as 1.x)
['2.0.0', '~1', false], // (Same as 1.x)
['0.2.5', '~0.2.3', true],
['0.3.0', '~0.2.3', false],
['0.2.5', '~0.2', true], // (Same as 0.2.x)
['0.3.0', '~0.2', false], // (Same as 0.2.x)
['0.0.0', '~0', true], // (Same as 0.x)
['0.1.2', '~0', true], // (Same as 0.x)
['1.0.0', '~0', false], // (Same as 0.x)
]);
});

describe('caret - https://docs.npmjs.com/cli/v6/using-npm/semver#caret-ranges-123-025-004', () => {
runTests([
['1.0.0', '^1', true],
['1.0.0', '^1.0', true],
['1.0.0', '^1.0.0', true],
Expand All @@ -12,51 +40,60 @@ describe('validate versions', () => {
['2.0.0', '^1.0.0', false],
['1.0.0', '^1.2.0', false],
['1.0.1', '^1.2.0', false],
['1.3.4', '^1.2.5', true],
['1.0.0', '~1.2.0', false],
['1.0.0', '~1.0.1', false],
['1.0.1', '~1.0.0', true],
['1.3.4', '~1.2.5', false],
['1.0.0', '~1.0.0', true],
['1.0.0-alpha.1', '^1.0.0', true],
['1.1.0-alpha.1', '^1.0.0', true],
['1.2.0', '>1.0.0', true],
['1.2.0', '<1.0.0', false],
['1.0.0', '<=1.0.0', true],
['1.0.0', '<=2.0.0', true],
['1.0.1', '1.0.0', false],
['1.0.0', '1.0.0', true],
['10.1.8', '>10.0.4', true],
['10.1.8', '>=10.0.4', true],
['10.0.1', '=10.0.1', true],
['10.0.1', '=10.1.*', false],
['10.1.1', '<10.2.2', true],
['10.1.1', '<10.0.2', false],
['10.1.1', '<=10.2.2', true],
['10.1.1', '<=10.1.1', true],
['10.1.1', '<=10.0.2', false],
['10.1.1', '>=10.0.2', true],
['10.1.1', '>=10.1.1', true],
['10.1.1', '>=10.2.2', false],
['11.0.0', '>=10.1.1', true],
['3', '3.x.x', true],
['3.3', '3.x.x', true],
['3.3.3', '3.x.x', true],
['3.x.x', '3.3.3', true],
['3.3.3', '3.X.X', true],
['3.3.3', '3.3.x', true],
['3.3.3', '3.*.*', true],
['3.3.3', '3.3.*', true],
['3.0.3', '3.0.*', true],
['1.1.0', '1.2.x', false],
['1.1.0', '2.x.x', false],
['2.0.0', '<2.x.x', false],
['2.0.0', '<=2.x.x', true],
['2.0.0', '>2.x.x', false],
] as const
).forEach(([v, m, expected]) => {
it(`${v} satisfies ${m}`, () => {
assert.equal(satisfies(v, m), expected);
});
['1.3.4', '^1.2.3', true],
['2.0.0', '^1.2.3', false],
['0.3.0', '^0.2.3', false],
['0.0.4', '^0.0.3', false],
]);
});

runTests([
['1.2.0', '>1.0.0', true],
['1.2.0', '<1.0.0', false],
['1.0.0', '<=1.0.0', true],
['1.0.0', '<=2.0.0', true],
['1.0.1', '1.0.0', false],
['1.0.0', '1.0.0', true],
['10.1.8', '>10.0.4', true],
['10.1.8', '>=10.0.4', true],
['10.0.1', '=10.0.1', true],
['10.0.1', '=10.1.*', false],
['10.1.1', '<10.2.2', true],
['10.1.1', '<10.0.2', false],
['10.1.1', '<=10.2.2', true],
['10.1.1', '<=10.1.1', true],
['10.1.1', '<=10.0.2', false],
['10.1.1', '>=10.0.2', true],
['10.1.1', '>=10.1.1', true],
['10.1.1', '>=10.2.2', false],
['11.0.0', '>=10.1.1', true],
['3', '3.x.x', true],
['3.3', '3.x.x', true],
['3.3.3', '3.x.x', true],
['3.x.x', '3.3.3', true],
['3.3.3', '3.X.X', true],
['3.3.3', '3.3.x', true],
['3.3.3', '3.*.*', true],
['3.3.3', '3.3.*', true],
['3.0.3', '3.0.*', true],
['1.1.0', '1.2.x', false],
['1.1.0', '2.x.x', false],
['2.0.0', '<2.x.x', false],
['2.0.0', '<=2.x.x', true],
['2.0.0', '>2.x.x', false],
]);

describe('pre-release versions - https://semver.org/#spec-item-9', () => {
runTests([
['1.2.3-beta.4', '~1.2.3-beta.2', true],
['1.2.3-beta.1', '~1.2.3-beta.2', false],
['1.2.4-beta.2', '~1.2.3-beta.2', false],
['1.2.3-beta.4', '^1.2.3-beta.2', true],
['1.2.4-beta.2', '^1.2.3-beta.2', false],
['2.0.0', '^1.2.3-beta.2', false],
['0.0.3-beta.2', '^0.0.3-beta', true],
['0.0.3-pr.2', '^0.0.3-beta', true],
['0.0.4', '^0.0.3-beta', false],
]);
});
});

0 comments on commit 29250c8

Please sign in to comment.