Skip to content

Commit

Permalink
feat(esm): add ES6 module version of script
Browse files Browse the repository at this point in the history
  • Loading branch information
omichelsen committed Oct 30, 2021
1 parent 2aa74e8 commit 326f88f
Show file tree
Hide file tree
Showing 10 changed files with 370 additions and 2,395 deletions.
1 change: 1 addition & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ root = true
[*]
indent_style = spaces
indent_size = 2
quote_type = single
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ arch:
language: node_js
node_js:
- "node"
after_script: "npm install coveralls && nyc report --reporter=text-lcov | coveralls"
after_script: "npm install coveralls && c8 report --reporter=text-lcov | coveralls"
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2015-2017 Ole Michelsen
Copyright (c) 2015-2021 Ole Michelsen

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
61 changes: 30 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@
[![Coverage Status](https://coveralls.io/repos/omichelsen/compare-versions/badge.svg?branch=master&service=github)](https://coveralls.io/github/omichelsen/compare-versions?branch=master)
[![npm bundle size (minified + gzip)](https://img.shields.io/bundlephobia/minzip/compare-versions.svg)](https://bundlephobia.com/result?p=compare-versions)

Compare [semver](https://semver.org/) version strings to find greater, equal or lesser. Runs in the browser as well as Node.js/React Native etc. Has no dependencies and is tiny (~630 bytes gzipped).
Compare [semver](https://semver.org/) version strings to find greater, equal or lesser. Runs in the browser as well as Node.js/React Native etc. Has no dependencies and is tiny.

This library supports the full semver specification, including comparing versions with different number of digits like `1.0.0`, `1.0`, `1`, and pre-release versions like `1.0.0-alpha`. Additionally supports the following variations:
Supports the full semver specification including versions with different number of digits like `1.0.0`, `1.0`, `1` and pre-releases like `1.0.0-alpha`. Additionally supports the following variations:

- Supports wildcards for minor and patch version like `1.0.x` or `1.0.*`.
- Supports [Chromium version numbers](https://www.chromium.org/developers/version-numbers) with 4 parts, e.g. version `25.0.1364.126`.
- Wildcards for minor and patch version like `1.0.x` or `1.0.*`.
- [Chromium version numbers](https://www.chromium.org/developers/version-numbers) with 4 parts, e.g. version `25.0.1364.126`.
- Any leading `v` is ignored, e.g. `v1.0` is interpreted as `1.0`.
- Leading zero is ignored, e.g. `1.01.1` is interpreted as `1.1.1`.

Expand All @@ -19,43 +19,37 @@ This library supports the full semver specification, including comparing version
$ npm install compare-versions
```

Note: Starting from v4 this library includes a ESM version which will automatically be selected by your bundler (webpack, parcel etc). The old CJS version is `index.js` and the new ESM version is `index.mjs`.

## Usage

### Import
Will return `1` if first version is greater, `0` if versions are equal, and `-1` if the second version is greater:

```javascript
// ES6/TypeScript
import compareVersions from 'compare-versions';

// Node
var compareVersions = require('compare-versions');
```

### Compare

```javascript
compareVersions('10.1.8', '10.0.4'); // 1
compareVersions('10.0.1', '10.0.1'); // 0
compareVersions('10.1.1', '10.2.2'); // -1
compareVersions('11.1.1', '10.0.0'); // 1
compareVersions('10.0.0', '10.0.0'); // 0
compareVersions('10.0.0', '11.1.1'); // -1
```

Can also be used for sorting:

```javascript
var versions = [
const versions = [
'1.5.19',
'1.2.3',
'1.5.5'
]
var sorted = versions.sort(compareVersions);
const sorted = versions.sort(compareVersions);
/*
[
'1.2.3',
'1.5.5',
'1.5.19'
]
*/
var sortDescending = versions.sort(compareVersions).reverse();
const sortDescending = versions.sort(compareVersions).reverse();
/*
[
'1.5.19'
Expand All @@ -67,25 +61,28 @@ var sortDescending = versions.sort(compareVersions).reverse();

### "Human Readable" Compare

The normal compare function doesn't return a self-explanatory value (using `1`, `0` and `-1`).
This version returns the boolean which fulfills the specified operator.
The alternative `compare` function accepts an operator which will be more familiar to humans:

```js
compareVersions.compare('10.1.8', '10.0.4', '>'); // return true
compareVersions.compare('10.0.1', '10.0.1', '='); // return true
compareVersions.compare('10.1.1', '10.2.2', '<'); // return true
compareVersions.compare('10.1.1', '10.2.2', '<='); // return true
compareVersions.compare('10.1.1', '10.2.2', '>='); // return false
import { compare } from 'compare-versions';

compare('10.1.8', '10.0.4', '>'); // true
compare('10.0.1', '10.0.1', '='); // true
compare('10.1.1', '10.2.2', '<'); // true
compare('10.1.1', '10.2.2', '<='); // true
compare('10.1.1', '10.2.2', '>='); // false
```

### Validate version numbers

Applies the same ruleset as used before comparing version numbers and returns a boolean:
Applies the same ruleset used comparing version numbers and returns a boolean:

```javascript
compareVersions.validate('1.0.0-rc.1'); // return true
compareVersions.validate('1.0-rc.1'); // return false
compareVersions.validate('foo'); // return false
import { validate } from 'compare-versions';

validate('1.0.0-rc.1'); // true
validate('1.0-rc.1'); // false
validate('foo'); // false
```

### Browser
Expand All @@ -95,6 +92,8 @@ If included directly in the browser, `compareVersions()` is available on the glo
```html
<script src="compare-versions/index.js"></script>
<script>
window.compareVersions('10.0.0', '10.1.0');
window.compareVersions('11.0.0', '10.0.0');
window.compareVersions.compare('11.0.0', '10.0.0', '>');
window.compareVersions.validate('11.0.0');
</script>
```
97 changes: 97 additions & 0 deletions index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
export default function compareVersions(v1, v2) {
// validate input and split into segments
const n1 = validateAndParseVersion(v1);
const n2 = validateAndParseVersion(v2);

// pop off the patch
const p1 = n1.pop();
const p2 = n2.pop();

// validate numbers
const r = compareSegments(n1, n2);
if (r !== 0) return r;

// validate pre-release
if (p1 && p2) {
return compareSegments(p1.split('.'), p2.split('.'));
} else if (p1 || p2) {
return p1 ? -1 : 1;
}

return 0;
}

export const validate = (version) =>
typeof version === 'string' && semver.test(version);

export const compare = (v1, v2, operator) => {
// validate input operator
assertValidOperator(operator);

// since result of compareVersions can only be -1 or 0 or 1
// a simple map can be used to replace switch
const res = compareVersions(v1, v2);

return operatorResMap[operator].includes(res);
};

const semver =
/^v?(\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+)(?:\.([x*]|\d+))?(?:-([\da-z\-]+(?:\.[\da-z\-]+)*))?(?:\+[\da-z\-]+(?:\.[\da-z\-]+)*)?)?)?$/i;

const validateAndParseVersion = (v) => {
if (typeof v !== 'string') {
throw new TypeError('Invalid argument expected string');
}
const match = v.match(semver);
if (!match) {
throw new Error(`Invalid argument not valid semver ('${v}' received)`);
}
match.shift();
return match;
};

const tryParse = (v) => {
const n = parseInt(v, 10);
return isNaN(n) ? v : n;
};

const forceType = (a, b) =>
typeof a !== typeof b ? [String(a), String(b)] : [a, b];

const compareStrings = (a, b) => {
const [ap, bp] = forceType(tryParse(a), tryParse(b));
if (ap > bp) return 1;
if (ap < bp) return -1;
return 0;
};

const compareSegments = (a, b) => {
for (let i = 0; i < Math.max(a.length, b.length); i++) {
const r = compareStrings(a[i] || 0, b[i] || 0);
if (r !== 0) return r;
}
return 0;
};

const operatorResMap = {
'>': [1],
'>=': [0, 1],
'=': [0],
'<=': [-1, 0],
'<': [-1],
};

const allowedOperators = Object.keys(operatorResMap);

const assertValidOperator = (op) => {
if (typeof op !== 'string') {
throw new TypeError(
`Invalid operator type, expected string but got ${typeof op}`
);
}
if (allowedOperators.indexOf(op) === -1) {
throw new Error(
`Invalid operator, expected one of ${allowedOperators.join('|')}`
);
}
};
Loading

0 comments on commit 326f88f

Please sign in to comment.