├── .release-please-manifest.json ├── map.js ├── .npmrc ├── preload.js ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug.yml ├── matchers │ └── tap.json ├── workflows │ ├── codeql-analysis.yml │ ├── audit.yml │ ├── pull-request.yml │ ├── release-integration.yml │ ├── ci.yml │ ├── post-dependabot.yml │ └── ci-release.yml ├── dependabot.yml ├── actions │ ├── create-check │ │ └── action.yml │ └── install-latest-npm │ │ └── action.yml └── settings.yml ├── functions ├── eq.js ├── gt.js ├── gte.js ├── lt.js ├── lte.js ├── neq.js ├── rcompare.js ├── compare-loose.js ├── major.js ├── minor.js ├── patch.js ├── sort.js ├── rsort.js ├── compare.js ├── valid.js ├── clean.js ├── prerelease.js ├── satisfies.js ├── compare-build.js ├── parse.js ├── inc.js ├── cmp.js ├── diff.js └── coerce.js ├── classes ├── index.js └── comparator.js ├── test ├── preload.js ├── classes │ ├── index.js │ ├── comparator.js │ ├── range.js │ └── semver.js ├── internal │ ├── constants.js │ ├── lrucache.js │ ├── identifiers.js │ ├── re.js │ ├── debug.js │ └── parse-options.js ├── functions │ ├── rcompare.js │ ├── rsort.js │ ├── sort.js │ ├── compare-build.js │ ├── major.js │ ├── minor.js │ ├── patch.js │ ├── prerelease.js │ ├── clean.js │ ├── gt.js │ ├── lt.js │ ├── gte.js │ ├── lte.js │ ├── compare-loose.js │ ├── eq.js │ ├── neq.js │ ├── satisfies.js │ ├── parse.js │ ├── valid.js │ ├── compare.js │ ├── inc.js │ ├── cmp.js │ ├── diff.js │ └── coerce.js ├── index.js ├── ranges │ ├── valid.js │ ├── ltr.js │ ├── max-satisfying.js │ ├── min-satisfying.js │ ├── gtr.js │ ├── simplify.js │ ├── outside.js │ ├── min-version.js │ ├── intersects.js │ ├── to-comparators.js │ └── subset.js ├── fixtures │ ├── invalid-versions.js │ ├── valid-versions.js │ ├── comparisons.js │ ├── comparator-intersection.js │ ├── equality.js │ ├── version-gt-range.js │ ├── version-lt-range.js │ ├── range-intersection.js │ ├── version-not-gt-range.js │ ├── version-not-lt-range.js │ ├── range-exclude.js │ ├── range-include.js │ ├── range-parse.js │ └── increments.js ├── map.js ├── integration │ └── whitespace.js └── bin │ └── semver.js ├── ranges ├── gtr.js ├── ltr.js ├── intersects.js ├── to-comparators.js ├── valid.js ├── min-satisfying.js ├── max-satisfying.js ├── simplify.js ├── min-version.js ├── outside.js └── subset.js ├── internal ├── debug.js ├── parse-options.js ├── identifiers.js ├── lrucache.js └── constants.js ├── CODE_OF_CONDUCT.md ├── .commitlintrc.js ├── .eslintrc.local.js ├── .eslintrc.js ├── benchmarks ├── bench-compare.js ├── bench-diff.js ├── bench-parse.js ├── bench-subset.js ├── bench-parse-options.js └── bench-satisfies.js ├── range.bnf ├── LICENSE ├── .gitignore ├── release-please-config.json ├── SECURITY.md ├── package.json ├── CONTRIBUTING.md ├── index.js └── bin └── semver.js /.release-please-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | ".": "7.7.3" 3 | } 4 | -------------------------------------------------------------------------------- /map.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = testFile => testFile.replace(/test\//, '') 4 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | ; This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | package-lock=false 4 | -------------------------------------------------------------------------------- /preload.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // XXX remove in v8 or beyond 4 | module.exports = require('./index.js') 5 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | * @npm/cli-team 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | blank_issues_enabled: true 4 | -------------------------------------------------------------------------------- /functions/eq.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compare = require('./compare') 4 | const eq = (a, b, loose) => compare(a, b, loose) === 0 5 | module.exports = eq 6 | -------------------------------------------------------------------------------- /functions/gt.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compare = require('./compare') 4 | const gt = (a, b, loose) => compare(a, b, loose) > 0 5 | module.exports = gt 6 | -------------------------------------------------------------------------------- /functions/gte.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compare = require('./compare') 4 | const gte = (a, b, loose) => compare(a, b, loose) >= 0 5 | module.exports = gte 6 | -------------------------------------------------------------------------------- /functions/lt.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compare = require('./compare') 4 | const lt = (a, b, loose) => compare(a, b, loose) < 0 5 | module.exports = lt 6 | -------------------------------------------------------------------------------- /functions/lte.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compare = require('./compare') 4 | const lte = (a, b, loose) => compare(a, b, loose) <= 0 5 | module.exports = lte 6 | -------------------------------------------------------------------------------- /functions/neq.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compare = require('./compare') 4 | const neq = (a, b, loose) => compare(a, b, loose) !== 0 5 | module.exports = neq 6 | -------------------------------------------------------------------------------- /functions/rcompare.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compare = require('./compare') 4 | const rcompare = (a, b, loose) => compare(b, a, loose) 5 | module.exports = rcompare 6 | -------------------------------------------------------------------------------- /functions/compare-loose.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compare = require('./compare') 4 | const compareLoose = (a, b) => compare(a, b, true) 5 | module.exports = compareLoose 6 | -------------------------------------------------------------------------------- /functions/major.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const major = (a, loose) => new SemVer(a, loose).major 5 | module.exports = major 6 | -------------------------------------------------------------------------------- /functions/minor.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const minor = (a, loose) => new SemVer(a, loose).minor 5 | module.exports = minor 6 | -------------------------------------------------------------------------------- /functions/patch.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const patch = (a, loose) => new SemVer(a, loose).patch 5 | module.exports = patch 6 | -------------------------------------------------------------------------------- /classes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | SemVer: require('./semver.js'), 5 | Range: require('./range.js'), 6 | Comparator: require('./comparator.js'), 7 | } 8 | -------------------------------------------------------------------------------- /functions/sort.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compareBuild = require('./compare-build') 4 | const sort = (list, loose) => list.sort((a, b) => compareBuild(a, b, loose)) 5 | module.exports = sort 6 | -------------------------------------------------------------------------------- /functions/rsort.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const compareBuild = require('./compare-build') 4 | const rsort = (list, loose) => list.sort((a, b) => compareBuild(b, a, loose)) 5 | module.exports = rsort 6 | -------------------------------------------------------------------------------- /test/preload.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const preload = require('../preload.js') 5 | const index = require('../index.js') 6 | t.equal(preload, index, 'preload and index match') 7 | -------------------------------------------------------------------------------- /functions/compare.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const compare = (a, b, loose) => 5 | new SemVer(a, loose).compare(new SemVer(b, loose)) 6 | 7 | module.exports = compare 8 | -------------------------------------------------------------------------------- /functions/valid.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const parse = require('./parse') 4 | const valid = (version, options) => { 5 | const v = parse(version, options) 6 | return v ? v.version : null 7 | } 8 | module.exports = valid 9 | -------------------------------------------------------------------------------- /functions/clean.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const parse = require('./parse') 4 | const clean = (version, options) => { 5 | const s = parse(version.trim().replace(/^[=v]+/, ''), options) 6 | return s ? s.version : null 7 | } 8 | module.exports = clean 9 | -------------------------------------------------------------------------------- /ranges/gtr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Determine if version is greater than all the versions possible in the range. 4 | const outside = require('./outside') 5 | const gtr = (version, range, options) => outside(version, range, '>', options) 6 | module.exports = gtr 7 | -------------------------------------------------------------------------------- /ranges/ltr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const outside = require('./outside') 4 | // Determine if version is less than all the versions possible in the range 5 | const ltr = (version, range, options) => outside(version, range, '<', options) 6 | module.exports = ltr 7 | -------------------------------------------------------------------------------- /functions/prerelease.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const parse = require('./parse') 4 | const prerelease = (version, options) => { 5 | const parsed = parse(version, options) 6 | return (parsed && parsed.prerelease.length) ? parsed.prerelease : null 7 | } 8 | module.exports = prerelease 9 | -------------------------------------------------------------------------------- /ranges/intersects.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Range = require('../classes/range') 4 | const intersects = (r1, r2, options) => { 5 | r1 = new Range(r1, options) 6 | r2 = new Range(r2, options) 7 | return r1.intersects(r2, options) 8 | } 9 | module.exports = intersects 10 | -------------------------------------------------------------------------------- /test/classes/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | t.same(require('../../classes'), { 5 | SemVer: require('../../classes/semver'), 6 | Range: require('../../classes/range'), 7 | Comparator: require('../../classes/comparator'), 8 | }, 'export all classes at semver/classes') 9 | -------------------------------------------------------------------------------- /internal/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const debug = ( 4 | typeof process === 'object' && 5 | process.env && 6 | process.env.NODE_DEBUG && 7 | /\bsemver\b/i.test(process.env.NODE_DEBUG) 8 | ) ? (...args) => console.error('SEMVER', ...args) 9 | : () => {} 10 | 11 | module.exports = debug 12 | -------------------------------------------------------------------------------- /functions/satisfies.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Range = require('../classes/range') 4 | const satisfies = (version, range, options) => { 5 | try { 6 | range = new Range(range, options) 7 | } catch (er) { 8 | return false 9 | } 10 | return range.test(version) 11 | } 12 | module.exports = satisfies 13 | -------------------------------------------------------------------------------- /functions/compare-build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const compareBuild = (a, b, loose) => { 5 | const versionA = new SemVer(a, loose) 6 | const versionB = new SemVer(b, loose) 7 | return versionA.compare(versionB) || versionA.compareBuild(versionB) 8 | } 9 | module.exports = compareBuild 10 | -------------------------------------------------------------------------------- /ranges/to-comparators.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Range = require('../classes/range') 4 | 5 | // Mostly just for testing and legacy API reasons 6 | const toComparators = (range, options) => 7 | new Range(range, options).set 8 | .map(comp => comp.map(c => c.value).join(' ').trim().split(' ')) 9 | 10 | module.exports = toComparators 11 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | All interactions in this repo are covered by the [npm Code of 4 | Conduct](https://docs.npmjs.com/policies/conduct) 5 | 6 | The npm cli team may, at its own discretion, moderate, remove, or edit 7 | any interactions such as pull requests, issues, and comments. 8 | -------------------------------------------------------------------------------- /test/internal/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const constants = require('../../internal/constants') 5 | 6 | t.match(constants, { 7 | MAX_LENGTH: Number, 8 | MAX_SAFE_COMPONENT_LENGTH: Number, 9 | MAX_SAFE_INTEGER: Number, 10 | RELEASE_TYPES: Array, 11 | SEMVER_SPEC_VERSION: String, 12 | }, 'got appropriate data types exported') 13 | -------------------------------------------------------------------------------- /test/functions/rcompare.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const rcompare = require('../../functions/rcompare') 5 | 6 | test('rcompare', (t) => { 7 | t.equal(rcompare('1.0.0', '1.0.1'), 1) 8 | t.equal(rcompare('1.0.0', '1.0.0'), 0) 9 | t.equal(rcompare('1.0.0+0', '1.0.0'), 0) 10 | t.equal(rcompare('1.0.1', '1.0.0'), -1) 11 | 12 | t.end() 13 | }) 14 | -------------------------------------------------------------------------------- /ranges/valid.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Range = require('../classes/range') 4 | const validRange = (range, options) => { 5 | try { 6 | // Return '*' instead of '' so that truthiness works. 7 | // This will throw if it's invalid anyway 8 | return new Range(range, options).range || '*' 9 | } catch (er) { 10 | return null 11 | } 12 | } 13 | module.exports = validRange 14 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const semver = require('../') 5 | const { SEMVER_SPEC_VERSION } = require('../internal/constants') 6 | 7 | t.match(Object.getOwnPropertyDescriptor(semver, 'SEMVER_SPEC_VERSION'), { 8 | get: undefined, 9 | set: undefined, 10 | value: SEMVER_SPEC_VERSION, 11 | configurable: true, 12 | enumerable: true, 13 | }, 'just a normal value property') 14 | -------------------------------------------------------------------------------- /functions/parse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const parse = (version, options, throwErrors = false) => { 5 | if (version instanceof SemVer) { 6 | return version 7 | } 8 | try { 9 | return new SemVer(version, options) 10 | } catch (er) { 11 | if (!throwErrors) { 12 | return null 13 | } 14 | throw er 15 | } 16 | } 17 | 18 | module.exports = parse 19 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | /* This file is automatically added by @npmcli/template-oss. Do not edit. */ 2 | 3 | module.exports = { 4 | extends: ['@commitlint/config-conventional'], 5 | rules: { 6 | 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'deps', 'chore']], 7 | 'header-max-length': [2, 'always', 80], 8 | 'subject-case': [0], 9 | 'body-max-line-length': [0], 10 | 'footer-max-line-length': [0], 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /internal/parse-options.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // parse out just the options we care about 4 | const looseOption = Object.freeze({ loose: true }) 5 | const emptyOpts = Object.freeze({ }) 6 | const parseOptions = options => { 7 | if (!options) { 8 | return emptyOpts 9 | } 10 | 11 | if (typeof options !== 'object') { 12 | return looseOption 13 | } 14 | 15 | return options 16 | } 17 | module.exports = parseOptions 18 | -------------------------------------------------------------------------------- /test/functions/rsort.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const rsort = require('../../functions/rsort') 5 | 6 | test('sorting', (t) => { 7 | const list = [ 8 | '1.2.3+1', 9 | '1.2.3+0', 10 | '1.2.3', 11 | '5.9.6', 12 | '0.1.2', 13 | ] 14 | const rsorted = [ 15 | '5.9.6', 16 | '1.2.3+1', 17 | '1.2.3+0', 18 | '1.2.3', 19 | '0.1.2', 20 | ] 21 | t.same(rsort(list), rsorted) 22 | t.end() 23 | }) 24 | -------------------------------------------------------------------------------- /test/functions/sort.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const sort = require('../../functions/sort') 5 | 6 | test('sorting', (t) => { 7 | const list = [ 8 | '1.2.3+1', 9 | '1.2.3+0', 10 | '1.2.3', 11 | '5.9.6', 12 | '0.1.2', 13 | ] 14 | const sorted = [ 15 | '0.1.2', 16 | '1.2.3', 17 | '1.2.3+0', 18 | '1.2.3+1', 19 | '5.9.6', 20 | ] 21 | 22 | t.same(sort(list), sorted) 23 | t.end() 24 | }) 25 | -------------------------------------------------------------------------------- /.eslintrc.local.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | module.exports = { 4 | overrides: [ 5 | { 6 | files: ['bin/**', 'classes/**', 'functions/**', 'internal/**', 'ranges/**'], 7 | rules: { 8 | 'import/no-extraneous-dependencies': [ 9 | 'error', 10 | { 11 | devDependencies: false, 12 | }, 13 | ], 14 | 'import/no-nodejs-modules': ['error'], 15 | strict: ['error', 'global'], 16 | }, 17 | }, 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* This file is automatically added by @npmcli/template-oss. Do not edit. */ 2 | 3 | 'use strict' 4 | 5 | const { readdirSync: readdir } = require('fs') 6 | 7 | const localConfigs = readdir(__dirname) 8 | .filter((file) => file.startsWith('.eslintrc.local.')) 9 | .map((file) => `./${file}`) 10 | 11 | module.exports = { 12 | root: true, 13 | ignorePatterns: [ 14 | 'tap-testdir*/', 15 | ], 16 | extends: [ 17 | '@npmcli', 18 | ...localConfigs, 19 | ], 20 | } 21 | -------------------------------------------------------------------------------- /test/ranges/valid.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const validRange = require('../../ranges/valid') 5 | const rangeParse = require('../fixtures/range-parse.js') 6 | 7 | test('valid range test', (t) => { 8 | // validRange(range) -> result 9 | // translate ranges into their canonical form 10 | t.plan(rangeParse.length) 11 | rangeParse.forEach(([pre, wanted, options]) => 12 | t.equal(validRange(pre, options), wanted, 13 | `validRange(${pre}) === ${wanted} ${JSON.stringify(options)}`)) 14 | }) 15 | -------------------------------------------------------------------------------- /test/internal/lrucache.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const LRUCache = require('../../internal/lrucache') 5 | 6 | test('basic cache operation', t => { 7 | const c = new LRUCache() 8 | const max = 1000 9 | 10 | for (let i = 0; i < max; i++) { 11 | t.equal(c.set(i, i), c) 12 | } 13 | for (let i = 0; i < max; i++) { 14 | t.equal(c.get(i), i) 15 | } 16 | c.set(1001, 1001) 17 | // lru item should be gone 18 | t.equal(c.get(0), undefined) 19 | c.set(42, undefined) 20 | t.end() 21 | }) 22 | -------------------------------------------------------------------------------- /benchmarks/bench-compare.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Benchmark = require('benchmark') 4 | const SemVer = require('../classes/semver') 5 | const suite = new Benchmark.Suite() 6 | 7 | const comparisons = require('../test/fixtures/comparisons') 8 | 9 | for (const [v0, v1] of comparisons) { 10 | suite.add(`compare ${v0} to ${v1}`, function () { 11 | const semver = new SemVer(v0) 12 | semver.compare(v1) 13 | }) 14 | } 15 | 16 | suite 17 | .on('cycle', function (event) { 18 | console.log(String(event.target)) 19 | }) 20 | .run({ async: false }) 21 | -------------------------------------------------------------------------------- /functions/inc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | 5 | const inc = (version, release, options, identifier, identifierBase) => { 6 | if (typeof (options) === 'string') { 7 | identifierBase = identifier 8 | identifier = options 9 | options = undefined 10 | } 11 | 12 | try { 13 | return new SemVer( 14 | version instanceof SemVer ? version.version : version, 15 | options 16 | ).inc(release, identifier, identifierBase).version 17 | } catch (er) { 18 | return null 19 | } 20 | } 21 | module.exports = inc 22 | -------------------------------------------------------------------------------- /benchmarks/bench-diff.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Benchmark = require('benchmark') 4 | const diff = require('../functions/diff') 5 | const suite = new Benchmark.Suite() 6 | 7 | const cases = [ 8 | ['0.0.1', '0.0.1-pre', 'patch'], 9 | ['0.0.1', '0.0.1-pre-2', 'patch'], 10 | ['1.1.0', '1.1.0-pre', 'minor'], 11 | ] 12 | 13 | for (const [v1, v2] of cases) { 14 | suite.add(`diff(${v1}, ${v2})`, function () { 15 | diff(v1, v2) 16 | }) 17 | } 18 | 19 | suite 20 | .on('cycle', function (event) { 21 | console.log(String(event.target)) 22 | }) 23 | .run({ async: false }) 24 | -------------------------------------------------------------------------------- /internal/identifiers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const numeric = /^[0-9]+$/ 4 | const compareIdentifiers = (a, b) => { 5 | if (typeof a === 'number' && typeof b === 'number') { 6 | return a === b ? 0 : a < b ? -1 : 1 7 | } 8 | 9 | const anum = numeric.test(a) 10 | const bnum = numeric.test(b) 11 | 12 | if (anum && bnum) { 13 | a = +a 14 | b = +b 15 | } 16 | 17 | return a === b ? 0 18 | : (anum && !bnum) ? -1 19 | : (bnum && !anum) ? 1 20 | : a < b ? -1 21 | : 1 22 | } 23 | 24 | const rcompareIdentifiers = (a, b) => compareIdentifiers(b, a) 25 | 26 | module.exports = { 27 | compareIdentifiers, 28 | rcompareIdentifiers, 29 | } 30 | -------------------------------------------------------------------------------- /range.bnf: -------------------------------------------------------------------------------- 1 | range-set ::= range ( logical-or range ) * 2 | logical-or ::= ( ' ' ) * '||' ( ' ' ) * 3 | range ::= hyphen | simple ( ' ' simple ) * | '' 4 | hyphen ::= partial ' - ' partial 5 | simple ::= primitive | partial | tilde | caret 6 | primitive ::= ( '<' | '>' | '>=' | '<=' | '=' ) partial 7 | partial ::= xr ( '.' xr ( '.' xr qualifier ? )? )? 8 | xr ::= 'x' | 'X' | '*' | nr 9 | nr ::= '0' | [1-9] ( [0-9] ) * 10 | tilde ::= '~' partial 11 | caret ::= '^' partial 12 | qualifier ::= ( '-' pre )? ( '+' build )? 13 | pre ::= parts 14 | build ::= parts 15 | parts ::= part ( '.' part ) * 16 | part ::= nr | [-0-9A-Za-z]+ 17 | -------------------------------------------------------------------------------- /test/functions/compare-build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const compareBuild = require('../../functions/compare-build') 5 | 6 | test('compareBuild', (t) => { 7 | const noBuild = '1.0.0' 8 | const build0 = '1.0.0+0' 9 | const build1 = '1.0.0+1' 10 | const build10 = '1.0.0+1.0' 11 | t.equal(compareBuild(noBuild, build0), -1) 12 | t.equal(compareBuild(build0, build0), 0) 13 | t.equal(compareBuild(build0, noBuild), 1) 14 | 15 | t.equal(compareBuild(build0, '1.0.0+0.0'), -1) 16 | t.equal(compareBuild(build0, build1), -1) 17 | t.equal(compareBuild(build1, build0), 1) 18 | t.equal(compareBuild(build10, build1), 1) 19 | 20 | t.end() 21 | }) 22 | -------------------------------------------------------------------------------- /benchmarks/bench-parse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Benchmark = require('benchmark') 4 | const parse = require('../functions/parse') 5 | const suite = new Benchmark.Suite() 6 | 7 | const cases = require(`../test/fixtures/valid-versions`) 8 | const invalidCases = require(`../test/fixtures/invalid-versions`) 9 | 10 | for (const test of cases) { 11 | suite.add(`parse(${test[0]})`, function () { 12 | parse(test[0]) 13 | }) 14 | } 15 | 16 | for (const test of invalidCases) { 17 | suite.add(`invalid parse(${test[0]})`, function () { 18 | parse(test[0]) 19 | }) 20 | } 21 | 22 | suite 23 | .on('cycle', function (event) { 24 | console.log(String(event.target)) 25 | }) 26 | .run({ async: false }) 27 | -------------------------------------------------------------------------------- /ranges/min-satisfying.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const Range = require('../classes/range') 5 | const minSatisfying = (versions, range, options) => { 6 | let min = null 7 | let minSV = null 8 | let rangeObj = null 9 | try { 10 | rangeObj = new Range(range, options) 11 | } catch (er) { 12 | return null 13 | } 14 | versions.forEach((v) => { 15 | if (rangeObj.test(v)) { 16 | // satisfies(v, range, options) 17 | if (!min || minSV.compare(v) === 1) { 18 | // compare(min, v, true) 19 | min = v 20 | minSV = new SemVer(min, options) 21 | } 22 | } 23 | }) 24 | return min 25 | } 26 | module.exports = minSatisfying 27 | -------------------------------------------------------------------------------- /ranges/max-satisfying.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const Range = require('../classes/range') 5 | 6 | const maxSatisfying = (versions, range, options) => { 7 | let max = null 8 | let maxSV = null 9 | let rangeObj = null 10 | try { 11 | rangeObj = new Range(range, options) 12 | } catch (er) { 13 | return null 14 | } 15 | versions.forEach((v) => { 16 | if (rangeObj.test(v)) { 17 | // satisfies(v, range, options) 18 | if (!max || maxSV.compare(v) === -1) { 19 | // compare(max, v, true) 20 | max = v 21 | maxSV = new SemVer(max, options) 22 | } 23 | } 24 | }) 25 | return max 26 | } 27 | module.exports = maxSatisfying 28 | -------------------------------------------------------------------------------- /benchmarks/bench-subset.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Benchmark = require('benchmark') 4 | const subset = require('../ranges/subset') 5 | const suite = new Benchmark.Suite() 6 | 7 | // taken from tests 8 | const cases = [ 9 | // everything is a subset of * 10 | ['1.2.3', '*', true], 11 | ['^1.2.3', '*', true], 12 | ['^1.2.3-pre.0', '*', false], 13 | ['^1.2.3-pre.0', '*', true, { includePrerelease: true }], 14 | ['1 || 2 || 3', '*', true], 15 | ] 16 | 17 | for (const [sub, dom] of cases) { 18 | suite.add(`subset(${sub}, ${dom})`, function () { 19 | subset(sub, dom) 20 | }) 21 | } 22 | 23 | suite 24 | .on('cycle', function (event) { 25 | console.log(String(event.target)) 26 | }) 27 | .run({ async: false }) 28 | -------------------------------------------------------------------------------- /test/internal/identifiers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { compareIdentifiers, rcompareIdentifiers } = require('../../internal/identifiers') 5 | 6 | test('rcompareIdentifiers and compareIdentifiers', (t) => { 7 | const set = [ 8 | ['1', '2'], 9 | ['alpha', 'beta'], 10 | ['0', 'beta'], 11 | [1, 2], 12 | ] 13 | set.forEach((ab) => { 14 | const a = ab[0] 15 | const b = ab[1] 16 | t.equal(compareIdentifiers(a, b), -1) 17 | t.equal(rcompareIdentifiers(a, b), 1) 18 | }) 19 | t.equal(compareIdentifiers('0', '0'), 0) 20 | t.equal(rcompareIdentifiers('0', '0'), 0) 21 | t.equal(compareIdentifiers(1, 1), 0) 22 | t.equal(rcompareIdentifiers(1, 1), 0) 23 | t.end() 24 | }) 25 | -------------------------------------------------------------------------------- /test/fixtures/invalid-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // none of these are semvers 4 | // [value, reason, opt] 5 | const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../../internal/constants') 6 | module.exports = [ 7 | [new Array(MAX_LENGTH).join('1') + '.0.0', 'too long'], 8 | [`${MAX_SAFE_INTEGER}0.0.0`, 'too big'], 9 | [`0.${MAX_SAFE_INTEGER}0.0`, 'too big'], 10 | [`0.0.${MAX_SAFE_INTEGER}0`, 'too big'], 11 | ['hello, world', 'not a version'], 12 | ['hello, world', 'even loose, its still junk', true], 13 | ['xyz', 'even loose as an opt, same', { loose: true }], 14 | [/a regexp/, 'regexp is not a string'], 15 | [/1.2.3/, 'semver-ish regexp is not a string'], 16 | [{ toString: () => '1.2.3' }, 'obj with a tostring is not a string'], 17 | ] 18 | -------------------------------------------------------------------------------- /test/functions/major.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const major = require('../../functions/major') 5 | 6 | test('major tests', (t) => { 7 | // [range, version] 8 | // Version should be detectable despite extra characters 9 | [ 10 | ['1.2.3', 1], 11 | [' 1.2.3 ', 1], 12 | [' 2.2.3-4 ', 2], 13 | [' 3.2.3-pre ', 3], 14 | ['v5.2.3', 5], 15 | [' v8.2.3 ', 8], 16 | ['\t13.2.3', 13], 17 | ['=21.2.3', 21, true], 18 | ['v=34.2.3', 34, true], 19 | ].forEach((tuple) => { 20 | const range = tuple[0] 21 | const version = tuple[1] 22 | const loose = tuple[2] || false 23 | const msg = `major(${range}) = ${version}` 24 | t.equal(major(range, loose), version, msg) 25 | }) 26 | t.end() 27 | }) 28 | -------------------------------------------------------------------------------- /test/functions/minor.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const minor = require('../../functions/minor') 5 | 6 | test('minor tests', (t) => { 7 | // [range, version] 8 | // Version should be detectable despite extra characters 9 | [ 10 | ['1.1.3', 1], 11 | [' 1.1.3 ', 1], 12 | [' 1.2.3-4 ', 2], 13 | [' 1.3.3-pre ', 3], 14 | ['v1.5.3', 5], 15 | [' v1.8.3 ', 8], 16 | ['\t1.13.3', 13], 17 | ['=1.21.3', 21, true], 18 | ['v=1.34.3', 34, true], 19 | ].forEach((tuple) => { 20 | const range = tuple[0] 21 | const version = tuple[1] 22 | const loose = tuple[2] || false 23 | const msg = `minor(${range}) = ${version}` 24 | t.equal(minor(range, loose), version, msg) 25 | }) 26 | t.end() 27 | }) 28 | -------------------------------------------------------------------------------- /test/functions/patch.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const patch = require('../../functions/patch') 5 | 6 | test('patch tests', (t) => { 7 | // [range, version] 8 | // Version should be detectable despite extra characters 9 | [ 10 | ['1.2.1', 1], 11 | [' 1.2.1 ', 1], 12 | [' 1.2.2-4 ', 2], 13 | [' 1.2.3-pre ', 3], 14 | ['v1.2.5', 5], 15 | [' v1.2.8 ', 8], 16 | ['\t1.2.13', 13], 17 | ['=1.2.21', 21, true], 18 | ['v=1.2.34', 34, true], 19 | ].forEach((tuple) => { 20 | const range = tuple[0] 21 | const version = tuple[1] 22 | const loose = tuple[2] || false 23 | const msg = `patch(${range}) = ${version}` 24 | t.equal(patch(range, loose), version, msg) 25 | }) 26 | t.end() 27 | }) 28 | -------------------------------------------------------------------------------- /.github/matchers/tap.json: -------------------------------------------------------------------------------- 1 | { 2 | "//@npmcli/template-oss": "This file is automatically added by @npmcli/template-oss. Do not edit.", 3 | "problemMatcher": [ 4 | { 5 | "owner": "tap", 6 | "pattern": [ 7 | { 8 | "regexp": "^\\s*not ok \\d+ - (.*)", 9 | "message": 1 10 | }, 11 | { 12 | "regexp": "^\\s*---" 13 | }, 14 | { 15 | "regexp": "^\\s*at:" 16 | }, 17 | { 18 | "regexp": "^\\s*line:\\s*(\\d+)", 19 | "line": 1 20 | }, 21 | { 22 | "regexp": "^\\s*column:\\s*(\\d+)", 23 | "column": 1 24 | }, 25 | { 26 | "regexp": "^\\s*file:\\s*(.*)", 27 | "file": 1 28 | } 29 | ] 30 | } 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The ISC License 2 | 3 | Copyright (c) Isaac Z. Schlueter and Contributors 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 10 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 11 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 12 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 13 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 14 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR 15 | IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /test/functions/prerelease.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const prerelease = require('../../functions/prerelease') 5 | 6 | test('prerelease', (t) => { 7 | // [prereleaseParts, version, loose] 8 | [ 9 | [['alpha', 1], '1.2.2-alpha.1'], 10 | [[1], '0.6.1-1'], 11 | [['beta', 2], '1.0.0-beta.2'], 12 | [['pre'], 'v0.5.4-pre'], 13 | [['alpha', 1], '1.2.2-alpha.1', false], 14 | [['beta'], '0.6.1beta', true], 15 | [null, '1.0.0', true], 16 | [null, '~2.0.0-alpha.1', false], 17 | [null, 'invalid version'], 18 | ].forEach((tuple) => { 19 | const expected = tuple[0] 20 | const version = tuple[1] 21 | const loose = tuple[2] 22 | const msg = `prerelease(${version})` 23 | t.same(prerelease(version, loose), expected, msg) 24 | }) 25 | t.end() 26 | }) 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | # ignore everything in the root 4 | /* 5 | 6 | !**/.gitignore 7 | !/.commitlintrc.js 8 | !/.eslint.config.js 9 | !/.eslintrc.js 10 | !/.eslintrc.local.* 11 | !/.git-blame-ignore-revs 12 | !/.github/ 13 | !/.gitignore 14 | !/.npmrc 15 | !/.prettierignore 16 | !/.prettierrc.js 17 | !/.release-please-manifest.json 18 | !/benchmarks 19 | !/bin/ 20 | !/CHANGELOG* 21 | !/classes/ 22 | !/CODE_OF_CONDUCT.md 23 | !/CONTRIBUTING.md 24 | !/docs/ 25 | !/functions/ 26 | !/index.js 27 | !/internal/ 28 | !/lib/ 29 | !/LICENSE* 30 | !/map.js 31 | !/package.json 32 | !/preload.js 33 | !/range.bnf 34 | !/ranges/ 35 | !/README* 36 | !/release-please-config.json 37 | !/scripts/ 38 | !/SECURITY.md 39 | !/tap-snapshots/ 40 | !/test/ 41 | !/tsconfig.json 42 | tap-testdir*/ 43 | -------------------------------------------------------------------------------- /test/functions/clean.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const clean = require('../../functions/clean') 5 | 6 | test('clean tests', (t) => { 7 | // [range, version] 8 | // Version should be detectable despite extra characters 9 | [ 10 | ['1.2.3', '1.2.3'], 11 | [' 1.2.3 ', '1.2.3'], 12 | [' 1.2.3-4 ', '1.2.3-4'], 13 | [' 1.2.3-pre ', '1.2.3-pre'], 14 | [' =v1.2.3 ', '1.2.3'], 15 | ['v1.2.3', '1.2.3'], 16 | [' v1.2.3 ', '1.2.3'], 17 | ['\t1.2.3', '1.2.3'], 18 | ['>1.2.3', null], 19 | ['~1.2.3', null], 20 | ['<=1.2.3', null], 21 | ['1.2.x', null], 22 | ['0.12.0-dev.1150+3c22cecee', '0.12.0-dev.1150'], 23 | ].forEach(([range, version]) => { 24 | const msg = `clean(${range}) = ${version}` 25 | t.equal(clean(range), version, msg) 26 | }) 27 | t.end() 28 | }) 29 | -------------------------------------------------------------------------------- /benchmarks/bench-parse-options.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Benchmark = require('benchmark') 4 | const parseOptions = require('../internal/parse-options') 5 | const suite = new Benchmark.Suite() 6 | 7 | const options1 = { 8 | includePrerelease: true, 9 | } 10 | 11 | const options2 = { 12 | includePrerelease: true, 13 | loose: true, 14 | } 15 | 16 | const options3 = { 17 | includePrerelease: true, 18 | loose: true, 19 | rtl: false, 20 | } 21 | 22 | suite 23 | .add('includePrerelease', function () { 24 | parseOptions(options1) 25 | }) 26 | .add('includePrerelease + loose', function () { 27 | parseOptions(options2) 28 | }) 29 | .add('includePrerelease + loose + rtl', function () { 30 | parseOptions(options3) 31 | }) 32 | .on('cycle', function (event) { 33 | console.log(String(event.target)) 34 | }) 35 | .run({ async: false }) 36 | -------------------------------------------------------------------------------- /release-please-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "group-pull-request-title-pattern": "chore: release ${version}", 3 | "pull-request-title-pattern": "chore: release${component} ${version}", 4 | "changelog-sections": [ 5 | { 6 | "type": "feat", 7 | "section": "Features", 8 | "hidden": false 9 | }, 10 | { 11 | "type": "fix", 12 | "section": "Bug Fixes", 13 | "hidden": false 14 | }, 15 | { 16 | "type": "docs", 17 | "section": "Documentation", 18 | "hidden": false 19 | }, 20 | { 21 | "type": "deps", 22 | "section": "Dependencies", 23 | "hidden": false 24 | }, 25 | { 26 | "type": "chore", 27 | "section": "Chores", 28 | "hidden": true 29 | } 30 | ], 31 | "packages": { 32 | ".": { 33 | "package-name": "" 34 | } 35 | }, 36 | "prerelease-type": "pre.0" 37 | } 38 | -------------------------------------------------------------------------------- /test/ranges/ltr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const ltr = require('../../ranges/ltr') 5 | const versionLtr = require('../fixtures/version-lt-range') 6 | const versionNotLtr = require('../fixtures/version-not-lt-range') 7 | 8 | test('ltr tests', (t) => { 9 | // [range, version, options] 10 | // Version should be less than range 11 | versionLtr.forEach(([range, version, options = false]) => { 12 | const msg = `ltr(${version}, ${range}, ${options})` 13 | t.ok(ltr(version, range, options), msg) 14 | }) 15 | t.end() 16 | }) 17 | 18 | test('negative ltr tests', (t) => { 19 | // [range, version, options] 20 | // Version should NOT be less than range 21 | versionNotLtr.forEach(([range, version, options = false]) => { 22 | const msg = `!ltr(${version}, ${range}, ${options})` 23 | t.notOk(ltr(version, range, options), msg) 24 | }) 25 | t.end() 26 | }) 27 | -------------------------------------------------------------------------------- /test/ranges/max-satisfying.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const maxSatisfying = require('../../ranges/max-satisfying') 5 | 6 | test('max satisfying', (t) => { 7 | [[['1.2.3', '1.2.4'], '1.2', '1.2.4'], 8 | [['1.2.4', '1.2.3'], '1.2', '1.2.4'], 9 | [['1.2.3', '1.2.4', '1.2.5', '1.2.6'], '~1.2.3', '1.2.6'], 10 | [['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0b3', '2.0.0', '2.1.0'], 11 | '~2.0.0', '2.0.0', true], 12 | ].forEach((v) => { 13 | const versions = v[0] 14 | const range = v[1] 15 | const expect = v[2] 16 | const loose = v[3] 17 | const actual = maxSatisfying(versions, range, loose) 18 | t.equal(actual, expect) 19 | }) 20 | t.end() 21 | }) 22 | 23 | test('bad ranges in max satisfying', (t) => { 24 | const r = 'some frogs and sneks-v2.5.6' 25 | t.equal(maxSatisfying([], r), null) 26 | t.end() 27 | }) 28 | -------------------------------------------------------------------------------- /test/ranges/min-satisfying.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const minSatisfying = require('../../ranges/min-satisfying') 5 | 6 | test('min satisfying', (t) => { 7 | [[['1.2.3', '1.2.4'], '1.2', '1.2.3'], 8 | [['1.2.4', '1.2.3'], '1.2', '1.2.3'], 9 | [['1.2.3', '1.2.4', '1.2.5', '1.2.6'], '~1.2.3', '1.2.3'], 10 | [['1.1.0', '1.2.0', '1.2.1', '1.3.0', '2.0.0b1', '2.0.0b2', '2.0.0b3', '2.0.0', '2.1.0'], 11 | '~2.0.0', '2.0.0', true], 12 | ].forEach((v) => { 13 | const versions = v[0] 14 | const range = v[1] 15 | const expect = v[2] 16 | const loose = v[3] 17 | const actual = minSatisfying(versions, range, loose) 18 | t.equal(actual, expect) 19 | }) 20 | t.end() 21 | }) 22 | 23 | test('bad ranges in min satisfying', (t) => { 24 | const r = 'some frogs and sneks-v2.5.6' 25 | t.equal(minSatisfying([], r), null) 26 | t.end() 27 | }) 28 | -------------------------------------------------------------------------------- /test/functions/gt.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const gt = require('../../functions/gt') 5 | const comparisons = require('../fixtures/comparisons.js') 6 | const equality = require('../fixtures/equality.js') 7 | 8 | test('comparison tests', t => { 9 | t.plan(comparisons.length) 10 | comparisons.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 11 | t.plan(4) 12 | t.ok(gt(v0, v1, loose), `gt('${v0}', '${v1}')`) 13 | t.ok(!gt(v1, v0, loose), `!gt('${v1}', '${v0}')`) 14 | t.ok(!gt(v1, v1, loose), `!gt('${v1}', '${v1}')`) 15 | t.ok(!gt(v0, v0, loose), `!gt('${v0}', '${v0}')`) 16 | })) 17 | }) 18 | 19 | test('equality tests', t => { 20 | t.plan(equality.length) 21 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 22 | t.plan(2) 23 | t.ok(!gt(v0, v1, loose), `!gt(${v0}, ${v1})`) 24 | t.ok(!gt(v1, v0, loose), `!gt(${v1}, ${v0})`) 25 | })) 26 | }) 27 | -------------------------------------------------------------------------------- /test/functions/lt.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const lt = require('../../functions/lt') 5 | const comparisons = require('../fixtures/comparisons.js') 6 | const equality = require('../fixtures/equality.js') 7 | 8 | test('comparison tests', t => { 9 | t.plan(comparisons.length) 10 | comparisons.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 11 | t.plan(4) 12 | t.ok(!lt(v0, v1, loose), `!lt('${v0}', '${v1}')`) 13 | t.ok(lt(v1, v0, loose), `lt('${v1}', '${v0}')`) 14 | t.ok(!lt(v1, v1, loose), `!lt('${v1}', '${v1}')`) 15 | t.ok(!lt(v0, v0, loose), `!lt('${v0}', '${v0}')`) 16 | })) 17 | }) 18 | 19 | test('equality tests', t => { 20 | t.plan(equality.length) 21 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 22 | t.plan(2) 23 | t.ok(!lt(v0, v1, loose), `!lt(${v0}, ${v1})`) 24 | t.ok(!lt(v1, v0, loose), `!lt(${v1}, ${v0})`) 25 | })) 26 | }) 27 | -------------------------------------------------------------------------------- /test/functions/gte.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const gte = require('../../functions/gte') 5 | const comparisons = require('../fixtures/comparisons.js') 6 | const equality = require('../fixtures/equality.js') 7 | 8 | test('comparison tests', t => { 9 | t.plan(comparisons.length) 10 | comparisons.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 11 | t.plan(4) 12 | t.ok(gte(v0, v1, loose), `gte('${v0}', '${v1}')`) 13 | t.ok(!gte(v1, v0, loose), `!gte('${v1}', '${v0}')`) 14 | t.ok(gte(v1, v1, loose), `gte('${v1}', '${v1}')`) 15 | t.ok(gte(v0, v0, loose), `gte('${v0}', '${v0}')`) 16 | })) 17 | }) 18 | 19 | test('equality tests', t => { 20 | t.plan(equality.length) 21 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 22 | t.plan(2) 23 | t.ok(gte(v0, v1, loose), `gte(${v0}, ${v1})`) 24 | t.ok(gte(v1, v0, loose), `gte(${v1}, ${v0})`) 25 | })) 26 | }) 27 | -------------------------------------------------------------------------------- /test/functions/lte.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const lte = require('../../functions/lte') 5 | const comparisons = require('../fixtures/comparisons.js') 6 | const equality = require('../fixtures/equality.js') 7 | 8 | test('comparison tests', t => { 9 | t.plan(comparisons.length) 10 | comparisons.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 11 | t.plan(4) 12 | t.ok(!lte(v0, v1, loose), `!lte('${v0}', '${v1}')`) 13 | t.ok(lte(v1, v0, loose), `lte('${v1}', '${v0}')`) 14 | t.ok(lte(v1, v1, loose), `lte('${v1}', '${v1}')`) 15 | t.ok(lte(v0, v0, loose), `lte('${v0}', '${v0}')`) 16 | })) 17 | }) 18 | 19 | test('equality tests', t => { 20 | t.plan(equality.length) 21 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 22 | t.plan(2) 23 | t.ok(lte(v0, v1, loose), `lte(${v0}, ${v1})`) 24 | t.ok(lte(v1, v0, loose), `lte(${v1}, ${v0})`) 25 | })) 26 | }) 27 | -------------------------------------------------------------------------------- /test/functions/compare-loose.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const compareLoose = require('../../functions/compare-loose') 5 | const SemVer = require('../../classes/semver') 6 | const eq = require('../../functions/eq') 7 | 8 | test('strict vs loose version numbers', (t) => { 9 | [['=1.2.3', '1.2.3'], 10 | ['01.02.03', '1.2.3'], 11 | ['1.2.3-beta.01', '1.2.3-beta.1'], 12 | [' =1.2.3', '1.2.3'], 13 | ['1.2.3foo', '1.2.3-foo'], 14 | ].forEach((v) => { 15 | const loose = v[0] 16 | const strict = v[1] 17 | t.throws(() => { 18 | SemVer(loose) // eslint-disable-line no-new 19 | }) 20 | const lv = new SemVer(loose, true) 21 | t.equal(lv.version, strict) 22 | t.ok(eq(loose, strict, true)) 23 | t.throws(() => { 24 | eq(loose, strict) 25 | }) 26 | t.throws(() => { 27 | new SemVer(strict).compare(loose) 28 | }) 29 | t.equal(compareLoose(v[0], v[1]), 0) 30 | }) 31 | t.end() 32 | }) 33 | -------------------------------------------------------------------------------- /internal/lrucache.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | class LRUCache { 4 | constructor () { 5 | this.max = 1000 6 | this.map = new Map() 7 | } 8 | 9 | get (key) { 10 | const value = this.map.get(key) 11 | if (value === undefined) { 12 | return undefined 13 | } else { 14 | // Remove the key from the map and add it to the end 15 | this.map.delete(key) 16 | this.map.set(key, value) 17 | return value 18 | } 19 | } 20 | 21 | delete (key) { 22 | return this.map.delete(key) 23 | } 24 | 25 | set (key, value) { 26 | const deleted = this.delete(key) 27 | 28 | if (!deleted && value !== undefined) { 29 | // If cache is full, delete the least recently used item 30 | if (this.map.size >= this.max) { 31 | const firstKey = this.map.keys().next().value 32 | this.delete(firstKey) 33 | } 34 | 35 | this.map.set(key, value) 36 | } 37 | 38 | return this 39 | } 40 | } 41 | 42 | module.exports = LRUCache 43 | -------------------------------------------------------------------------------- /internal/constants.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // Note: this is the semver.org version of the spec that it implements 4 | // Not necessarily the package version of this code. 5 | const SEMVER_SPEC_VERSION = '2.0.0' 6 | 7 | const MAX_LENGTH = 256 8 | const MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9 | /* istanbul ignore next */ 9007199254740991 10 | 11 | // Max safe segment length for coercion. 12 | const MAX_SAFE_COMPONENT_LENGTH = 16 13 | 14 | // Max safe length for a build identifier. The max length minus 6 characters for 15 | // the shortest version with a build 0.0.0+BUILD. 16 | const MAX_SAFE_BUILD_LENGTH = MAX_LENGTH - 6 17 | 18 | const RELEASE_TYPES = [ 19 | 'major', 20 | 'premajor', 21 | 'minor', 22 | 'preminor', 23 | 'patch', 24 | 'prepatch', 25 | 'prerelease', 26 | ] 27 | 28 | module.exports = { 29 | MAX_LENGTH, 30 | MAX_SAFE_COMPONENT_LENGTH, 31 | MAX_SAFE_BUILD_LENGTH, 32 | MAX_SAFE_INTEGER, 33 | RELEASE_TYPES, 34 | SEMVER_SPEC_VERSION, 35 | FLAG_INCLUDE_PRERELEASE: 0b001, 36 | FLAG_LOOSE: 0b010, 37 | } 38 | -------------------------------------------------------------------------------- /test/functions/eq.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const eq = require('../../functions/eq') 5 | const comparisons = require('../fixtures/comparisons.js') 6 | const equality = require('../fixtures/equality.js') 7 | 8 | test('comparison tests', t => { 9 | t.plan(comparisons.length) 10 | comparisons.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 11 | t.plan(4) 12 | t.notOk(eq(v0, v1, loose), `!eq(${v0}, ${v1})`) 13 | t.notOk(eq(v1, v0, loose), `!eq(${v1}, ${v0})`) 14 | t.ok(eq(v1, v1, loose), `eq('${v1}', '${v1}')`) 15 | t.ok(eq(v0, v0, loose), `eq('${v0}', '${v0}')`) 16 | })) 17 | }) 18 | 19 | test('equality tests', t => { 20 | t.plan(equality.length) 21 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 22 | t.plan(4) 23 | t.ok(eq(v0, v1, loose), `eq(${v0}, ${v1})`) 24 | t.ok(eq(v1, v0, loose), `eq(${v1}, ${v0})`) 25 | t.ok(eq(v0, v0, loose), `eq(${v0}, ${v0})`) 26 | t.ok(eq(v1, v1, loose), `eq(${v1}, ${v1})`) 27 | })) 28 | }) 29 | -------------------------------------------------------------------------------- /test/internal/re.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const { src, re, safeRe, safeSrc } = require('../../internal/re') 5 | const semver = require('../../') 6 | 7 | test('Semver itself has a list of src, re, and tokens', (t) => { 8 | t.match(Object.assign({}, semver), { 9 | re: Array, 10 | src: Array, 11 | tokens: Object, 12 | }) 13 | re.forEach(r => t.match(r, RegExp, 'regexps are regexps')) 14 | safeRe.forEach(r => t.match(r, RegExp, 'safe regexps are regexps')) 15 | src.forEach(s => t.match(s, String, 'src are strings')) 16 | safeSrc.forEach(s => t.match(s, String, 'safe srcare strings')) 17 | t.ok(Object.keys(semver.tokens).length, 'there are tokens') 18 | for (const i in semver.tokens) { 19 | t.match(semver.tokens[i], Number, 'tokens are numbers') 20 | } 21 | 22 | safeRe.forEach(r => { 23 | t.notMatch(r.source, '\\s+', 'safe regex do not contain greedy whitespace') 24 | t.notMatch(r.source, '\\s*', 'safe regex do not contain greedy whitespace') 25 | }) 26 | 27 | t.end() 28 | }) 29 | -------------------------------------------------------------------------------- /test/ranges/gtr.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const gtr = require('../../ranges/gtr') 5 | const versionGtr = require('../fixtures/version-gt-range') 6 | const versionNotGtr = require('../fixtures/version-not-gt-range') 7 | 8 | test('gtr tests', (t) => { 9 | // [range, version, options] 10 | // Version should be greater than range 11 | versionGtr.forEach((tuple) => { 12 | const range = tuple[0] 13 | const version = tuple[1] 14 | const options = tuple[2] || false 15 | const msg = `gtr(${version}, ${range}, ${options})` 16 | t.ok(gtr(version, range, options), msg) 17 | }) 18 | t.end() 19 | }) 20 | 21 | test('negative gtr tests', (t) => { 22 | // [range, version, options] 23 | // Version should NOT be greater than range 24 | versionNotGtr.forEach((tuple) => { 25 | const range = tuple[0] 26 | const version = tuple[1] 27 | const options = tuple[2] || false 28 | const msg = `!gtr(${version}, ${range}, ${options})` 29 | t.notOk(gtr(version, range, options), msg) 30 | }) 31 | t.end() 32 | }) 33 | -------------------------------------------------------------------------------- /test/functions/neq.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const neq = require('../../functions/neq') 5 | const comparisons = require('../fixtures/comparisons.js') 6 | const equality = require('../fixtures/equality.js') 7 | 8 | test('comparison tests', t => { 9 | t.plan(comparisons.length) 10 | comparisons.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 11 | t.plan(4) 12 | t.ok(neq(v0, v1, loose), `neq(${v0}, ${v1})`) 13 | t.ok(neq(v1, v0, loose), `neq(${v1}, ${v0})`) 14 | t.notOk(neq(v1, v1, loose), `!neq('${v1}', '${v1}')`) 15 | t.notOk(neq(v0, v0, loose), `!neq('${v0}', '${v0}')`) 16 | })) 17 | }) 18 | 19 | test('equality tests', t => { 20 | t.plan(equality.length) 21 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 22 | t.plan(4) 23 | t.notOk(neq(v0, v1, loose), `!neq(${v0}, ${v1})`) 24 | t.notOk(neq(v1, v0, loose), `!neq(${v1}, ${v0})`) 25 | t.notOk(neq(v0, v0, loose), `!neq(${v0}, ${v0})`) 26 | t.notOk(neq(v1, v1, loose), `!neq(${v1}, ${v1})`) 27 | })) 28 | }) 29 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: CodeQL 4 | 5 | on: 6 | push: 7 | branches: 8 | - main 9 | - release/v* 10 | pull_request: 11 | branches: 12 | - main 13 | - release/v* 14 | schedule: 15 | # "At 10:00 UTC (03:00 PT) on Monday" https://crontab.guru/#0_10_*_*_1 16 | - cron: "0 10 * * 1" 17 | 18 | permissions: 19 | contents: read 20 | 21 | jobs: 22 | analyze: 23 | name: Analyze 24 | runs-on: ubuntu-latest 25 | permissions: 26 | actions: read 27 | contents: read 28 | security-events: write 29 | steps: 30 | - name: Checkout 31 | uses: actions/checkout@v4 32 | - name: Setup Git User 33 | run: | 34 | git config --global user.email "npm-cli+bot@github.com" 35 | git config --global user.name "npm CLI robot" 36 | - name: Initialize CodeQL 37 | uses: github/codeql-action/init@v3 38 | with: 39 | languages: javascript 40 | - name: Perform CodeQL Analysis 41 | uses: github/codeql-action/analyze@v3 42 | -------------------------------------------------------------------------------- /test/functions/satisfies.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const satisfies = require('../../functions/satisfies') 5 | const rangeInclude = require('../fixtures/range-include.js') 6 | const rangeExclude = require('../fixtures/range-exclude.js') 7 | test('range tests', t => { 8 | t.plan(rangeInclude.length) 9 | rangeInclude.forEach(([range, ver, options]) => 10 | t.ok(satisfies(ver, range, options), `${range} satisfied by ${ver}`)) 11 | }) 12 | 13 | test('negative range tests', t => { 14 | t.plan(rangeExclude.length) 15 | rangeExclude.forEach(([range, ver, options]) => 16 | t.notOk(satisfies(ver, range, options), `${range} not satisfied by ${ver}`)) 17 | }) 18 | 19 | test('invalid ranges never satisfied (but do not throw)', t => { 20 | const cases = [ 21 | ['blerg', '1.2.3'], 22 | ['git+https://user:password0123@github.com/foo', '123.0.0', true], 23 | ['^1.2.3', '2.0.0-pre'], 24 | ['0.x', undefined], 25 | ['*', undefined], 26 | ] 27 | t.plan(cases.length) 28 | cases.forEach(([range, ver]) => 29 | t.notOk(satisfies(ver, range), `${range} not satisfied because invalid`)) 30 | }) 31 | -------------------------------------------------------------------------------- /test/ranges/simplify.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const simplify = require('../../ranges/simplify.js') 4 | const Range = require('../../classes/range.js') 5 | const t = require('tap') 6 | const versions = [ 7 | '1.0.0', 8 | '1.0.1', 9 | '1.0.2', 10 | '1.0.3', 11 | '1.0.4', 12 | '1.1.0', 13 | '1.1.1', 14 | '1.1.2', 15 | '1.2.0', 16 | '1.2.1', 17 | '1.2.2', 18 | '1.2.3', 19 | '1.2.4', 20 | '1.2.5', 21 | '2.0.0', 22 | '2.0.1', 23 | '2.1.0', 24 | '2.1.1', 25 | '2.1.2', 26 | '2.2.0', 27 | '2.2.1', 28 | '2.2.2', 29 | '2.3.0', 30 | '2.3.1', 31 | '2.4.0', 32 | '3.0.0', 33 | '3.1.0', 34 | '3.2.0', 35 | '3.3.0', 36 | ] 37 | 38 | t.equal(simplify(versions, '1.x'), '1.x') 39 | t.equal(simplify(versions, '1.0.0 || 1.0.1 || 1.0.2 || 1.0.3 || 1.0.4'), '<=1.0.4') 40 | t.equal(simplify(versions, new Range('1.0.0 || 1.0.1 || 1.0.2 || 1.0.3 || 1.0.4')), '<=1.0.4') 41 | t.equal(simplify(versions, '>=3.0.0 <3.1.0'), '3.0.0') 42 | t.equal(simplify(versions, '3.0.0 || 3.1 || 3.2 || 3.3'), '>=3.0.0') 43 | t.equal(simplify(versions, '1 || 2 || 3'), '*') 44 | t.equal(simplify(versions, '2.1 || 2.2 || 2.3'), '2.1.0 - 2.3.1') 45 | -------------------------------------------------------------------------------- /functions/cmp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const eq = require('./eq') 4 | const neq = require('./neq') 5 | const gt = require('./gt') 6 | const gte = require('./gte') 7 | const lt = require('./lt') 8 | const lte = require('./lte') 9 | 10 | const cmp = (a, op, b, loose) => { 11 | switch (op) { 12 | case '===': 13 | if (typeof a === 'object') { 14 | a = a.version 15 | } 16 | if (typeof b === 'object') { 17 | b = b.version 18 | } 19 | return a === b 20 | 21 | case '!==': 22 | if (typeof a === 'object') { 23 | a = a.version 24 | } 25 | if (typeof b === 'object') { 26 | b = b.version 27 | } 28 | return a !== b 29 | 30 | case '': 31 | case '=': 32 | case '==': 33 | return eq(a, b, loose) 34 | 35 | case '!=': 36 | return neq(a, b, loose) 37 | 38 | case '>': 39 | return gt(a, b, loose) 40 | 41 | case '>=': 42 | return gte(a, b, loose) 43 | 44 | case '<': 45 | return lt(a, b, loose) 46 | 47 | case '<=': 48 | return lte(a, b, loose) 49 | 50 | default: 51 | throw new TypeError(`Invalid operator: ${op}`) 52 | } 53 | } 54 | module.exports = cmp 55 | -------------------------------------------------------------------------------- /test/fixtures/valid-versions.js: -------------------------------------------------------------------------------- 1 | // [version, major, minor, patch, prerelease[], build[]] 2 | module.exports = [ 3 | ['1.0.0', 1, 0, 0, [], []], 4 | ['2.1.0', 2, 1, 0, [], []], 5 | ['3.2.1', 3, 2, 1, [], []], 6 | ['v1.2.3', 1, 2, 3, [], []], 7 | 8 | // prerelease 9 | ['1.2.3-0', 1, 2, 3, [0], []], 10 | ['1.2.3-123', 1, 2, 3, [123], []], 11 | ['1.2.3-1.2.3', 1, 2, 3, [1, 2, 3], []], 12 | ['1.2.3-1a', 1, 2, 3, ['1a'], []], 13 | ['1.2.3-a1', 1, 2, 3, ['a1'], []], 14 | ['1.2.3-alpha', 1, 2, 3, ['alpha'], []], 15 | ['1.2.3-alpha.1', 1, 2, 3, ['alpha', 1], []], 16 | ['1.2.3-alpha-1', 1, 2, 3, ['alpha-1'], []], 17 | ['1.2.3-alpha-.-beta', 1, 2, 3, ['alpha-', '-beta'], []], 18 | 19 | // build 20 | ['1.2.3+456', 1, 2, 3, [], ['456']], 21 | ['1.2.3+build', 1, 2, 3, [], ['build']], 22 | ['1.2.3+new-build', 1, 2, 3, [], ['new-build']], 23 | ['1.2.3+build.1', 1, 2, 3, [], ['build', '1']], 24 | ['1.2.3+build.1a', 1, 2, 3, [], ['build', '1a']], 25 | ['1.2.3+build.a1', 1, 2, 3, [], ['build', 'a1']], 26 | ['1.2.3+build.alpha', 1, 2, 3, [], ['build', 'alpha']], 27 | ['1.2.3+build.alpha.beta', 1, 2, 3, [], ['build', 'alpha', 'beta']], 28 | 29 | // mixed 30 | ['1.2.3-alpha+build', 1, 2, 3, ['alpha'], ['build']], 31 | ] 32 | -------------------------------------------------------------------------------- /test/fixtures/comparisons.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [version1, version2] 4 | // version1 should be greater than version2 5 | // used by the cmp, eq, gt, lt, and neq tests 6 | module.exports = [ 7 | ['0.0.0', '0.0.0-foo'], 8 | ['0.0.1', '0.0.0'], 9 | ['1.0.0', '0.9.9'], 10 | ['0.10.0', '0.9.0'], 11 | ['0.99.0', '0.10.0', {}], 12 | ['2.0.0', '1.2.3', { loose: false }], 13 | ['v0.0.0', '0.0.0-foo', true], 14 | ['v0.0.1', '0.0.0', { loose: true }], 15 | ['v1.0.0', '0.9.9', true], 16 | ['v0.10.0', '0.9.0', true], 17 | ['v0.99.0', '0.10.0', true], 18 | ['v2.0.0', '1.2.3', true], 19 | ['0.0.0', 'v0.0.0-foo', true], 20 | ['0.0.1', 'v0.0.0', true], 21 | ['1.0.0', 'v0.9.9', true], 22 | ['0.10.0', 'v0.9.0', true], 23 | ['0.99.0', 'v0.10.0', true], 24 | ['2.0.0', 'v1.2.3', true], 25 | ['1.2.3', '1.2.3-asdf'], 26 | ['1.2.3', '1.2.3-4'], 27 | ['1.2.3', '1.2.3-4-foo'], 28 | ['1.2.3-5-foo', '1.2.3-5'], 29 | ['1.2.3-5', '1.2.3-4'], 30 | ['1.2.3-5-foo', '1.2.3-5-Foo'], 31 | ['3.0.0', '2.7.2+asdf'], 32 | ['1.2.3-a.10', '1.2.3-a.5'], 33 | ['1.2.3-a.b', '1.2.3-a.5'], 34 | ['1.2.3-a.b', '1.2.3-a'], 35 | ['1.2.3-a.b.c.10.d.5', '1.2.3-a.b.c.5.d.100'], 36 | ['1.2.3-r2', '1.2.3-r100'], 37 | ['1.2.3-r100', '1.2.3-R2'], 38 | ] 39 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | GitHub takes the security of our software products and services seriously, including the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub). 4 | 5 | If you believe you have found a security vulnerability in this GitHub-owned open source repository, you can report it to us in one of two ways. 6 | 7 | If the vulnerability you have found is *not* [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) or if you do not wish to be considered for a bounty reward, please report the issue to us directly through [opensource-security@github.com](mailto:opensource-security@github.com). 8 | 9 | If the vulnerability you have found is [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) and you would like for your finding to be considered for a bounty reward, please submit the vulnerability to us through [HackerOne](https://hackerone.com/github) in order to be eligible to receive a bounty award. 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.** 12 | 13 | Thanks for helping make GitHub safe for everyone. 14 | -------------------------------------------------------------------------------- /test/functions/parse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const parse = require('../../functions/parse') 5 | const SemVer = require('../../classes/semver') 6 | const invalidVersions = require('../fixtures/invalid-versions') 7 | 8 | t.test('returns null instead of throwing when presented with garbage', t => { 9 | t.plan(invalidVersions.length) 10 | invalidVersions.forEach(([v, msg, opts]) => 11 | t.equal(parse(v, opts), null, msg)) 12 | }) 13 | 14 | t.test('throw errors if asked to', t => { 15 | t.throws(() => { 16 | parse('bad', null, true) 17 | }, { 18 | name: 'TypeError', 19 | message: 'Invalid Version: bad', 20 | }) 21 | t.throws(() => { 22 | parse([], null, true) 23 | }, { 24 | name: 'TypeError', 25 | message: 'Invalid version. Must be a string. Got type "object".', 26 | }) 27 | t.end() 28 | }) 29 | 30 | t.test('parse a version into a SemVer object', t => { 31 | t.match(parse('1.2.3'), new SemVer('1.2.3')) 32 | const s = new SemVer('4.5.6') 33 | t.equal(parse(s), s, 'just return it if its a SemVer obj') 34 | const loose = new SemVer('4.2.0', { loose: true }) 35 | t.match(parse('4.2.0', true), loose, 'looseness as a boolean') 36 | t.match(parse('4.2.0', { loose: true }), loose, 'looseness as an option') 37 | t.end() 38 | }) 39 | -------------------------------------------------------------------------------- /test/functions/valid.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const valid = require('../../functions/valid') 5 | const SemVer = require('../../classes/semver') 6 | const invalidVersions = require('../fixtures/invalid-versions') 7 | const { MAX_SAFE_INTEGER } = require('../../internal/constants') 8 | 9 | t.test('returns null instead of throwing when presented with garbage', t => { 10 | t.plan(invalidVersions.length) 11 | invalidVersions.forEach(([v, msg, opts]) => 12 | t.equal(valid(v, opts), null, msg)) 13 | }) 14 | 15 | t.test('validate a version into a SemVer object', t => { 16 | t.equal(valid('1.2.3'), '1.2.3') 17 | const s = new SemVer('4.5.6') 18 | t.equal(valid(s), '4.5.6', 'return the version if a SemVer obj') 19 | t.equal(valid('4.2.0foo', true), '4.2.0-foo', 'looseness as a boolean') 20 | t.equal(valid('4.2.0foo', { loose: true }), '4.2.0-foo', 'looseness as an option') 21 | t.end() 22 | }) 23 | 24 | t.test('long build id', t => { 25 | const longBuild = '-928490632884417731e7af463c92b034d6a78268fc993bcb88a57944' 26 | const shortVersion = '1.1.1' 27 | const longVersion = `${MAX_SAFE_INTEGER}.${MAX_SAFE_INTEGER}.${MAX_SAFE_INTEGER}` 28 | t.equal(valid(shortVersion + longBuild), shortVersion + longBuild) 29 | t.equal(valid(longVersion + longBuild), longVersion + longBuild) 30 | t.end() 31 | }) 32 | -------------------------------------------------------------------------------- /test/internal/debug.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const main = () => { 4 | const t = require('tap') 5 | const { spawn } = require('child_process') 6 | t.plan(2) 7 | t.test('without env set', t => { 8 | const c = spawn(process.execPath, [__filename, 'child'], { env: { 9 | ...process.env, 10 | NODE_DEBUG: '', 11 | } }) 12 | const err = [] 13 | c.stderr.on('data', chunk => err.push(chunk)) 14 | c.on('close', (code, signal) => { 15 | t.equal(code, 0, 'success exit status') 16 | t.equal(signal, null, 'no signal') 17 | t.equal(Buffer.concat(err).toString('utf8'), '', 'got no output') 18 | t.end() 19 | }) 20 | }) 21 | t.test('with env set', t => { 22 | const c = spawn(process.execPath, [__filename, 'child'], { env: { 23 | ...process.env, 24 | NODE_DEBUG: 'semver', 25 | } }) 26 | const err = [] 27 | c.stderr.on('data', chunk => err.push(chunk)) 28 | c.on('close', (code, signal) => { 29 | t.equal(code, 0, 'success exit status') 30 | t.equal(signal, null, 'no signal') 31 | t.equal(Buffer.concat(err).toString('utf8'), 'SEMVER hello, world\n', 'got expected output') 32 | t.end() 33 | }) 34 | }) 35 | t.end() 36 | } 37 | 38 | if (process.argv[2] === 'child') { 39 | require('../../internal/debug')('hello, world') 40 | } else { 41 | main() 42 | } 43 | -------------------------------------------------------------------------------- /.github/workflows/audit.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Audit 4 | 5 | on: 6 | workflow_dispatch: 7 | schedule: 8 | # "At 08:00 UTC (01:00 PT) on Monday" https://crontab.guru/#0_8_*_*_1 9 | - cron: "0 8 * * 1" 10 | 11 | permissions: 12 | contents: read 13 | 14 | jobs: 15 | audit: 16 | name: Audit Dependencies 17 | if: github.repository_owner == 'npm' 18 | runs-on: ubuntu-latest 19 | defaults: 20 | run: 21 | shell: bash 22 | steps: 23 | - name: Checkout 24 | uses: actions/checkout@v4 25 | - name: Setup Git User 26 | run: | 27 | git config --global user.email "npm-cli+bot@github.com" 28 | git config --global user.name "npm CLI robot" 29 | - name: Setup Node 30 | uses: actions/setup-node@v4 31 | id: node 32 | with: 33 | node-version: 22.x 34 | check-latest: contains('22.x', '.x') 35 | - name: Install Latest npm 36 | uses: ./.github/actions/install-latest-npm 37 | with: 38 | node: ${{ steps.node.outputs.node-version }} 39 | - name: Install Dependencies 40 | run: npm i --ignore-scripts --no-audit --no-fund --package-lock 41 | - name: Run Production Audit 42 | run: npm audit --omit=dev 43 | - name: Run Full Audit 44 | run: npm audit --audit-level=none 45 | -------------------------------------------------------------------------------- /benchmarks/bench-satisfies.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Benchmark = require('benchmark') 4 | const satisfies = require('../functions/satisfies') 5 | const suite = new Benchmark.Suite() 6 | 7 | const versions = ['1.0.3||^2.0.0', '2.2.2||~3.0.0', '2.3.0||<4.0.0'] 8 | const versionToCompare = '1.0.6' 9 | const option1 = { includePrelease: true } 10 | const option2 = { includePrelease: true, loose: true } 11 | const option3 = { includePrelease: true, loose: true, rtl: true } 12 | 13 | for (const version of versions) { 14 | suite.add(`satisfies(${versionToCompare}, ${version})`, function () { 15 | satisfies(versionToCompare, version) 16 | }) 17 | } 18 | 19 | for (const version of versions) { 20 | suite.add(`satisfies(${versionToCompare}, ${version}, ${JSON.stringify(option1)})`, function () { 21 | satisfies(versionToCompare, version, option1) 22 | }) 23 | } 24 | 25 | for (const version of versions) { 26 | suite.add(`satisfies(${versionToCompare}, ${version}, ${JSON.stringify(option2)})`, function () { 27 | satisfies(versionToCompare, version, option2) 28 | }) 29 | } 30 | 31 | for (const version of versions) { 32 | suite.add(`satisfies(${versionToCompare}, ${version}, ${JSON.stringify(option3)})`, function () { 33 | satisfies(versionToCompare, version, option3) 34 | }) 35 | } 36 | 37 | suite 38 | .on('cycle', function (event) { 39 | console.log(String(event.target)) 40 | }) 41 | .run({ async: false }) 42 | -------------------------------------------------------------------------------- /test/map.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const { resolve, join, relative, extname, dirname, basename } = require('path') 5 | const { statSync, readdirSync } = require('fs') 6 | const map = require('../map.js') 7 | const pkg = require('../package.json') 8 | 9 | const ROOT = resolve(__dirname, '..') 10 | const TEST = join(ROOT, 'test') 11 | const IGNORE_DIRS = ['fixtures', 'integration'] 12 | 13 | const getFile = (f) => { 14 | try { 15 | if (statSync(f).isFile()) { 16 | return extname(f) === '.js' ? [f] : [] 17 | } 18 | } catch { 19 | return [] 20 | } 21 | } 22 | 23 | const walk = (item, res = []) => getFile(item) || readdirSync(item) 24 | .map(f => join(item, f)) 25 | .reduce((acc, f) => acc.concat(statSync(f).isDirectory() ? walk(f, res) : getFile(f)), []) 26 | .filter(Boolean) 27 | 28 | const walkAll = (items, relativeTo) => items 29 | .reduce((acc, f) => acc.concat(walk(join(ROOT, f))), []) 30 | .map((f) => relative(relativeTo, f)) 31 | .sort() 32 | 33 | t.test('tests match system', t => { 34 | const sut = walkAll([pkg.tap['coverage-map'], ...pkg.files], ROOT) 35 | const tests = walkAll([basename(TEST)], TEST) 36 | .filter(f => !IGNORE_DIRS.includes(dirname(f))) 37 | 38 | t.strictSame(sut, tests, 'test files should match system files') 39 | 40 | for (const f of tests) { 41 | t.test(f, t => { 42 | t.plan(1) 43 | t.ok(sut.includes(map(f)), 'test covers a file') 44 | }) 45 | } 46 | 47 | t.end() 48 | }) 49 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | version: 2 4 | 5 | updates: 6 | - package-ecosystem: npm 7 | directory: / 8 | schedule: 9 | interval: daily 10 | target-branch: "main" 11 | allow: 12 | - dependency-type: direct 13 | versioning-strategy: increase-if-necessary 14 | commit-message: 15 | prefix: deps 16 | prefix-development: chore 17 | labels: 18 | - "Dependencies" 19 | open-pull-requests-limit: 10 20 | - package-ecosystem: npm 21 | directory: / 22 | schedule: 23 | interval: daily 24 | target-branch: "release/v5" 25 | allow: 26 | - dependency-type: direct 27 | dependency-name: "@npmcli/template-oss" 28 | versioning-strategy: increase-if-necessary 29 | commit-message: 30 | prefix: deps 31 | prefix-development: chore 32 | labels: 33 | - "Dependencies" 34 | - "Backport" 35 | - "release/v5" 36 | open-pull-requests-limit: 10 37 | - package-ecosystem: npm 38 | directory: / 39 | schedule: 40 | interval: daily 41 | target-branch: "release/v6" 42 | allow: 43 | - dependency-type: direct 44 | dependency-name: "@npmcli/template-oss" 45 | versioning-strategy: increase-if-necessary 46 | commit-message: 47 | prefix: deps 48 | prefix-development: chore 49 | labels: 50 | - "Dependencies" 51 | - "Backport" 52 | - "release/v6" 53 | open-pull-requests-limit: 10 54 | -------------------------------------------------------------------------------- /test/functions/compare.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const compare = require('../../functions/compare.js') 5 | const comparisons = require('../fixtures/comparisons.js') 6 | const equality = require('../fixtures/equality.js') 7 | const SemVer = require('../../classes/semver.js') 8 | 9 | test('comparison tests', t => { 10 | t.plan(comparisons.length) 11 | comparisons.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 12 | t.plan(4) 13 | t.equal(compare(v0, v1, loose), 1, `compare('${v0}', '${v1}')`) 14 | t.equal(compare(v1, v0, loose), -1, `compare('${v1}', '${v0}')`) 15 | t.equal(compare(v0, v0, loose), 0, `compare('${v0}', '${v0}')`) 16 | t.equal(compare(v1, v1, loose), 0, `compare('${v1}', '${v1}')`) 17 | })) 18 | }) 19 | 20 | test('equality tests', (t) => { 21 | // [version1, version2] 22 | // version1 should be equivalent to version2 23 | t.plan(equality.length) 24 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 25 | t.plan(5) 26 | t.equal(compare(v0, v1, loose), 0, `${v0} ${v1}`) 27 | t.equal(compare(v1, v0, loose), 0, `${v1} ${v0}`) 28 | t.equal(compare(v0, v0, loose), 0, `${v0} ${v0}`) 29 | t.equal(compare(v1, v1, loose), 0, `${v1} ${v1}`) 30 | 31 | // also test with an object. they are === because obj.version matches 32 | t.equal(compare(new SemVer(v0, { loose: loose }), 33 | new SemVer(v1, { loose: loose })), 0, 34 | `compare(${v0}, ${v1}) object`) 35 | })) 36 | t.end() 37 | }) 38 | -------------------------------------------------------------------------------- /test/fixtures/comparator-intersection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // c0, c1, expected intersection, includePrerelease 4 | module.exports = [ 5 | // One is a Version 6 | ['1.3.0', '>=1.3.0', true], 7 | ['1.3.0', '>1.3.0', false], 8 | ['>=1.3.0', '1.3.0', true], 9 | ['>1.3.0', '1.3.0', false], 10 | // Same direction increasing 11 | ['>1.3.0', '>1.2.0', true], 12 | ['>1.2.0', '>1.3.0', true], 13 | ['>=1.2.0', '>1.3.0', true], 14 | ['>1.2.0', '>=1.3.0', true], 15 | // Same direction decreasing 16 | ['<1.3.0', '<1.2.0', true], 17 | ['<1.2.0', '<1.3.0', true], 18 | ['<=1.2.0', '<1.3.0', true], 19 | ['<1.2.0', '<=1.3.0', true], 20 | // Different directions, same semver and inclusive operator 21 | ['>=1.3.0', '<=1.3.0', true], 22 | ['>=v1.3.0', '<=1.3.0', true], 23 | ['>=1.3.0', '>=1.3.0', true], 24 | ['<=1.3.0', '<=1.3.0', true], 25 | ['<=1.3.0', '<=v1.3.0', true], 26 | ['>1.3.0', '<=1.3.0', false], 27 | ['>=1.3.0', '<1.3.0', false], 28 | // Opposite matching directions 29 | ['>1.0.0', '<2.0.0', true], 30 | ['>=1.0.0', '<2.0.0', true], 31 | ['>=1.0.0', '<=2.0.0', true], 32 | ['>1.0.0', '<=2.0.0', true], 33 | ['<=2.0.0', '>1.0.0', true], 34 | ['<=1.0.0', '>=2.0.0', false], 35 | ['', '', true], 36 | ['', '>1.0.0', true], 37 | ['<=2.0.0', '', true], 38 | ['<0.0.0', '<0.1.0', false], 39 | ['<0.1.0', '<0.0.0', false], 40 | ['<0.0.0-0', '<0.1.0', false], 41 | ['<0.1.0', '<0.0.0-0', false], 42 | ['<0.0.0-0', '<0.1.0', false, true], 43 | ['<0.1.0', '<0.0.0-0', false, true], 44 | ] 45 | -------------------------------------------------------------------------------- /ranges/simplify.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // given a set of versions and a range, create a "simplified" range 4 | // that includes the same versions that the original range does 5 | // If the original range is shorter than the simplified one, return that. 6 | const satisfies = require('../functions/satisfies.js') 7 | const compare = require('../functions/compare.js') 8 | module.exports = (versions, range, options) => { 9 | const set = [] 10 | let first = null 11 | let prev = null 12 | const v = versions.sort((a, b) => compare(a, b, options)) 13 | for (const version of v) { 14 | const included = satisfies(version, range, options) 15 | if (included) { 16 | prev = version 17 | if (!first) { 18 | first = version 19 | } 20 | } else { 21 | if (prev) { 22 | set.push([first, prev]) 23 | } 24 | prev = null 25 | first = null 26 | } 27 | } 28 | if (first) { 29 | set.push([first, null]) 30 | } 31 | 32 | const ranges = [] 33 | for (const [min, max] of set) { 34 | if (min === max) { 35 | ranges.push(min) 36 | } else if (!max && min === v[0]) { 37 | ranges.push('*') 38 | } else if (!max) { 39 | ranges.push(`>=${min}`) 40 | } else if (min === v[0]) { 41 | ranges.push(`<=${max}`) 42 | } else { 43 | ranges.push(`${min} - ${max}`) 44 | } 45 | } 46 | const simplified = ranges.join(' || ') 47 | const original = typeof range.raw === 'string' ? range.raw : String(range) 48 | return simplified.length < original.length ? simplified : range 49 | } 50 | -------------------------------------------------------------------------------- /test/functions/inc.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const inc = require('../../functions/inc') 5 | const parse = require('../../functions/parse') 6 | const increments = require('../fixtures/increments.js') 7 | 8 | test('increment versions test', (t) => { 9 | increments.forEach(([pre, what, wanted, options, id, base]) => { 10 | const found = inc(pre, what, options, id, base) 11 | const cmd = `inc(${pre}, ${what}, ${id}, ${base})` 12 | t.equal(found, wanted, `${cmd} === ${wanted}`) 13 | 14 | const parsed = parse(pre, options) 15 | const parsedAsInput = parse(pre, options) 16 | if (wanted) { 17 | parsed.inc(what, id, base) 18 | t.equal(parsed.version, wanted, `${cmd} object version updated`) 19 | if (parsed.build.length) { 20 | t.equal( 21 | parsed.raw, 22 | `${wanted}+${parsed.build.join('.')}`, 23 | `${cmd} object raw field updated with build` 24 | ) 25 | } else { 26 | t.equal(parsed.raw, wanted, `${cmd} object raw field updated`) 27 | } 28 | 29 | const preIncObject = JSON.stringify(parsedAsInput) 30 | inc(parsedAsInput, what, options, id, base) 31 | const postIncObject = JSON.stringify(parsedAsInput) 32 | t.equal( 33 | postIncObject, 34 | preIncObject, 35 | `${cmd} didn't modify its input` 36 | ) 37 | } else if (parsed) { 38 | t.throws(() => { 39 | parsed.inc(what, id, base) 40 | }) 41 | } else { 42 | t.equal(parsed, null) 43 | } 44 | }) 45 | 46 | t.end() 47 | }) 48 | -------------------------------------------------------------------------------- /test/fixtures/equality.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [version1, version2] 4 | // version1 should be equivalent to version2 5 | module.exports = [ 6 | ['1.2.3', 'v1.2.3', true], 7 | ['1.2.3', '=1.2.3', true], 8 | ['1.2.3', 'v 1.2.3', true], 9 | ['1.2.3', '= 1.2.3', true], 10 | ['1.2.3', ' v1.2.3', true], 11 | ['1.2.3', ' =1.2.3', true], 12 | ['1.2.3', ' v 1.2.3', true], 13 | ['1.2.3', ' = 1.2.3', true], 14 | ['1.2.3-0', 'v1.2.3-0', true], 15 | ['1.2.3-0', '=1.2.3-0', true], 16 | ['1.2.3-0', 'v 1.2.3-0', true], 17 | ['1.2.3-0', '= 1.2.3-0', true], 18 | ['1.2.3-0', ' v1.2.3-0', true], 19 | ['1.2.3-0', ' =1.2.3-0', true], 20 | ['1.2.3-0', ' v 1.2.3-0', true], 21 | ['1.2.3-0', ' = 1.2.3-0', true], 22 | ['1.2.3-1', 'v1.2.3-1', true], 23 | ['1.2.3-1', '=1.2.3-1', true], 24 | ['1.2.3-1', 'v 1.2.3-1', true], 25 | ['1.2.3-1', '= 1.2.3-1', true], 26 | ['1.2.3-1', ' v1.2.3-1', true], 27 | ['1.2.3-1', ' =1.2.3-1', true], 28 | ['1.2.3-1', ' v 1.2.3-1', true], 29 | ['1.2.3-1', ' = 1.2.3-1', true], 30 | ['1.2.3-beta', 'v1.2.3-beta', true], 31 | ['1.2.3-beta', '=1.2.3-beta', true], 32 | ['1.2.3-beta', 'v 1.2.3-beta', true], 33 | ['1.2.3-beta', '= 1.2.3-beta', true], 34 | ['1.2.3-beta', ' v1.2.3-beta', true], 35 | ['1.2.3-beta', ' =1.2.3-beta', true], 36 | ['1.2.3-beta', ' v 1.2.3-beta', true], 37 | ['1.2.3-beta', ' = 1.2.3-beta', true], 38 | ['1.2.3-beta+build', ' = 1.2.3-beta+otherbuild', true], 39 | ['1.2.3+build', ' = 1.2.3+otherbuild', true], 40 | ['1.2.3-beta+build', '1.2.3-beta+otherbuild'], 41 | ['1.2.3+build', '1.2.3+otherbuild'], 42 | [' v1.2.3+build', '1.2.3+otherbuild'], 43 | ] 44 | -------------------------------------------------------------------------------- /functions/diff.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const parse = require('./parse.js') 4 | 5 | const diff = (version1, version2) => { 6 | const v1 = parse(version1, null, true) 7 | const v2 = parse(version2, null, true) 8 | const comparison = v1.compare(v2) 9 | 10 | if (comparison === 0) { 11 | return null 12 | } 13 | 14 | const v1Higher = comparison > 0 15 | const highVersion = v1Higher ? v1 : v2 16 | const lowVersion = v1Higher ? v2 : v1 17 | const highHasPre = !!highVersion.prerelease.length 18 | const lowHasPre = !!lowVersion.prerelease.length 19 | 20 | if (lowHasPre && !highHasPre) { 21 | // Going from prerelease -> no prerelease requires some special casing 22 | 23 | // If the low version has only a major, then it will always be a major 24 | // Some examples: 25 | // 1.0.0-1 -> 1.0.0 26 | // 1.0.0-1 -> 1.1.1 27 | // 1.0.0-1 -> 2.0.0 28 | if (!lowVersion.patch && !lowVersion.minor) { 29 | return 'major' 30 | } 31 | 32 | // If the main part has no difference 33 | if (lowVersion.compareMain(highVersion) === 0) { 34 | if (lowVersion.minor && !lowVersion.patch) { 35 | return 'minor' 36 | } 37 | return 'patch' 38 | } 39 | } 40 | 41 | // add the `pre` prefix if we are going to a prerelease version 42 | const prefix = highHasPre ? 'pre' : '' 43 | 44 | if (v1.major !== v2.major) { 45 | return prefix + 'major' 46 | } 47 | 48 | if (v1.minor !== v2.minor) { 49 | return prefix + 'minor' 50 | } 51 | 52 | if (v1.patch !== v2.patch) { 53 | return prefix + 'patch' 54 | } 55 | 56 | // high and low are preleases 57 | return 'prerelease' 58 | } 59 | 60 | module.exports = diff 61 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Bug 4 | description: File a bug/issue 5 | title: "[BUG] " 6 | labels: [ Bug, Needs Triage ] 7 | 8 | body: 9 | - type: checkboxes 10 | attributes: 11 | label: Is there an existing issue for this? 12 | description: Please [search here](./issues) to see if an issue already exists for your problem. 13 | options: 14 | - label: I have searched the existing issues 15 | required: true 16 | - type: textarea 17 | attributes: 18 | label: Current Behavior 19 | description: A clear & concise description of what you're experiencing. 20 | validations: 21 | required: false 22 | - type: textarea 23 | attributes: 24 | label: Expected Behavior 25 | description: A clear & concise description of what you expected to happen. 26 | validations: 27 | required: false 28 | - type: textarea 29 | attributes: 30 | label: Steps To Reproduce 31 | description: Steps to reproduce the behavior. 32 | value: | 33 | 1. In this environment... 34 | 2. With this config... 35 | 3. Run '...' 36 | 4. See error... 37 | validations: 38 | required: false 39 | - type: textarea 40 | attributes: 41 | label: Environment 42 | description: | 43 | examples: 44 | - **npm**: 7.6.3 45 | - **Node**: 13.14.0 46 | - **OS**: Ubuntu 20.04 47 | - **platform**: Macbook Pro 48 | value: | 49 | - npm: 50 | - Node: 51 | - OS: 52 | - platform: 53 | validations: 54 | required: false 55 | -------------------------------------------------------------------------------- /.github/actions/create-check/action.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: 'Create Check' 4 | inputs: 5 | name: 6 | required: true 7 | token: 8 | required: true 9 | sha: 10 | required: true 11 | check-name: 12 | default: '' 13 | outputs: 14 | check-id: 15 | value: ${{ steps.create-check.outputs.check_id }} 16 | runs: 17 | using: "composite" 18 | steps: 19 | - name: Get Workflow Job 20 | uses: actions/github-script@v7 21 | id: workflow 22 | env: 23 | JOB_NAME: "${{ inputs.name }}" 24 | SHA: "${{ inputs.sha }}" 25 | with: 26 | result-encoding: string 27 | script: | 28 | const { repo: { owner, repo}, runId, serverUrl } = context 29 | const { JOB_NAME, SHA } = process.env 30 | 31 | const job = await github.rest.actions.listJobsForWorkflowRun({ 32 | owner, 33 | repo, 34 | run_id: runId, 35 | per_page: 100 36 | }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME))) 37 | 38 | return [ 39 | `This check is assosciated with ${serverUrl}/${owner}/${repo}/commit/${SHA}.`, 40 | 'Run logs:', 41 | job?.html_url || `could not be found for a job ending with: "${JOB_NAME}"`, 42 | ].join(' ') 43 | - name: Create Check 44 | uses: LouisBrunner/checks-action@v1.6.0 45 | id: create-check 46 | with: 47 | token: ${{ inputs.token }} 48 | sha: ${{ inputs.sha }} 49 | status: in_progress 50 | name: ${{ inputs.check-name || inputs.name }} 51 | output: | 52 | {"summary":"${{ steps.workflow.outputs.result }}"} 53 | -------------------------------------------------------------------------------- /.github/workflows/pull-request.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Pull Request 4 | 5 | on: 6 | pull_request: 7 | types: 8 | - opened 9 | - reopened 10 | - edited 11 | - synchronize 12 | 13 | permissions: 14 | contents: read 15 | 16 | jobs: 17 | commitlint: 18 | name: Lint Commits 19 | if: github.repository_owner == 'npm' 20 | runs-on: ubuntu-latest 21 | defaults: 22 | run: 23 | shell: bash 24 | steps: 25 | - name: Checkout 26 | uses: actions/checkout@v4 27 | with: 28 | fetch-depth: 0 29 | - name: Setup Git User 30 | run: | 31 | git config --global user.email "npm-cli+bot@github.com" 32 | git config --global user.name "npm CLI robot" 33 | - name: Setup Node 34 | uses: actions/setup-node@v4 35 | id: node 36 | with: 37 | node-version: 22.x 38 | check-latest: contains('22.x', '.x') 39 | - name: Install Latest npm 40 | uses: ./.github/actions/install-latest-npm 41 | with: 42 | node: ${{ steps.node.outputs.node-version }} 43 | - name: Install Dependencies 44 | run: npm i --ignore-scripts --no-audit --no-fund 45 | - name: Run Commitlint on Commits 46 | id: commit 47 | continue-on-error: true 48 | run: npx --offline commitlint -V --from 'origin/${{ github.base_ref }}' --to ${{ github.event.pull_request.head.sha }} 49 | - name: Run Commitlint on PR Title 50 | if: steps.commit.outcome == 'failure' 51 | env: 52 | PR_TITLE: ${{ github.event.pull_request.title }} 53 | run: echo "$PR_TITLE" | npx --offline commitlint -V 54 | -------------------------------------------------------------------------------- /ranges/min-version.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const Range = require('../classes/range') 5 | const gt = require('../functions/gt') 6 | 7 | const minVersion = (range, loose) => { 8 | range = new Range(range, loose) 9 | 10 | let minver = new SemVer('0.0.0') 11 | if (range.test(minver)) { 12 | return minver 13 | } 14 | 15 | minver = new SemVer('0.0.0-0') 16 | if (range.test(minver)) { 17 | return minver 18 | } 19 | 20 | minver = null 21 | for (let i = 0; i < range.set.length; ++i) { 22 | const comparators = range.set[i] 23 | 24 | let setMin = null 25 | comparators.forEach((comparator) => { 26 | // Clone to avoid manipulating the comparator's semver object. 27 | const compver = new SemVer(comparator.semver.version) 28 | switch (comparator.operator) { 29 | case '>': 30 | if (compver.prerelease.length === 0) { 31 | compver.patch++ 32 | } else { 33 | compver.prerelease.push(0) 34 | } 35 | compver.raw = compver.format() 36 | /* fallthrough */ 37 | case '': 38 | case '>=': 39 | if (!setMin || gt(compver, setMin)) { 40 | setMin = compver 41 | } 42 | break 43 | case '<': 44 | case '<=': 45 | /* Ignore maximum versions */ 46 | break 47 | /* istanbul ignore next */ 48 | default: 49 | throw new Error(`Unexpected operation: ${comparator.operator}`) 50 | } 51 | }) 52 | if (setMin && (!minver || gt(minver, setMin))) { 53 | minver = setMin 54 | } 55 | } 56 | 57 | if (minver && range.test(minver)) { 58 | return minver 59 | } 60 | 61 | return null 62 | } 63 | module.exports = minVersion 64 | -------------------------------------------------------------------------------- /test/internal/parse-options.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const parseOptions = require('../../internal/parse-options.js') 5 | 6 | t.test('falsey values always empty options object', t => { 7 | t.strictSame(parseOptions(null), {}) 8 | t.strictSame(parseOptions(false), {}) 9 | t.strictSame(parseOptions(undefined), {}) 10 | t.strictSame(parseOptions(), {}) 11 | t.strictSame(parseOptions(0), {}) 12 | t.strictSame(parseOptions(''), {}) 13 | t.end() 14 | }) 15 | 16 | t.test('truthy non-objects always loose mode, for backwards comp', t => { 17 | t.strictSame(parseOptions('hello'), { loose: true }) 18 | t.strictSame(parseOptions(true), { loose: true }) 19 | t.strictSame(parseOptions(1), { loose: true }) 20 | t.end() 21 | }) 22 | 23 | t.test('any object passed is returned', t => { 24 | t.strictSame(parseOptions(/asdf/), /asdf/) 25 | t.strictSame(parseOptions(new Error('hello')), new Error('hello')) 26 | t.strictSame(parseOptions({ loose: true, a: 1, rtl: false }), { loose: true, a: 1, rtl: false }) 27 | t.strictSame(parseOptions({ loose: 1, rtl: 2, includePrerelease: 10 }), { 28 | loose: 1, 29 | rtl: 2, 30 | includePrerelease: 10, 31 | }) 32 | t.strictSame(parseOptions({ loose: true }), { loose: true }) 33 | t.strictSame(parseOptions({ rtl: true }), { rtl: true }) 34 | t.strictSame(parseOptions({ includePrerelease: true }), { includePrerelease: true }) 35 | t.strictSame(parseOptions({ loose: true, rtl: true }), { loose: true, rtl: true }) 36 | t.strictSame(parseOptions({ loose: true, includePrerelease: true }), { 37 | loose: true, 38 | includePrerelease: true, 39 | }) 40 | t.strictSame(parseOptions({ rtl: true, includePrerelease: true }), { 41 | rtl: true, 42 | includePrerelease: true, 43 | }) 44 | t.end() 45 | }) 46 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | repository: 4 | allow_merge_commit: false 5 | allow_rebase_merge: true 6 | allow_squash_merge: true 7 | squash_merge_commit_title: PR_TITLE 8 | squash_merge_commit_message: PR_BODY 9 | delete_branch_on_merge: true 10 | enable_automated_security_fixes: true 11 | enable_vulnerability_alerts: true 12 | 13 | branches: 14 | - name: main 15 | protection: 16 | required_status_checks: null 17 | enforce_admins: true 18 | block_creations: true 19 | required_pull_request_reviews: 20 | required_approving_review_count: 1 21 | require_code_owner_reviews: true 22 | require_last_push_approval: true 23 | dismiss_stale_reviews: true 24 | restrictions: 25 | apps: [] 26 | users: [] 27 | teams: [ "cli-team" ] 28 | - name: release/v5 29 | protection: 30 | required_status_checks: null 31 | enforce_admins: true 32 | block_creations: true 33 | required_pull_request_reviews: 34 | required_approving_review_count: 1 35 | require_code_owner_reviews: true 36 | require_last_push_approval: true 37 | dismiss_stale_reviews: true 38 | restrictions: 39 | apps: [] 40 | users: [] 41 | teams: [ "cli-team" ] 42 | - name: release/v6 43 | protection: 44 | required_status_checks: null 45 | enforce_admins: true 46 | block_creations: true 47 | required_pull_request_reviews: 48 | required_approving_review_count: 1 49 | require_code_owner_reviews: true 50 | require_last_push_approval: true 51 | dismiss_stale_reviews: true 52 | restrictions: 53 | apps: [] 54 | users: [] 55 | teams: [ "cli-team" ] 56 | -------------------------------------------------------------------------------- /test/fixtures/version-gt-range.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [range, version, options] 4 | // Version should be greater than range 5 | module.exports = [ 6 | ['~1.2.2', '1.3.0'], 7 | ['~0.6.1-1', '0.7.1-1'], 8 | ['1.0.0 - 2.0.0', '2.0.1'], 9 | ['1.0.0', '1.0.1-beta1'], 10 | ['1.0.0', '2.0.0'], 11 | ['<=2.0.0', '2.1.1'], 12 | ['<=2.0.0', '3.2.9'], 13 | ['<2.0.0', '2.0.0'], 14 | ['0.1.20 || 1.2.4', '1.2.5'], 15 | ['2.x.x', '3.0.0'], 16 | ['1.2.x', '1.3.0'], 17 | ['1.2.x || 2.x', '3.0.0'], 18 | ['2.*.*', '5.0.1'], 19 | ['1.2.*', '1.3.3'], 20 | ['1.2.* || 2.*', '4.0.0'], 21 | ['2', '3.0.0'], 22 | ['2.3', '2.4.2'], 23 | ['~2.4', '2.5.0'], // >=2.4.0 <2.5.0 24 | ['~2.4', '2.5.5'], 25 | ['~>3.2.1', '3.3.0'], // >=3.2.1 <3.3.0 26 | ['~1', '2.2.3'], // >=1.0.0 <2.0.0 27 | ['~>1', '2.2.4'], 28 | ['~> 1', '3.2.3'], 29 | ['~1.0', '1.1.2'], // >=1.0.0 <1.1.0 30 | ['~ 1.0', '1.1.0'], 31 | ['<1.2', '1.2.0'], 32 | ['< 1.2', '1.2.1'], 33 | ['1', '2.0.0beta', true], 34 | ['~v0.5.4-pre', '0.6.0'], 35 | ['~v0.5.4-pre', '0.6.1-pre'], 36 | ['=0.7.x', '0.8.0'], 37 | ['=0.7.x', '0.8.0-asdf'], 38 | ['<0.7.x', '0.7.0'], 39 | ['1.0.0 - 2.0.0', '2.2.3'], 40 | ['1.0.0', '1.0.1'], 41 | ['<=2.0.0', '3.0.0'], 42 | ['<=2.0.0', '2.9999.9999'], 43 | ['<=2.0.0', '2.2.9'], 44 | ['<2.0.0', '2.9999.9999'], 45 | ['<2.0.0', '2.2.9'], 46 | ['2.x.x', '3.1.3'], 47 | ['1.2.x', '1.3.3'], 48 | ['1.2.x || 2.x', '3.1.3'], 49 | ['2.*.*', '3.1.3'], 50 | ['1.2.* || 2.*', '3.1.3'], 51 | ['2', '3.1.2'], 52 | ['2.3', '2.4.1'], 53 | ['~>3.2.1', '3.3.2'], // >=3.2.1 <3.3.0 54 | ['~>1', '2.2.3'], 55 | ['~1.0', '1.1.0'], // >=1.0.0 <1.1.0 56 | ['<1', '1.0.0'], 57 | ['<1', '1.0.0beta', true], 58 | ['< 1', '1.0.0beta', true], 59 | ['=0.7.x', '0.8.2'], 60 | ['<0.7.x', '0.7.2'], 61 | ['0.7.x', '0.7.2-beta'], 62 | ] 63 | -------------------------------------------------------------------------------- /test/fixtures/version-lt-range.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [range, version, options] 4 | // Version should be less than range 5 | module.exports = [ 6 | ['~1.2.2', '1.2.1'], 7 | ['~0.6.1-1', '0.6.1-0'], 8 | ['1.0.0 - 2.0.0', '0.0.1'], 9 | ['1.0.0-beta.2', '1.0.0-beta.1'], 10 | ['1.0.0', '0.0.0'], 11 | ['>=2.0.0', '1.1.1'], 12 | ['>=2.0.0', '1.2.9'], 13 | ['>2.0.0', '2.0.0'], 14 | ['0.1.20 || 1.2.4', '0.1.5'], 15 | ['2.x.x', '1.0.0'], 16 | ['1.2.x', '1.1.0'], 17 | ['1.2.x || 2.x', '1.0.0'], 18 | ['2.*.*', '1.0.1'], 19 | ['1.2.*', '1.1.3'], 20 | ['1.2.* || 2.*', '1.1.9999'], 21 | ['2', '1.0.0'], 22 | ['2.3', '2.2.2'], 23 | ['~2.4', '2.3.0'], // >=2.4.0 <2.5.0 24 | ['~2.4', '2.3.5'], 25 | ['~>3.2.1', '3.2.0'], // >=3.2.1 <3.3.0 26 | ['~1', '0.2.3'], // >=1.0.0 <2.0.0 27 | ['~>1', '0.2.4'], 28 | ['~> 1', '0.2.3'], 29 | ['~1.0', '0.1.2'], // >=1.0.0 <1.1.0 30 | ['~ 1.0', '0.1.0'], 31 | ['>1.2', '1.2.0'], 32 | ['> 1.2', '1.2.1'], 33 | ['1', '0.0.0beta', true], 34 | ['~v0.5.4-pre', '0.5.4-alpha'], 35 | ['=0.7.x', '0.6.0'], 36 | ['=0.7.x', '0.6.0-asdf'], 37 | ['>=0.7.x', '0.6.0'], 38 | ['1.0.0 - 2.0.0', '0.2.3'], 39 | ['1.0.0', '0.0.1'], 40 | ['>=2.0.0', '1.0.0'], 41 | ['>=2.0.0', '1.9999.9999'], 42 | ['>2.0.0', '1.2.9'], 43 | ['2.x.x', '1.1.3'], 44 | ['1.2.x', '1.1.3'], 45 | ['1.2.x || 2.x', '1.1.3'], 46 | ['2.*.*', '1.1.3'], 47 | ['1.2.* || 2.*', '1.1.3'], 48 | ['2', '1.9999.9999'], 49 | ['2.3', '2.2.1'], 50 | ['~>3.2.1', '2.3.2'], // >=3.2.1 <3.3.0 51 | ['~>1', '0.2.3'], 52 | ['~1.0', '0.0.0'], // >=1.0.0 <1.1.0 53 | ['>1', '1.0.0'], 54 | ['2', '1.0.0beta', true], 55 | ['>1', '1.0.0beta', true], 56 | ['> 1', '1.0.0beta', true], 57 | ['=0.7.x', '0.6.2'], 58 | ['=0.7.x', '0.7.0-asdf'], 59 | ['^1', '1.0.0-0'], 60 | ['>=0.7.x', '0.7.0-asdf'], 61 | ['1', '1.0.0beta', true], 62 | ['>=0.7.x', '0.6.2'], 63 | ['>1.2.3', '1.3.0-alpha'], 64 | ] 65 | -------------------------------------------------------------------------------- /test/integration/whitespace.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Range = require('../../classes/range') 5 | const SemVer = require('../../classes/semver') 6 | const Comparator = require('../../classes/comparator') 7 | const validRange = require('../../ranges/valid') 8 | const minVersion = require('../../ranges/min-version') 9 | const minSatisfying = require('../../ranges/min-satisfying') 10 | const maxSatisfying = require('../../ranges/max-satisfying') 11 | 12 | const wsMedium = ' '.repeat(125) 13 | const wsLarge = ' '.repeat(500000) 14 | const zeroLarge = '0'.repeat(500000) 15 | 16 | test('range with whitespace', (t) => { 17 | // a range with these extra characters would take a few minutes to process if 18 | // any redos susceptible regexes were used. there is a global tap timeout per 19 | // file set in the package.json that will error if this test takes too long. 20 | const r = `1.2.3 ${wsLarge} <1.3.0` 21 | t.equal(new Range(r).range, '1.2.3 <1.3.0') 22 | t.equal(validRange(r), '1.2.3 <1.3.0') 23 | t.equal(minVersion(r).version, '1.2.3') 24 | t.equal(minSatisfying(['1.2.3'], r), '1.2.3') 25 | t.equal(maxSatisfying(['1.2.3'], r), '1.2.3') 26 | t.end() 27 | }) 28 | 29 | test('range with 0', (t) => { 30 | const r = `1.2.3 ${zeroLarge} <1.3.0` 31 | t.throws(() => new Range(r).range) 32 | t.equal(validRange(r), null) 33 | t.throws(() => minVersion(r).version) 34 | t.equal(minSatisfying(['1.2.3'], r), null) 35 | t.equal(maxSatisfying(['1.2.3'], r), null) 36 | t.end() 37 | }) 38 | 39 | test('semver version', (t) => { 40 | const v = `${wsMedium}1.2.3${wsMedium}` 41 | const tooLong = `${wsLarge}1.2.3${wsLarge}` 42 | t.equal(new SemVer(v).version, '1.2.3') 43 | t.throws(() => new SemVer(tooLong)) 44 | t.end() 45 | }) 46 | 47 | test('comparator', (t) => { 48 | const comparator = `${wsLarge}<${wsLarge}1.2.3${wsLarge}` 49 | t.equal(new Comparator(comparator).value, '<1.2.3') 50 | t.end() 51 | }) 52 | -------------------------------------------------------------------------------- /test/ranges/outside.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const outside = require('../../ranges/outside') 5 | const versionGtr = require('../fixtures/version-gt-range') 6 | const versionNotGtr = require('../fixtures/version-not-gt-range') 7 | const versionLtr = require('../fixtures/version-lt-range') 8 | const versionNotLtr = require('../fixtures/version-not-lt-range') 9 | 10 | test('gtr tests', (t) => { 11 | // [range, version, options] 12 | // Version should be greater than range 13 | versionGtr.forEach(([range, version, options = false]) => { 14 | const msg = `outside(${version}, ${range}, > ${options})` 15 | t.ok(outside(version, range, '>', options), msg) 16 | }) 17 | t.end() 18 | }) 19 | 20 | test('ltr tests', (t) => { 21 | // [range, version, options] 22 | // Version should be less than range 23 | versionLtr.forEach(([range, version, options = false]) => { 24 | const msg = `outside(${version}, ${range}, <, ${options})` 25 | t.ok(outside(version, range, '<', options), msg) 26 | }) 27 | t.end() 28 | }) 29 | 30 | test('negative gtr tests', (t) => { 31 | // [range, version, options] 32 | // Version should NOT be greater than range 33 | versionNotGtr.forEach(([range, version, options = false]) => { 34 | const msg = `!outside(${version}, ${range}, > ${options})` 35 | t.notOk(outside(version, range, '>', options), msg) 36 | }) 37 | t.end() 38 | }) 39 | 40 | test('negative ltr tests', (t) => { 41 | // [range, version, options] 42 | // Version should NOT be less than range 43 | versionNotLtr.forEach(([range, version, options = false]) => { 44 | const msg = `!outside(${version}, ${range}, < ${options})` 45 | t.notOk(outside(version, range, '<', options), msg) 46 | }) 47 | t.end() 48 | }) 49 | 50 | test('outside with bad hilo throws', (t) => { 51 | t.throws(() => { 52 | outside('1.2.3', '>1.5.0', 'blerg', true) 53 | }, new TypeError('Must provide a hilo val of "<" or ">"')) 54 | t.end() 55 | }) 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "semver", 3 | "version": "7.7.3", 4 | "description": "The semantic version parser used by npm.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "tap", 8 | "snap": "tap", 9 | "lint": "npm run eslint", 10 | "postlint": "template-oss-check", 11 | "lintfix": "npm run eslint -- --fix", 12 | "posttest": "npm run lint", 13 | "template-oss-apply": "template-oss-apply --force", 14 | "eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\"" 15 | }, 16 | "devDependencies": { 17 | "@npmcli/eslint-config": "^6.0.0", 18 | "@npmcli/template-oss": "4.28.1", 19 | "benchmark": "^2.1.4", 20 | "tap": "^16.0.0" 21 | }, 22 | "license": "ISC", 23 | "repository": { 24 | "type": "git", 25 | "url": "git+https://github.com/npm/node-semver.git" 26 | }, 27 | "bin": { 28 | "semver": "bin/semver.js" 29 | }, 30 | "files": [ 31 | "bin/", 32 | "lib/", 33 | "classes/", 34 | "functions/", 35 | "internal/", 36 | "ranges/", 37 | "index.js", 38 | "preload.js", 39 | "range.bnf" 40 | ], 41 | "tap": { 42 | "timeout": 30, 43 | "coverage-map": "map.js", 44 | "nyc-arg": [ 45 | "--exclude", 46 | "tap-snapshots/**" 47 | ] 48 | }, 49 | "engines": { 50 | "node": ">=10" 51 | }, 52 | "author": "GitHub Inc.", 53 | "templateOSS": { 54 | "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.", 55 | "version": "4.28.1", 56 | "engines": ">=10", 57 | "distPaths": [ 58 | "classes/", 59 | "functions/", 60 | "internal/", 61 | "ranges/", 62 | "index.js", 63 | "preload.js", 64 | "range.bnf" 65 | ], 66 | "allowPaths": [ 67 | "/classes/", 68 | "/functions/", 69 | "/internal/", 70 | "/ranges/", 71 | "/index.js", 72 | "/preload.js", 73 | "/range.bnf", 74 | "/benchmarks" 75 | ], 76 | "publish": "true" 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /.github/actions/install-latest-npm/action.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: 'Install Latest npm' 4 | description: 'Install the latest version of npm compatible with the Node version' 5 | inputs: 6 | node: 7 | description: 'Current Node version' 8 | required: true 9 | runs: 10 | using: "composite" 11 | steps: 12 | # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows 13 | - name: Update Windows npm 14 | if: | 15 | runner.os == 'Windows' && ( 16 | startsWith(inputs.node, 'v10.') || 17 | startsWith(inputs.node, 'v12.') || 18 | startsWith(inputs.node, 'v14.') 19 | ) 20 | shell: cmd 21 | run: | 22 | curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz 23 | tar xf npm-7.5.4.tgz 24 | cd package 25 | node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz 26 | cd .. 27 | rmdir /s /q package 28 | - name: Install Latest npm 29 | shell: bash 30 | env: 31 | NODE_VERSION: ${{ inputs.node }} 32 | working-directory: ${{ runner.temp }} 33 | run: | 34 | MATCH="" 35 | SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6") 36 | 37 | echo "node@$NODE_VERSION" 38 | 39 | for SPEC in ${SPECS[@]}; do 40 | ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node') 41 | echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)" 42 | 43 | if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then 44 | MATCH=$SPEC 45 | echo "Found compatible version: npm@$MATCH" 46 | break 47 | fi 48 | done 49 | 50 | if [ -z $MATCH ]; then 51 | echo "Could not find a compatible version of npm for node@$NODE_VERSION" 52 | exit 1 53 | fi 54 | 55 | npm i --prefer-online --no-fund --no-audit -g npm@$MATCH 56 | - name: npm Version 57 | shell: bash 58 | run: npm -v 59 | -------------------------------------------------------------------------------- /test/functions/cmp.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const cmp = require('../../functions/cmp') 5 | const comparisons = require('../fixtures/comparisons.js') 6 | const equality = require('../fixtures/equality.js') 7 | const SemVer = require('../../classes/semver') 8 | 9 | test('invalid cmp usage', (t) => { 10 | t.throws(() => { 11 | cmp('1.2.3', 'a frog', '4.5.6') 12 | }, new TypeError('Invalid operator: a frog')) 13 | t.end() 14 | }) 15 | 16 | test('comparison tests', t => { 17 | t.plan(comparisons.length) 18 | comparisons.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 19 | t.plan(8) 20 | t.ok(cmp(v0, '>', v1, loose), `cmp('${v0}' > '${v1}')`) 21 | t.ok(cmp(v1, '<', v0, loose), `cmp('${v1}' < '${v0}')`) 22 | t.ok(!cmp(v1, '>', v0, loose), `!cmp('${v1}' > '${v0}')`) 23 | t.ok(!cmp(v0, '<', v1, loose), `!cmp('${v0}' < '${v1}')`) 24 | t.ok(cmp(v1, '==', v1, loose), `cmp('${v1}' == '${v1}')`) 25 | t.ok(cmp(v0, '>=', v1, loose), `cmp('${v0}' >= '${v1}')`) 26 | t.ok(cmp(v1, '<=', v0, loose), `cmp('${v1}' <= '${v0}')`) 27 | t.ok(cmp(v0, '!=', v1, loose), `cmp('${v0}' != '${v1}')`) 28 | })) 29 | }) 30 | 31 | test('equality tests', t => { 32 | t.plan(equality.length) 33 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 34 | t.plan(8) 35 | t.ok(cmp(v0, '', v1, loose), `cmp(${v0} "" ${v1})`) 36 | t.ok(cmp(v0, '=', v1, loose), `cmp(${v0}=${v1})`) 37 | t.ok(cmp(v0, '==', v1, loose), `cmp(${v0}==${v1})`) 38 | t.ok(!cmp(v0, '!=', v1, loose), `!cmp(${v0}!=${v1})`) 39 | t.ok(!cmp(v0, '===', v1, loose), `!cmp(${v0}===${v1})`) 40 | 41 | // also test with an object. they are === because obj.version matches 42 | t.ok(cmp(new SemVer(v0, { loose: loose }), '===', 43 | new SemVer(v1, { loose: loose })), 44 | `!cmp(${v0}===${v1}) object`) 45 | 46 | t.ok(cmp(v0, '!==', v1, loose), `cmp(${v0}!==${v1})`) 47 | 48 | t.ok(!cmp(new SemVer(v0, loose), '!==', new SemVer(v1, loose)), 49 | `cmp(${v0}!==${v1}) object`) 50 | })) 51 | }) 52 | -------------------------------------------------------------------------------- /test/classes/comparator.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Comparator = require('../../classes/comparator') 5 | const comparatorIntersection = require('../fixtures/comparator-intersection.js') 6 | 7 | test('comparator testing', t => { 8 | const c = new Comparator('>=1.2.3') 9 | t.ok(c.test('1.2.4')) 10 | const c2 = new Comparator(c) 11 | t.ok(c2.test('1.2.4')) 12 | const c3 = new Comparator(c, true) 13 | t.ok(c3.test('1.2.4')) 14 | // test an invalid version, should not throw 15 | const c4 = new Comparator(c) 16 | t.notOk(c4.test('not a version string')) 17 | t.end() 18 | }) 19 | 20 | test('tostrings', (t) => { 21 | t.equal(new Comparator('>= v1.2.3').toString(), '>=1.2.3') 22 | t.end() 23 | }) 24 | 25 | test('intersect comparators', (t) => { 26 | t.plan(comparatorIntersection.length) 27 | comparatorIntersection.forEach(([c0, c1, expect, includePrerelease]) => 28 | t.test(`${c0} ${c1} ${expect}`, t => { 29 | const comp0 = new Comparator(c0) 30 | const comp1 = new Comparator(c1) 31 | 32 | t.equal(comp0.intersects(comp1, { includePrerelease }), expect, 33 | `${c0} intersects ${c1}`) 34 | 35 | t.equal(comp1.intersects(comp0, { includePrerelease }), expect, 36 | `${c1} intersects ${c0}`) 37 | t.end() 38 | })) 39 | }) 40 | 41 | test('intersect demands another comparator', t => { 42 | const c = new Comparator('>=1.2.3') 43 | t.throws(() => c.intersects(), new TypeError('a Comparator is required')) 44 | t.end() 45 | }) 46 | 47 | test('ANY matches anything', t => { 48 | const c = new Comparator('') 49 | t.ok(c.test('1.2.3'), 'ANY matches anything') 50 | const c1 = new Comparator('>=1.2.3') 51 | const ANY = Comparator.ANY 52 | t.ok(c1.test(ANY), 'anything matches ANY') 53 | t.end() 54 | }) 55 | 56 | test('invalid comparator parse throws', t => { 57 | t.throws(() => new Comparator('foo bar baz'), 58 | new TypeError('Invalid comparator: foo bar baz')) 59 | t.end() 60 | }) 61 | 62 | test('= is ignored', t => { 63 | t.match(new Comparator('=1.2.3'), new Comparator('1.2.3')) 64 | t.end() 65 | }) 66 | -------------------------------------------------------------------------------- /test/fixtures/range-intersection.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // r0, r1, expected intersection 4 | module.exports = [ 5 | ['1.3.0 || <1.0.0 >2.0.0', '1.3.0 || <1.0.0 >2.0.0', true], 6 | ['<1.0.0 >2.0.0', '>0.0.0', false], 7 | ['>0.0.0', '<1.0.0 >2.0.0', false], 8 | ['<1.0.0 >2.0.0', '>1.4.0 <1.6.0', false], 9 | ['<1.0.0 >2.0.0', '>1.4.0 <1.6.0 || 2.0.0', false], 10 | ['>1.0.0 <=2.0.0', '2.0.0', true], 11 | ['<1.0.0 >=2.0.0', '2.1.0', false], 12 | ['<1.0.0 >=2.0.0', '>1.4.0 <1.6.0 || 2.0.0', false], 13 | ['1.5.x', '<1.5.0 || >=1.6.0', false], 14 | ['<1.5.0 || >=1.6.0', '1.5.x', false], 15 | ['<1.6.16 || >=1.7.0 <1.7.11 || >=1.8.0 <1.8.2', 16 | '>=1.6.16 <1.7.0 || >=1.7.11 <1.8.0 || >=1.8.2', false], 17 | ['<=1.6.16 || >=1.7.0 <1.7.11 || >=1.8.0 <1.8.2', 18 | '>=1.6.16 <1.7.0 || >=1.7.11 <1.8.0 || >=1.8.2', true], 19 | ['>=1.0.0', '<=1.0.0', true], 20 | ['>1.0.0 <1.0.0', '<=0.0.0', false], 21 | ['*', '0.0.1', true], 22 | ['*', '>=1.0.0', true], 23 | ['*', '>1.0.0', true], 24 | ['*', '~1.0.0', true], 25 | ['*', '<1.6.0', true], 26 | ['*', '<=1.6.0', true], 27 | ['1.*', '0.0.1', false], 28 | ['1.*', '2.0.0', false], 29 | ['1.*', '1.0.0', true], 30 | ['1.*', '<2.0.0', true], 31 | ['1.*', '>1.0.0', true], 32 | ['1.*', '<=1.0.0', true], 33 | ['1.*', '^1.0.0', true], 34 | ['1.0.*', '0.0.1', false], 35 | ['1.0.*', '<0.0.1', false], 36 | ['1.0.*', '>0.0.1', true], 37 | ['*', '1.3.0 || <1.0.0 >2.0.0', true], 38 | ['1.3.0 || <1.0.0 >2.0.0', '*', true], 39 | ['1.*', '1.3.0 || <1.0.0 >2.0.0', true], 40 | ['x', '0.0.1', true], 41 | ['x', '>=1.0.0', true], 42 | ['x', '>1.0.0', true], 43 | ['x', '~1.0.0', true], 44 | ['x', '<1.6.0', true], 45 | ['x', '<=1.6.0', true], 46 | ['1.x', '0.0.1', false], 47 | ['1.x', '2.0.0', false], 48 | ['1.x', '1.0.0', true], 49 | ['1.x', '<2.0.0', true], 50 | ['1.x', '>1.0.0', true], 51 | ['1.x', '<=1.0.0', true], 52 | ['1.x', '^1.0.0', true], 53 | ['1.0.x', '0.0.1', false], 54 | ['1.0.x', '<0.0.1', false], 55 | ['1.0.x', '>0.0.1', true], 56 | ['x', '1.3.0 || <1.0.0 >2.0.0', true], 57 | ['1.3.0 || <1.0.0 >2.0.0', 'x', true], 58 | ['1.x', '1.3.0 || <1.0.0 >2.0.0', true], 59 | ['*', '*', true], 60 | ['x', '', true], 61 | ] 62 | -------------------------------------------------------------------------------- /test/functions/diff.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const diff = require('../../functions/diff') 5 | 6 | test('diff versions test', (t) => { 7 | // [version1, version2, result] 8 | // diff(version1, version2) -> result 9 | [ 10 | ['1.2.3', '0.2.3', 'major'], 11 | ['0.2.3', '1.2.3', 'major'], 12 | ['1.4.5', '0.2.3', 'major'], 13 | ['1.2.3', '2.0.0-pre', 'premajor'], 14 | ['2.0.0-pre', '1.2.3', 'premajor'], 15 | ['1.2.3', '1.3.3', 'minor'], 16 | ['1.0.1', '1.1.0-pre', 'preminor'], 17 | ['1.2.3', '1.2.4', 'patch'], 18 | ['1.2.3', '1.2.4-pre', 'prepatch'], 19 | ['0.0.1', '0.0.1-pre', 'patch'], 20 | ['0.0.1', '0.0.1-pre-2', 'patch'], 21 | ['1.1.0', '1.1.0-pre', 'minor'], 22 | ['1.1.0-pre-1', '1.1.0-pre-2', 'prerelease'], 23 | ['1.0.0', '1.0.0', null], 24 | ['1.0.0-1', '1.0.0-1', null], 25 | ['0.0.2-1', '0.0.2', 'patch'], 26 | ['0.0.2-1', '0.0.3', 'patch'], 27 | ['0.0.2-1', '0.1.0', 'minor'], 28 | ['0.0.2-1', '1.0.0', 'major'], 29 | ['0.1.0-1', '0.1.0', 'minor'], 30 | ['1.0.0-1', '1.0.0', 'major'], 31 | ['1.0.0-1', '1.1.1', 'major'], 32 | ['1.0.0-1', '2.1.1', 'major'], 33 | ['1.0.1-1', '1.0.1', 'patch'], 34 | ['0.0.0-1', '0.0.0', 'major'], 35 | ['1.0.0-1', '2.0.0', 'major'], 36 | ['1.0.0-1', '2.0.0-1', 'premajor'], 37 | ['1.0.0-1', '1.1.0-1', 'preminor'], 38 | ['1.0.0-1', '1.0.1-1', 'prepatch'], 39 | ['1.7.2-1', '1.8.1', 'minor'], 40 | ['1.1.1-pre', '2.1.1-pre', 'premajor'], 41 | ['1.1.1-pre', '2.1.1', 'major'], 42 | ['1.2.3-1', '1.2.3', 'patch'], 43 | ['1.4.0-1', '2.3.5', 'major'], 44 | ['1.6.1-5', '1.7.2', 'minor'], 45 | ['2.0.0-1', '2.1.1', 'major'], 46 | ].forEach((v) => { 47 | const version1 = v[0] 48 | const version2 = v[1] 49 | const wanted = v[2] 50 | const found = diff(version1, version2) 51 | const cmd = `diff(${version1}, ${version2})` 52 | t.equal(found, wanted, `${cmd} === ${wanted}`) 53 | }) 54 | 55 | t.end() 56 | }) 57 | 58 | test('throws on bad version', (t) => { 59 | t.throws(() => { 60 | diff('bad', '1.2.3') 61 | }, { 62 | message: 'Invalid Version: bad', 63 | name: 'TypeError', 64 | }) 65 | t.end() 66 | }) 67 | -------------------------------------------------------------------------------- /functions/coerce.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const parse = require('./parse') 5 | const { safeRe: re, t } = require('../internal/re') 6 | 7 | const coerce = (version, options) => { 8 | if (version instanceof SemVer) { 9 | return version 10 | } 11 | 12 | if (typeof version === 'number') { 13 | version = String(version) 14 | } 15 | 16 | if (typeof version !== 'string') { 17 | return null 18 | } 19 | 20 | options = options || {} 21 | 22 | let match = null 23 | if (!options.rtl) { 24 | match = version.match(options.includePrerelease ? re[t.COERCEFULL] : re[t.COERCE]) 25 | } else { 26 | // Find the right-most coercible string that does not share 27 | // a terminus with a more left-ward coercible string. 28 | // Eg, '1.2.3.4' wants to coerce '2.3.4', not '3.4' or '4' 29 | // With includePrerelease option set, '1.2.3.4-rc' wants to coerce '2.3.4-rc', not '2.3.4' 30 | // 31 | // Walk through the string checking with a /g regexp 32 | // Manually set the index so as to pick up overlapping matches. 33 | // Stop when we get a match that ends at the string end, since no 34 | // coercible string can be more right-ward without the same terminus. 35 | const coerceRtlRegex = options.includePrerelease ? re[t.COERCERTLFULL] : re[t.COERCERTL] 36 | let next 37 | while ((next = coerceRtlRegex.exec(version)) && 38 | (!match || match.index + match[0].length !== version.length) 39 | ) { 40 | if (!match || 41 | next.index + next[0].length !== match.index + match[0].length) { 42 | match = next 43 | } 44 | coerceRtlRegex.lastIndex = next.index + next[1].length + next[2].length 45 | } 46 | // leave it in a clean state 47 | coerceRtlRegex.lastIndex = -1 48 | } 49 | 50 | if (match === null) { 51 | return null 52 | } 53 | 54 | const major = match[2] 55 | const minor = match[3] || '0' 56 | const patch = match[4] || '0' 57 | const prerelease = options.includePrerelease && match[5] ? `-${match[5]}` : '' 58 | const build = options.includePrerelease && match[6] ? `+${match[6]}` : '' 59 | 60 | return parse(`${major}.${minor}.${patch}${prerelease}${build}`, options) 61 | } 62 | module.exports = coerce 63 | -------------------------------------------------------------------------------- /test/ranges/min-version.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const minVersion = require('../../ranges/min-version') 5 | 6 | test('minimum version in range tests', (t) => { 7 | // [range, minimum, loose] 8 | [ 9 | // Stars 10 | ['*', '0.0.0'], 11 | ['* || >=2', '0.0.0'], 12 | ['>=2 || *', '0.0.0'], 13 | ['>2 || *', '0.0.0'], 14 | 15 | // equal 16 | ['1.0.0', '1.0.0'], 17 | ['1.0', '1.0.0'], 18 | ['1.0.x', '1.0.0'], 19 | ['1.0.*', '1.0.0'], 20 | ['1', '1.0.0'], 21 | ['1.x.x', '1.0.0'], 22 | ['1.x.x', '1.0.0'], 23 | ['1.*.x', '1.0.0'], 24 | ['1.x.*', '1.0.0'], 25 | ['1.x', '1.0.0'], 26 | ['1.*', '1.0.0'], 27 | ['=1.0.0', '1.0.0'], 28 | 29 | // Tilde 30 | ['~1.1.1', '1.1.1'], 31 | ['~1.1.1-beta', '1.1.1-beta'], 32 | ['~1.1.1 || >=2', '1.1.1'], 33 | 34 | // Carot 35 | ['^1.1.1', '1.1.1'], 36 | ['^1.1.1-beta', '1.1.1-beta'], 37 | ['^1.1.1 || >=2', '1.1.1'], 38 | ['^2.16.2 ^2.16', '2.16.2'], 39 | 40 | // '-' operator 41 | ['1.1.1 - 1.8.0', '1.1.1'], 42 | ['1.1 - 1.8.0', '1.1.0'], 43 | 44 | // Less / less or equal 45 | ['<2', '0.0.0'], 46 | ['<0.0.0-beta', '0.0.0-0'], 47 | ['<0.0.1-beta', '0.0.0'], 48 | ['<2 || >4', '0.0.0'], 49 | ['>4 || <2', '0.0.0'], 50 | ['<=2 || >=4', '0.0.0'], 51 | ['>=4 || <=2', '0.0.0'], 52 | ['<0.0.0-beta >0.0.0-alpha', '0.0.0-alpha.0'], 53 | ['>0.0.0-alpha <0.0.0-beta', '0.0.0-alpha.0'], 54 | 55 | // Greater than or equal 56 | ['>=1.1.1 <2 || >=2.2.2 <2', '1.1.1'], 57 | ['>=2.2.2 <2 || >=1.1.1 <2', '1.1.1'], 58 | 59 | // Greater than but not equal 60 | ['>1.0.0', '1.0.1'], 61 | ['>1.0.0-0', '1.0.0-0.0'], 62 | ['>1.0.0-beta', '1.0.0-beta.0'], 63 | ['>2 || >1.0.0', '1.0.1'], 64 | ['>2 || >1.0.0-0', '1.0.0-0.0'], 65 | ['>2 || >1.0.0-beta', '1.0.0-beta.0'], 66 | 67 | // Impossible range 68 | ['>4 <3', null], 69 | ].forEach((tuple) => { 70 | const range = tuple[0] 71 | const version = tuple[1] 72 | const loose = tuple[2] || false 73 | const msg = `minVersion(${range}, ${loose}) = ${version}` 74 | const min = minVersion(range, loose) 75 | t.ok(min === version || (min && min.version === version), msg, { 76 | found: min, 77 | wanted: version, 78 | }) 79 | }) 80 | t.end() 81 | }) 82 | -------------------------------------------------------------------------------- /.github/workflows/release-integration.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Release Integration 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | releases: 9 | required: true 10 | type: string 11 | description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' 12 | workflow_call: 13 | inputs: 14 | releases: 15 | required: true 16 | type: string 17 | description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version' 18 | secrets: 19 | PUBLISH_TOKEN: 20 | required: true 21 | 22 | permissions: 23 | contents: read 24 | id-token: write 25 | 26 | jobs: 27 | publish: 28 | name: Publish 29 | runs-on: ubuntu-latest 30 | defaults: 31 | run: 32 | shell: bash 33 | permissions: 34 | id-token: write 35 | steps: 36 | - name: Checkout 37 | uses: actions/checkout@v4 38 | with: 39 | ref: ${{ fromJSON(inputs.releases)[0].tagName }} 40 | - name: Setup Git User 41 | run: | 42 | git config --global user.email "npm-cli+bot@github.com" 43 | git config --global user.name "npm CLI robot" 44 | - name: Setup Node 45 | uses: actions/setup-node@v4 46 | id: node 47 | with: 48 | node-version: 22.x 49 | check-latest: contains('22.x', '.x') 50 | - name: Install Latest npm 51 | uses: ./.github/actions/install-latest-npm 52 | with: 53 | node: ${{ steps.node.outputs.node-version }} 54 | - name: Install Dependencies 55 | run: npm i --ignore-scripts --no-audit --no-fund 56 | - name: Set npm authToken 57 | run: npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN} 58 | - name: Publish 59 | env: 60 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }} 61 | RELEASES: ${{ inputs.releases }} 62 | run: | 63 | EXIT_CODE=0 64 | 65 | for release in $(echo $RELEASES | jq -r '.[] | @base64'); do 66 | PUBLISH_TAG=$(echo "$release" | base64 --decode | jq -r .publishTag) 67 | npm publish --provenance --tag="$PUBLISH_TAG" 68 | STATUS=$? 69 | if [[ "$STATUS" -eq 1 ]]; then 70 | EXIT_CODE=$STATUS 71 | fi 72 | done 73 | 74 | exit $EXIT_CODE 75 | -------------------------------------------------------------------------------- /ranges/outside.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const SemVer = require('../classes/semver') 4 | const Comparator = require('../classes/comparator') 5 | const { ANY } = Comparator 6 | const Range = require('../classes/range') 7 | const satisfies = require('../functions/satisfies') 8 | const gt = require('../functions/gt') 9 | const lt = require('../functions/lt') 10 | const lte = require('../functions/lte') 11 | const gte = require('../functions/gte') 12 | 13 | const outside = (version, range, hilo, options) => { 14 | version = new SemVer(version, options) 15 | range = new Range(range, options) 16 | 17 | let gtfn, ltefn, ltfn, comp, ecomp 18 | switch (hilo) { 19 | case '>': 20 | gtfn = gt 21 | ltefn = lte 22 | ltfn = lt 23 | comp = '>' 24 | ecomp = '>=' 25 | break 26 | case '<': 27 | gtfn = lt 28 | ltefn = gte 29 | ltfn = gt 30 | comp = '<' 31 | ecomp = '<=' 32 | break 33 | default: 34 | throw new TypeError('Must provide a hilo val of "<" or ">"') 35 | } 36 | 37 | // If it satisfies the range it is not outside 38 | if (satisfies(version, range, options)) { 39 | return false 40 | } 41 | 42 | // From now on, variable terms are as if we're in "gtr" mode. 43 | // but note that everything is flipped for the "ltr" function. 44 | 45 | for (let i = 0; i < range.set.length; ++i) { 46 | const comparators = range.set[i] 47 | 48 | let high = null 49 | let low = null 50 | 51 | comparators.forEach((comparator) => { 52 | if (comparator.semver === ANY) { 53 | comparator = new Comparator('>=0.0.0') 54 | } 55 | high = high || comparator 56 | low = low || comparator 57 | if (gtfn(comparator.semver, high.semver, options)) { 58 | high = comparator 59 | } else if (ltfn(comparator.semver, low.semver, options)) { 60 | low = comparator 61 | } 62 | }) 63 | 64 | // If the edge version comparator has a operator then our version 65 | // isn't outside it 66 | if (high.operator === comp || high.operator === ecomp) { 67 | return false 68 | } 69 | 70 | // If the lowest version comparator has an operator and our version 71 | // is less than it then it isn't higher than the range 72 | if ((!low.operator || low.operator === comp) && 73 | ltefn(version, low.semver)) { 74 | return false 75 | } else if (low.operator === ecomp && ltfn(version, low.semver)) { 76 | return false 77 | } 78 | } 79 | return true 80 | } 81 | 82 | module.exports = outside 83 | -------------------------------------------------------------------------------- /test/ranges/intersects.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const intersects = require('../../ranges/intersects') 5 | const Range = require('../../classes/range') 6 | const Comparator = require('../../classes/comparator') 7 | const comparatorIntersection = require('../fixtures/comparator-intersection.js') 8 | const rangeIntersection = require('../fixtures/range-intersection.js') 9 | 10 | test('intersect comparators', t => { 11 | t.plan(comparatorIntersection.length) 12 | comparatorIntersection.forEach(([c0, c1, expect, includePrerelease]) => 13 | t.test(`${c0} ${c1} ${expect}`, t => { 14 | const opts = { loose: false, includePrerelease } 15 | const comp0 = new Comparator(c0) 16 | const comp1 = new Comparator(c1) 17 | 18 | t.equal(intersects(comp0, comp1, opts), expect, `${c0} intersects ${c1} objects`) 19 | t.equal(intersects(comp1, comp0, opts), expect, `${c1} intersects ${c0} objects`) 20 | t.equal(intersects(c0, c1, opts), expect, `${c0} intersects ${c1}`) 21 | t.equal(intersects(c1, c0, opts), expect, `${c1} intersects ${c0}`) 22 | 23 | opts.loose = true 24 | t.equal(intersects(comp0, comp1, opts), expect, `${c0} intersects ${c1} loose, objects`) 25 | t.equal(intersects(comp1, comp0, opts), expect, `${c1} intersects ${c0} loose, objects`) 26 | t.equal(intersects(c0, c1, opts), expect, `${c0} intersects ${c1} loose`) 27 | t.equal(intersects(c1, c0, opts), expect, `${c1} intersects ${c0} loose`) 28 | t.end() 29 | })) 30 | }) 31 | 32 | test('ranges intersect', (t) => { 33 | rangeIntersection.forEach(([r0, r1, expect]) => { 34 | t.test(`${r0} <~> ${r1}`, t => { 35 | const range0 = new Range(r0) 36 | const range1 = new Range(r1) 37 | 38 | t.equal(intersects(r1, r0), expect, `${r0} <~> ${r1}`) 39 | t.equal(intersects(r0, r1), expect, `${r1} <~> ${r0}`) 40 | t.equal(intersects(r1, r0, true), expect, `${r0} <~> ${r1} loose`) 41 | t.equal(intersects(r0, r1, true), expect, `${r1} <~> ${r0} loose`) 42 | t.equal(intersects(range0, range1), expect, `${r0} <~> ${r1} objects`) 43 | t.equal(intersects(range1, range0), expect, `${r1} <~> ${r0} objects`) 44 | t.equal(intersects(range0, range1, true), expect, 45 | `${r0} <~> ${r1} objects loose`) 46 | t.equal(intersects(range1, range0, true), expect, 47 | `${r1} <~> ${r0} objects loose`) 48 | t.end() 49 | }) 50 | }) 51 | t.end() 52 | }) 53 | 54 | test('missing comparator parameter in intersect comparators', (t) => { 55 | t.throws(() => { 56 | new Comparator('>1.0.0').intersects() 57 | }, new TypeError('a Comparator is required'), 58 | 'throws type error') 59 | t.end() 60 | }) 61 | -------------------------------------------------------------------------------- /test/fixtures/version-not-gt-range.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [range, version, options] 4 | // Version should NOT be greater than range 5 | module.exports = [ 6 | ['~0.6.1-1', '0.6.1-1'], 7 | ['1.0.0 - 2.0.0', '1.2.3'], 8 | ['1.0.0 - 2.0.0', '0.9.9'], 9 | ['1.0.0', '1.0.0'], 10 | ['>=*', '0.2.4'], 11 | ['', '1.0.0', true], 12 | ['*', '1.2.3'], 13 | ['*', 'v1.2.3-foo'], 14 | ['>=1.0.0', '1.0.0'], 15 | ['>=1.0.0', '1.0.1'], 16 | ['>=1.0.0', '1.1.0'], 17 | ['>1.0.0', '1.0.1'], 18 | ['>1.0.0', '1.1.0'], 19 | ['<=2.0.0', '2.0.0'], 20 | ['<=2.0.0', '1.9999.9999'], 21 | ['<=2.0.0', '0.2.9'], 22 | ['<2.0.0', '1.9999.9999'], 23 | ['<2.0.0', '0.2.9'], 24 | ['>= 1.0.0', '1.0.0'], 25 | ['>= 1.0.0', '1.0.1'], 26 | ['>= 1.0.0', '1.1.0'], 27 | ['> 1.0.0', '1.0.1'], 28 | ['> 1.0.0', '1.1.0'], 29 | ['<= 2.0.0', '2.0.0'], 30 | ['<= 2.0.0', '1.9999.9999'], 31 | ['<= 2.0.0', '0.2.9'], 32 | ['< 2.0.0', '1.9999.9999'], 33 | ['<\t2.0.0', '0.2.9'], 34 | ['>=0.1.97', 'v0.1.97'], 35 | ['>=0.1.97', '0.1.97'], 36 | ['0.1.20 || 1.2.4', '1.2.4'], 37 | ['0.1.20 || >1.2.4', '1.2.4'], 38 | ['0.1.20 || 1.2.4', '1.2.3'], 39 | ['0.1.20 || 1.2.4', '0.1.20'], 40 | ['>=0.2.3 || <0.0.1', '0.0.0'], 41 | ['>=0.2.3 || <0.0.1', '0.2.3'], 42 | ['>=0.2.3 || <0.0.1', '0.2.4'], 43 | ['||', '1.3.4'], 44 | ['2.x.x', '2.1.3'], 45 | ['1.2.x', '1.2.3'], 46 | ['1.2.x || 2.x', '2.1.3'], 47 | ['1.2.x || 2.x', '1.2.3'], 48 | ['x', '1.2.3'], 49 | ['2.*.*', '2.1.3'], 50 | ['1.2.*', '1.2.3'], 51 | ['1.2.* || 2.*', '2.1.3'], 52 | ['1.2.* || 2.*', '1.2.3'], 53 | ['2', '2.1.2'], 54 | ['2.3', '2.3.1'], 55 | ['~2.4', '2.4.0'], // >=2.4.0 <2.5.0 56 | ['~2.4', '2.4.5'], 57 | ['~>3.2.1', '3.2.2'], // >=3.2.1 <3.3.0 58 | ['~1', '1.2.3'], // >=1.0.0 <2.0.0 59 | ['~>1', '1.2.3'], 60 | ['~> 1', '1.2.3'], 61 | ['~1.0', '1.0.2'], // >=1.0.0 <1.1.0 62 | ['~ 1.0', '1.0.2'], 63 | ['>=1', '1.0.0'], 64 | ['>= 1', '1.0.0'], 65 | ['<1.2', '1.1.1'], 66 | ['< 1.2', '1.1.1'], 67 | ['1', '1.0.0beta', true], 68 | ['~v0.5.4-pre', '0.5.5'], 69 | ['~v0.5.4-pre', '0.5.4'], 70 | ['=0.7.x', '0.7.2'], 71 | ['>=0.7.x', '0.7.2'], 72 | ['=0.7.x', '0.7.0-asdf'], 73 | ['>=0.7.x', '0.7.0-asdf'], 74 | ['<=0.7.x', '0.6.2'], 75 | ['>0.2.3 >0.2.4 <=0.2.5', '0.2.5'], 76 | ['>=0.2.3 <=0.2.4', '0.2.4'], 77 | ['1.0.0 - 2.0.0', '2.0.0'], 78 | ['^1', '0.0.0-0'], 79 | ['^3.0.0', '2.0.0'], 80 | ['^1.0.0 || ~2.0.1', '2.0.0'], 81 | ['^0.1.0 || ~3.0.1 || 5.0.0', '3.2.0'], 82 | ['^0.1.0 || ~3.0.1 || 5.0.0', '1.0.0beta', true], 83 | ['^0.1.0 || ~3.0.1 || 5.0.0', '5.0.0-0', true], 84 | ['^0.1.0 || ~3.0.1 || >4 <=5.0.0', '3.5.0'], 85 | ['0.7.x', '0.7.2-beta', { includePrerelease: true }], 86 | ] 87 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | <!-- This file is automatically added by @npmcli/template-oss. Do not edit. --> 2 | 3 | # Contributing 4 | 5 | ## Code of Conduct 6 | 7 | All interactions in the **npm** organization on GitHub are considered to be covered by our standard [Code of Conduct](https://docs.npmjs.com/policies/conduct). 8 | 9 | ## Reporting Bugs 10 | 11 | Before submitting a new bug report please search for an existing or similar report. 12 | 13 | Use one of our existing issue templates if you believe you've come across a unique problem. 14 | 15 | Duplicate issues, or issues that don't use one of our templates may get closed without a response. 16 | 17 | ## Pull Request Conventions 18 | 19 | ### Commits 20 | 21 | We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/). 22 | 23 | When opening a pull request please be sure that either the pull request title, or each commit in the pull request, has one of the following prefixes: 24 | 25 | - `feat`: For when introducing a new feature. The result will be a new semver minor version of the package when it is next published. 26 | - `fix`: For bug fixes. The result will be a new semver patch version of the package when it is next published. 27 | - `docs`: For documentation updates. The result will be a new semver patch version of the package when it is next published. 28 | - `chore`: For changes that do not affect the published module. Often these are changes to tests. The result will be *no* change to the version of the package when it is next published (as the commit does not affect the published version). 29 | 30 | ### Test Coverage 31 | 32 | Pull requests made against this repo will run `npm test` automatically. Please make sure tests pass locally before submitting a PR. 33 | 34 | Every new feature or bug fix should come with a corresponding test or tests that validate the solutions. Testing also reports on code coverage and will fail if code coverage drops. 35 | 36 | ### Linting 37 | 38 | Linting is also done automatically once tests pass. `npm run lintfix` will fix most linting errors automatically. 39 | 40 | Please make sure linting passes before submitting a PR. 41 | 42 | ## What _not_ to contribute? 43 | 44 | ### Dependencies 45 | 46 | It should be noted that our team does not accept third-party dependency updates/PRs. If you submit a PR trying to update our dependencies we will close it with or without a reference to these contribution guidelines. 47 | 48 | ### Tools/Automation 49 | 50 | Our core team is responsible for the maintenance of the tooling/automation in this project and we ask contributors to not make changes to these when contributing (e.g. `.github/*`, `.eslintrc.json`, `.licensee.json`). Most of those files also have a header at the top to remind folks they are automatically generated. Pull requests that alter these will not be accepted. 51 | -------------------------------------------------------------------------------- /test/fixtures/version-not-lt-range.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [range, version, options] 4 | // Version should NOT be less than range 5 | module.exports = [ 6 | ['~ 1.0', '1.1.0'], 7 | ['~0.6.1-1', '0.6.1-1'], 8 | ['1.0.0 - 2.0.0', '1.2.3'], 9 | ['1.0.0 - 2.0.0', '2.9.9'], 10 | ['1.0.0', '1.0.0'], 11 | ['>=*', '0.2.4'], 12 | ['', '1.0.0', true], 13 | ['*', '1.2.3'], 14 | ['>=1.0.0', '1.0.0'], 15 | ['>=1.0.0', '1.0.1'], 16 | ['>=1.0.0', '1.1.0'], 17 | ['>1.0.0', '1.0.1'], 18 | ['>1.0.0', '1.1.0'], 19 | ['<=2.0.0', '2.0.0'], 20 | ['<=2.0.0', '1.9999.9999'], 21 | ['<=2.0.0', '0.2.9'], 22 | ['<2.0.0', '1.9999.9999'], 23 | ['<2.0.0', '0.2.9'], 24 | ['>= 1.0.0', '1.0.0'], 25 | ['>= 1.0.0', '1.0.1'], 26 | ['>= 1.0.0', '1.1.0'], 27 | ['> 1.0.0', '1.0.1'], 28 | ['> 1.0.0', '1.1.0'], 29 | ['<= 2.0.0', '2.0.0'], 30 | ['<= 2.0.0', '1.9999.9999'], 31 | ['<= 2.0.0', '0.2.9'], 32 | ['< 2.0.0', '1.9999.9999'], 33 | ['<\t2.0.0', '0.2.9'], 34 | ['>=0.1.97', 'v0.1.97'], 35 | ['>=0.1.97', '0.1.97'], 36 | ['0.1.20 || 1.2.4', '1.2.4'], 37 | ['0.1.20 || >1.2.4', '1.2.4'], 38 | ['0.1.20 || 1.2.4', '1.2.3'], 39 | ['0.1.20 || 1.2.4', '0.1.20'], 40 | ['>=0.2.3 || <0.0.1', '0.0.0'], 41 | ['>=0.2.3 || <0.0.1', '0.2.3'], 42 | ['>=0.2.3 || <0.0.1', '0.2.4'], 43 | ['||', '1.3.4'], 44 | ['2.x.x', '2.1.3'], 45 | ['1.2.x', '1.2.3'], 46 | ['1.2.x || 2.x', '2.1.3'], 47 | ['1.2.x || 2.x', '1.2.3'], 48 | ['x', '1.2.3'], 49 | ['2.*.*', '2.1.3'], 50 | ['1.2.*', '1.2.3'], 51 | ['1.2.* || 2.*', '2.1.3'], 52 | ['1.2.* || 2.*', '1.2.3'], 53 | ['2', '2.1.2'], 54 | ['2.3', '2.3.1'], 55 | ['~2.4', '2.4.0'], // >=2.4.0 <2.5.0 56 | ['~2.4', '2.4.5'], 57 | ['~>3.2.1', '3.2.2'], // >=3.2.1 <3.3.0 58 | ['~1', '1.2.3'], // >=1.0.0 <2.0.0 59 | ['~>1', '1.2.3'], 60 | ['~> 1', '1.2.3'], 61 | ['~1.0', '1.0.2'], // >=1.0.0 <1.1.0 62 | ['~ 1.0', '1.0.2'], 63 | ['>=1', '1.0.0'], 64 | ['>= 1', '1.0.0'], 65 | ['<1.2', '1.1.1'], 66 | ['< 1.2', '1.1.1'], 67 | ['~v0.5.4-pre', '0.5.5'], 68 | ['~v0.5.4-pre', '0.5.4'], 69 | ['=0.7.x', '0.7.2'], 70 | ['>=0.7.x', '0.7.2'], 71 | ['<=0.7.x', '0.6.2'], 72 | ['>0.2.3 >0.2.4 <=0.2.5', '0.2.5'], 73 | ['>=0.2.3 <=0.2.4', '0.2.4'], 74 | ['1.0.0 - 2.0.0', '2.0.0'], 75 | ['^3.0.0', '4.0.0'], 76 | ['^1.0.0 || ~2.0.1', '2.0.0'], 77 | ['^0.1.0 || ~3.0.1 || 5.0.0', '3.2.0'], 78 | ['^0.1.0 || ~3.0.1 || 5.0.0', '1.0.0beta', true], 79 | ['^0.1.0 || ~3.0.1 || 5.0.0', '5.0.0-0', true], 80 | ['^0.1.0 || ~3.0.1 || >4 <=5.0.0', '3.5.0'], 81 | ['^1.0.0alpha', '1.0.0beta', true], 82 | ['~1.0.0alpha', '1.0.0beta', true], 83 | ['^1.0.0-alpha', '1.0.0beta', true], 84 | ['~1.0.0-alpha', '1.0.0beta', true], 85 | ['^1.0.0-alpha', '1.0.0-beta'], 86 | ['~1.0.0-alpha', '1.0.0-beta'], 87 | ['=0.1.0', '1.0.0'], 88 | ['>1.2.3', '1.3.0-alpha', { includePrerelease: true }], 89 | ] 90 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // just pre-load all the stuff that index.js lazily exports 4 | const internalRe = require('./internal/re') 5 | const constants = require('./internal/constants') 6 | const SemVer = require('./classes/semver') 7 | const identifiers = require('./internal/identifiers') 8 | const parse = require('./functions/parse') 9 | const valid = require('./functions/valid') 10 | const clean = require('./functions/clean') 11 | const inc = require('./functions/inc') 12 | const diff = require('./functions/diff') 13 | const major = require('./functions/major') 14 | const minor = require('./functions/minor') 15 | const patch = require('./functions/patch') 16 | const prerelease = require('./functions/prerelease') 17 | const compare = require('./functions/compare') 18 | const rcompare = require('./functions/rcompare') 19 | const compareLoose = require('./functions/compare-loose') 20 | const compareBuild = require('./functions/compare-build') 21 | const sort = require('./functions/sort') 22 | const rsort = require('./functions/rsort') 23 | const gt = require('./functions/gt') 24 | const lt = require('./functions/lt') 25 | const eq = require('./functions/eq') 26 | const neq = require('./functions/neq') 27 | const gte = require('./functions/gte') 28 | const lte = require('./functions/lte') 29 | const cmp = require('./functions/cmp') 30 | const coerce = require('./functions/coerce') 31 | const Comparator = require('./classes/comparator') 32 | const Range = require('./classes/range') 33 | const satisfies = require('./functions/satisfies') 34 | const toComparators = require('./ranges/to-comparators') 35 | const maxSatisfying = require('./ranges/max-satisfying') 36 | const minSatisfying = require('./ranges/min-satisfying') 37 | const minVersion = require('./ranges/min-version') 38 | const validRange = require('./ranges/valid') 39 | const outside = require('./ranges/outside') 40 | const gtr = require('./ranges/gtr') 41 | const ltr = require('./ranges/ltr') 42 | const intersects = require('./ranges/intersects') 43 | const simplifyRange = require('./ranges/simplify') 44 | const subset = require('./ranges/subset') 45 | module.exports = { 46 | parse, 47 | valid, 48 | clean, 49 | inc, 50 | diff, 51 | major, 52 | minor, 53 | patch, 54 | prerelease, 55 | compare, 56 | rcompare, 57 | compareLoose, 58 | compareBuild, 59 | sort, 60 | rsort, 61 | gt, 62 | lt, 63 | eq, 64 | neq, 65 | gte, 66 | lte, 67 | cmp, 68 | coerce, 69 | Comparator, 70 | Range, 71 | satisfies, 72 | toComparators, 73 | maxSatisfying, 74 | minSatisfying, 75 | minVersion, 76 | validRange, 77 | outside, 78 | gtr, 79 | ltr, 80 | intersects, 81 | simplifyRange, 82 | subset, 83 | SemVer, 84 | re: internalRe.re, 85 | src: internalRe.src, 86 | tokens: internalRe.t, 87 | SEMVER_SPEC_VERSION: constants.SEMVER_SPEC_VERSION, 88 | RELEASE_TYPES: constants.RELEASE_TYPES, 89 | compareIdentifiers: identifiers.compareIdentifiers, 90 | rcompareIdentifiers: identifiers.rcompareIdentifiers, 91 | } 92 | -------------------------------------------------------------------------------- /test/bin/semver.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | 5 | const thisVersion = require('../../package.json').version 6 | t.cleanSnapshot = str => str.split(thisVersion).join('@@VERSION@@') 7 | 8 | const spawn = require('child_process').spawn 9 | const bin = require.resolve('../../bin/semver') 10 | const run = args => new Promise((resolve, reject) => { 11 | const c = spawn(process.execPath, [bin].concat(args)) 12 | c.on('error', reject) 13 | const out = [] 14 | const err = [] 15 | c.stdout.setEncoding('utf-8') 16 | c.stdout.on('data', chunk => out.push(chunk)) 17 | c.stderr.setEncoding('utf-8') 18 | c.stderr.on('data', chunk => err.push(chunk)) 19 | c.on('close', (code, signal) => { 20 | resolve({ 21 | out: out.join(''), 22 | err: err.join(''), 23 | code: code, 24 | signal: signal, 25 | }) 26 | }) 27 | }) 28 | 29 | t.test('inc tests', t => Promise.all([ 30 | ['-i', 'major', '1.0.0'], 31 | ['-i', 'major', '1.0.0', '1.0.1'], 32 | ['-i', 'premajor', '1.0.0', '--preid=beta'], 33 | ['-i', 'premajor', '1.0.0', '--preid=beta', '-n', '1'], 34 | ['-i', 'premajor', '1.0.0', '--preid=beta', '-n', 'false'], 35 | ['-i', '1.2.3'], 36 | ].map(args => t.resolveMatchSnapshot(run(args), args.join(' '))))) 37 | 38 | t.test('help output', t => Promise.all([ 39 | ['-h'], 40 | ['-?'], 41 | ['--help'], 42 | [], 43 | ].map(h => t.resolveMatchSnapshot(run(h), h[0] || '(no args)')))) 44 | 45 | t.test('sorting and filtering', t => Promise.all([ 46 | ['1.2.3', '3.2.1', '2.3.4'], 47 | ['1.2.3', '3.2.1', '2.3.4', '2.3.4-beta'], 48 | ['1.2.3', '-v', '3.2.1', '--version', '2.3.4'], 49 | ['1.2.3', '-v', '3.2.1', '--version', '2.3.4', '-rv'], 50 | ['1.2.3foo', '1.2.3-bar'], 51 | ['1.2.3foo', '1.2.3-bar', '-l'], 52 | ['1.2.3', '3.2.1', '-r', '2.x', '2.3.4'], 53 | ['1.2.3', '3.2.1', '2.3.4', '2.3.4-beta', '2.0.0asdf', '-r', '2.x'], 54 | ['1.2.3', '3.2.1', '2.3.4', '2.3.4-beta', '2.0.0asdf', '-r', '2.x', '-p'], 55 | ['3.2.1', '2.3.4', '2.3.4-beta', '2.0.0asdf', '-r', '2.x', '-p', '-l'], 56 | ['1.2.3', '3.2.1', '-r', '2.x'], 57 | ].map(args => t.resolveMatchSnapshot(run(args), args.join(' '))))) 58 | 59 | t.test('coercing', t => Promise.all([ 60 | ['1.2.3.4.5.6', '-c'], 61 | ['1.2.3.4.5.6', '-c', '--rtl'], 62 | ['1.2.3.4.5.6', '-c', '--rtl', '--ltr'], 63 | ['not a version', '1.2.3', '-c'], 64 | ['not a version', '-c'], 65 | ].map(args => t.resolveMatchSnapshot(run(args), args.join(' '))))) 66 | 67 | t.test('args with equals', t => Promise.all([ 68 | [['--version', '1.2.3'], '1.2.3'], 69 | [['--range', '1'], ['1.2.3'], ['2.3.4'], '1.2.3'], 70 | [['--increment', 'major'], ['1.0.0'], '2.0.0'], 71 | [['--increment', 'premajor'], ['--preid', 'beta'], ['1.0.0'], '2.0.0-beta.0'], 72 | ].map(async (args) => { 73 | const expected = args.pop() 74 | const equals = args.map((a) => a.join('=')) 75 | const spaces = args.reduce((acc, a) => acc.concat(a), []) 76 | const res1 = await run(equals) 77 | const res2 = await run(spaces) 78 | t.equal(res1.signal, null) 79 | t.equal(res1.code, 0) 80 | t.equal(res1.err, '') 81 | t.equal(res1.out.trim(), expected) 82 | t.strictSame(res1, res2, args.join(' ')) 83 | }))) 84 | -------------------------------------------------------------------------------- /test/ranges/to-comparators.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const toComparators = require('../../ranges/to-comparators') 5 | 6 | test('comparators test', (t) => { 7 | // [range, comparators] 8 | // turn range into a set of individual comparators 9 | [['1.0.0 - 2.0.0', [['>=1.0.0', '<=2.0.0']]], 10 | ['1.0.0', [['1.0.0']]], 11 | ['>=*', [['']]], 12 | ['', [['']]], 13 | ['*', [['']]], 14 | ['*', [['']]], 15 | ['>=1.0.0', [['>=1.0.0']]], 16 | ['>=1.0.0', [['>=1.0.0']]], 17 | ['>=1.0.0', [['>=1.0.0']]], 18 | ['>1.0.0', [['>1.0.0']]], 19 | ['>1.0.0', [['>1.0.0']]], 20 | ['<=2.0.0', [['<=2.0.0']]], 21 | ['1', [['>=1.0.0', '<2.0.0-0']]], 22 | ['<=2.0.0', [['<=2.0.0']]], 23 | ['<=2.0.0', [['<=2.0.0']]], 24 | ['<2.0.0', [['<2.0.0']]], 25 | ['<2.0.0', [['<2.0.0']]], 26 | ['>= 1.0.0', [['>=1.0.0']]], 27 | ['>= 1.0.0', [['>=1.0.0']]], 28 | ['>= 1.0.0', [['>=1.0.0']]], 29 | ['> 1.0.0', [['>1.0.0']]], 30 | ['> 1.0.0', [['>1.0.0']]], 31 | ['<= 2.0.0', [['<=2.0.0']]], 32 | ['<= 2.0.0', [['<=2.0.0']]], 33 | ['<= 2.0.0', [['<=2.0.0']]], 34 | ['< 2.0.0', [['<2.0.0']]], 35 | ['<\t2.0.0', [['<2.0.0']]], 36 | ['>=0.1.97', [['>=0.1.97']]], 37 | ['>=0.1.97', [['>=0.1.97']]], 38 | ['0.1.20 || 1.2.4', [['0.1.20'], ['1.2.4']]], 39 | ['>=0.2.3 || <0.0.1', [['>=0.2.3'], ['<0.0.1']]], 40 | ['>=0.2.3 || <0.0.1', [['>=0.2.3'], ['<0.0.1']]], 41 | ['>=0.2.3 || <0.0.1', [['>=0.2.3'], ['<0.0.1']]], 42 | ['||', [['']]], 43 | ['2.x.x', [['>=2.0.0', '<3.0.0-0']]], 44 | ['1.2.x', [['>=1.2.0', '<1.3.0-0']]], 45 | ['1.2.x || 2.x', [['>=1.2.0', '<1.3.0-0'], ['>=2.0.0', '<3.0.0-0']]], 46 | ['1.2.x || 2.x', [['>=1.2.0', '<1.3.0-0'], ['>=2.0.0', '<3.0.0-0']]], 47 | ['x', [['']]], 48 | ['2.*.*', [['>=2.0.0', '<3.0.0-0']]], 49 | ['1.2.*', [['>=1.2.0', '<1.3.0-0']]], 50 | ['1.2.* || 2.*', [['>=1.2.0', '<1.3.0-0'], ['>=2.0.0', '<3.0.0-0']]], 51 | ['1.2.* || 2.*', [['>=1.2.0', '<1.3.0-0'], ['>=2.0.0', '<3.0.0-0']]], 52 | ['*', [['']]], 53 | ['2', [['>=2.0.0', '<3.0.0-0']]], 54 | ['2.3', [['>=2.3.0', '<2.4.0-0']]], 55 | ['~2.4', [['>=2.4.0', '<2.5.0-0']]], 56 | ['~2.4', [['>=2.4.0', '<2.5.0-0']]], 57 | ['~>3.2.1', [['>=3.2.1', '<3.3.0-0']]], 58 | ['~1', [['>=1.0.0', '<2.0.0-0']]], 59 | ['~>1', [['>=1.0.0', '<2.0.0-0']]], 60 | ['~> 1', [['>=1.0.0', '<2.0.0-0']]], 61 | ['~1.0', [['>=1.0.0', '<1.1.0-0']]], 62 | ['~ 1.0', [['>=1.0.0', '<1.1.0-0']]], 63 | ['~ 1.0.3', [['>=1.0.3', '<1.1.0-0']]], 64 | ['~> 1.0.3', [['>=1.0.3', '<1.1.0-0']]], 65 | ['<1', [['<1.0.0-0']]], 66 | ['< 1', [['<1.0.0-0']]], 67 | ['>=1', [['>=1.0.0']]], 68 | ['>= 1', [['>=1.0.0']]], 69 | ['<1.2', [['<1.2.0-0']]], 70 | ['< 1.2', [['<1.2.0-0']]], 71 | ['1', [['>=1.0.0', '<2.0.0-0']]], 72 | ['1 2', [['>=1.0.0', '<2.0.0-0', '>=2.0.0', '<3.0.0-0']]], 73 | ['1.2 - 3.4.5', [['>=1.2.0', '<=3.4.5']]], 74 | ['1.2.3 - 3.4', [['>=1.2.3', '<3.5.0-0']]], 75 | ['1.2.3 - 3', [['>=1.2.3', '<4.0.0-0']]], 76 | ['>*', [['<0.0.0-0']]], 77 | ['<*', [['<0.0.0-0']]], 78 | ['>X', [['<0.0.0-0']]], 79 | ['<X', [['<0.0.0-0']]], 80 | ['<x <* || >* 2.x', [['<0.0.0-0']]], 81 | ['>x 2.x || * || <x', [['']]], 82 | ].forEach(([pre, wanted]) => { 83 | const found = toComparators(pre) 84 | const jw = JSON.stringify(wanted) 85 | t.same(found, wanted, `toComparators(${pre}) === ${jw}`) 86 | }) 87 | 88 | t.end() 89 | }) 90 | -------------------------------------------------------------------------------- /test/fixtures/range-exclude.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [range, version, options] 4 | // version should not be included by range 5 | module.exports = [ 6 | ['1.0.0 - 2.0.0', '2.2.3'], 7 | ['1.2.3+asdf - 2.4.3+asdf', '1.2.3-pre.2'], 8 | ['1.2.3+asdf - 2.4.3+asdf', '2.4.3-alpha'], 9 | ['^1.2.3+build', '2.0.0'], 10 | ['^1.2.3+build', '1.2.0'], 11 | ['^1.2.3', '1.2.3-pre'], 12 | ['^1.2', '1.2.0-pre'], 13 | ['>1.2', '1.3.0-beta'], 14 | ['<=1.2.3', '1.2.3-beta'], 15 | ['^1.2.3', '1.2.3-beta'], 16 | ['=0.7.x', '0.7.0-asdf'], 17 | ['>=0.7.x', '0.7.0-asdf'], 18 | ['<=0.7.x', '0.7.0-asdf'], 19 | ['1', '1.0.0beta', { loose: 420 }], 20 | ['<1', '1.0.0beta', true], 21 | ['< 1', '1.0.0beta', true], 22 | ['1.0.0', '1.0.1'], 23 | ['>=1.0.0', '0.0.0'], 24 | ['>=1.0.0', '0.0.1'], 25 | ['>=1.0.0', '0.1.0'], 26 | ['>1.0.0', '0.0.1'], 27 | ['>1.0.0', '0.1.0'], 28 | ['<=2.0.0', '3.0.0'], 29 | ['<=2.0.0', '2.9999.9999'], 30 | ['<=2.0.0', '2.2.9'], 31 | ['<2.0.0', '2.9999.9999'], 32 | ['<2.0.0', '2.2.9'], 33 | ['>=0.1.97', 'v0.1.93', true], 34 | ['>=0.1.97', '0.1.93'], 35 | ['0.1.20 || 1.2.4', '1.2.3'], 36 | ['>=0.2.3 || <0.0.1', '0.0.3'], 37 | ['>=0.2.3 || <0.0.1', '0.2.2'], 38 | ['2.x.x', '1.1.3', { loose: NaN }], 39 | ['2.x.x', '3.1.3'], 40 | ['1.2.x', '1.3.3'], 41 | ['1.2.x || 2.x', '3.1.3'], 42 | ['1.2.x || 2.x', '1.1.3'], 43 | ['2.*.*', '1.1.3'], 44 | ['2.*.*', '3.1.3'], 45 | ['1.2.*', '1.3.3'], 46 | ['1.2.* || 2.*', '3.1.3'], 47 | ['1.2.* || 2.*', '1.1.3'], 48 | ['2', '1.1.2'], 49 | ['2.3', '2.4.1'], 50 | ['~0.0.1', '0.1.0-alpha'], 51 | ['~0.0.1', '0.1.0'], 52 | ['~2.4', '2.5.0'], // >=2.4.0 <2.5.0 53 | ['~2.4', '2.3.9'], 54 | ['~>3.2.1', '3.3.2'], // >=3.2.1 <3.3.0 55 | ['~>3.2.1', '3.2.0'], // >=3.2.1 <3.3.0 56 | ['~1', '0.2.3'], // >=1.0.0 <2.0.0 57 | ['~>1', '2.2.3'], 58 | ['~1.0', '1.1.0'], // >=1.0.0 <1.1.0 59 | ['<1', '1.0.0'], 60 | ['>=1.2', '1.1.1'], 61 | ['1', '2.0.0beta', true], 62 | ['~v0.5.4-beta', '0.5.4-alpha'], 63 | ['=0.7.x', '0.8.2'], 64 | ['>=0.7.x', '0.6.2'], 65 | ['<0.7.x', '0.7.2'], 66 | ['<1.2.3', '1.2.3-beta'], 67 | ['=1.2.3', '1.2.3-beta'], 68 | ['>1.2', '1.2.8'], 69 | ['^0.0.1', '0.0.2-alpha'], 70 | ['^0.0.1', '0.0.2'], 71 | ['^1.2.3', '2.0.0-alpha'], 72 | ['^1.2.3', '1.2.2'], 73 | ['^1.2', '1.1.9'], 74 | ['*', 'v1.2.3-foo', true], 75 | 76 | // invalid versions never satisfy, but shouldn't throw 77 | ['*', 'not a version'], 78 | ['>=2', 'glorp'], 79 | ['>=2', false], 80 | 81 | ['2.x', '3.0.0-pre.0', { includePrerelease: true }], 82 | ['^1.0.0', '1.0.0-rc1', { includePrerelease: true }], 83 | ['^1.0.0', '2.0.0-rc1', { includePrerelease: true }], 84 | ['^1.2.3-rc2', '2.0.0', { includePrerelease: true }], 85 | ['^1.0.0', '2.0.0-rc1'], 86 | 87 | ['1 - 2', '3.0.0-pre', { includePrerelease: true }], 88 | ['1 - 2', '2.0.0-pre'], 89 | ['1 - 2', '1.0.0-pre'], 90 | ['1.0 - 2', '1.0.0-pre'], 91 | 92 | ['1.1.x', '1.0.0-a'], 93 | ['1.1.x', '1.1.0-a'], 94 | ['1.1.x', '1.2.0-a'], 95 | ['1.1.x', '1.2.0-a', { includePrerelease: true }], 96 | ['1.1.x', '1.0.0-a', { includePrerelease: true }], 97 | ['1.x', '1.0.0-a'], 98 | ['1.x', '1.1.0-a'], 99 | ['1.x', '1.2.0-a'], 100 | ['1.x', '0.0.0-a', { includePrerelease: true }], 101 | ['1.x', '2.0.0-a', { includePrerelease: true }], 102 | 103 | ['>=1.0.0 <1.1.0', '1.1.0'], 104 | ['>=1.0.0 <1.1.0', '1.1.0', { includePrerelease: true }], 105 | ['>=1.0.0 <1.1.0', '1.1.0-pre'], 106 | ['>=1.0.0 <1.1.0-pre', '1.1.0-pre'], 107 | 108 | ['== 1.0.0 || foo', '2.0.0', { loose: true }], 109 | ] 110 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: CI 4 | 5 | on: 6 | workflow_dispatch: 7 | pull_request: 8 | push: 9 | branches: 10 | - main 11 | - release/v* 12 | schedule: 13 | # "At 09:00 UTC (02:00 PT) on Monday" https://crontab.guru/#0_9_*_*_1 14 | - cron: "0 9 * * 1" 15 | 16 | permissions: 17 | contents: read 18 | 19 | jobs: 20 | lint: 21 | name: Lint 22 | if: github.repository_owner == 'npm' 23 | runs-on: ubuntu-latest 24 | defaults: 25 | run: 26 | shell: bash 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v4 30 | - name: Setup Git User 31 | run: | 32 | git config --global user.email "npm-cli+bot@github.com" 33 | git config --global user.name "npm CLI robot" 34 | - name: Setup Node 35 | uses: actions/setup-node@v4 36 | id: node 37 | with: 38 | node-version: 22.x 39 | check-latest: contains('22.x', '.x') 40 | - name: Install Latest npm 41 | uses: ./.github/actions/install-latest-npm 42 | with: 43 | node: ${{ steps.node.outputs.node-version }} 44 | - name: Install Dependencies 45 | run: npm i --ignore-scripts --no-audit --no-fund 46 | - name: Lint 47 | run: npm run lint --ignore-scripts 48 | - name: Post Lint 49 | run: npm run postlint --ignore-scripts 50 | 51 | test: 52 | name: Test - ${{ matrix.platform.name }} - ${{ matrix.node-version }} 53 | if: github.repository_owner == 'npm' 54 | strategy: 55 | fail-fast: false 56 | matrix: 57 | platform: 58 | - name: Linux 59 | os: ubuntu-latest 60 | shell: bash 61 | - name: macOS 62 | os: macos-latest 63 | shell: bash 64 | - name: macOS 65 | os: macos-13 66 | shell: bash 67 | - name: Windows 68 | os: windows-latest 69 | shell: cmd 70 | node-version: 71 | - 10.0.0 72 | - 10.x 73 | - 12.x 74 | - 14.x 75 | - 16.x 76 | - 18.x 77 | - 20.x 78 | - 22.x 79 | exclude: 80 | - platform: { name: macOS, os: macos-latest, shell: bash } 81 | node-version: 10.0.0 82 | - platform: { name: macOS, os: macos-latest, shell: bash } 83 | node-version: 10.x 84 | - platform: { name: macOS, os: macos-latest, shell: bash } 85 | node-version: 12.x 86 | - platform: { name: macOS, os: macos-latest, shell: bash } 87 | node-version: 14.x 88 | - platform: { name: macOS, os: macos-13, shell: bash } 89 | node-version: 16.x 90 | - platform: { name: macOS, os: macos-13, shell: bash } 91 | node-version: 18.x 92 | - platform: { name: macOS, os: macos-13, shell: bash } 93 | node-version: 20.x 94 | - platform: { name: macOS, os: macos-13, shell: bash } 95 | node-version: 22.x 96 | runs-on: ${{ matrix.platform.os }} 97 | defaults: 98 | run: 99 | shell: ${{ matrix.platform.shell }} 100 | steps: 101 | - name: Checkout 102 | uses: actions/checkout@v4 103 | - name: Setup Git User 104 | run: | 105 | git config --global user.email "npm-cli+bot@github.com" 106 | git config --global user.name "npm CLI robot" 107 | - name: Setup Node 108 | uses: actions/setup-node@v4 109 | id: node 110 | with: 111 | node-version: ${{ matrix.node-version }} 112 | check-latest: contains(matrix.node-version, '.x') 113 | - name: Install Latest npm 114 | uses: ./.github/actions/install-latest-npm 115 | with: 116 | node: ${{ steps.node.outputs.node-version }} 117 | - name: Install Dependencies 118 | run: npm i --ignore-scripts --no-audit --no-fund 119 | - name: Add Problem Matcher 120 | run: echo "::add-matcher::.github/matchers/tap.json" 121 | - name: Test 122 | run: npm test --ignore-scripts 123 | -------------------------------------------------------------------------------- /classes/comparator.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const ANY = Symbol('SemVer ANY') 4 | // hoisted class for cyclic dependency 5 | class Comparator { 6 | static get ANY () { 7 | return ANY 8 | } 9 | 10 | constructor (comp, options) { 11 | options = parseOptions(options) 12 | 13 | if (comp instanceof Comparator) { 14 | if (comp.loose === !!options.loose) { 15 | return comp 16 | } else { 17 | comp = comp.value 18 | } 19 | } 20 | 21 | comp = comp.trim().split(/\s+/).join(' ') 22 | debug('comparator', comp, options) 23 | this.options = options 24 | this.loose = !!options.loose 25 | this.parse(comp) 26 | 27 | if (this.semver === ANY) { 28 | this.value = '' 29 | } else { 30 | this.value = this.operator + this.semver.version 31 | } 32 | 33 | debug('comp', this) 34 | } 35 | 36 | parse (comp) { 37 | const r = this.options.loose ? re[t.COMPARATORLOOSE] : re[t.COMPARATOR] 38 | const m = comp.match(r) 39 | 40 | if (!m) { 41 | throw new TypeError(`Invalid comparator: ${comp}`) 42 | } 43 | 44 | this.operator = m[1] !== undefined ? m[1] : '' 45 | if (this.operator === '=') { 46 | this.operator = '' 47 | } 48 | 49 | // if it literally is just '>' or '' then allow anything. 50 | if (!m[2]) { 51 | this.semver = ANY 52 | } else { 53 | this.semver = new SemVer(m[2], this.options.loose) 54 | } 55 | } 56 | 57 | toString () { 58 | return this.value 59 | } 60 | 61 | test (version) { 62 | debug('Comparator.test', version, this.options.loose) 63 | 64 | if (this.semver === ANY || version === ANY) { 65 | return true 66 | } 67 | 68 | if (typeof version === 'string') { 69 | try { 70 | version = new SemVer(version, this.options) 71 | } catch (er) { 72 | return false 73 | } 74 | } 75 | 76 | return cmp(version, this.operator, this.semver, this.options) 77 | } 78 | 79 | intersects (comp, options) { 80 | if (!(comp instanceof Comparator)) { 81 | throw new TypeError('a Comparator is required') 82 | } 83 | 84 | if (this.operator === '') { 85 | if (this.value === '') { 86 | return true 87 | } 88 | return new Range(comp.value, options).test(this.value) 89 | } else if (comp.operator === '') { 90 | if (comp.value === '') { 91 | return true 92 | } 93 | return new Range(this.value, options).test(comp.semver) 94 | } 95 | 96 | options = parseOptions(options) 97 | 98 | // Special cases where nothing can possibly be lower 99 | if (options.includePrerelease && 100 | (this.value === '<0.0.0-0' || comp.value === '<0.0.0-0')) { 101 | return false 102 | } 103 | if (!options.includePrerelease && 104 | (this.value.startsWith('<0.0.0') || comp.value.startsWith('<0.0.0'))) { 105 | return false 106 | } 107 | 108 | // Same direction increasing (> or >=) 109 | if (this.operator.startsWith('>') && comp.operator.startsWith('>')) { 110 | return true 111 | } 112 | // Same direction decreasing (< or <=) 113 | if (this.operator.startsWith('<') && comp.operator.startsWith('<')) { 114 | return true 115 | } 116 | // same SemVer and both sides are inclusive (<= or >=) 117 | if ( 118 | (this.semver.version === comp.semver.version) && 119 | this.operator.includes('=') && comp.operator.includes('=')) { 120 | return true 121 | } 122 | // opposite directions less than 123 | if (cmp(this.semver, '<', comp.semver, options) && 124 | this.operator.startsWith('>') && comp.operator.startsWith('<')) { 125 | return true 126 | } 127 | // opposite directions greater than 128 | if (cmp(this.semver, '>', comp.semver, options) && 129 | this.operator.startsWith('<') && comp.operator.startsWith('>')) { 130 | return true 131 | } 132 | return false 133 | } 134 | } 135 | 136 | module.exports = Comparator 137 | 138 | const parseOptions = require('../internal/parse-options') 139 | const { safeRe: re, t } = require('../internal/re') 140 | const cmp = require('../functions/cmp') 141 | const debug = require('../internal/debug') 142 | const SemVer = require('./semver') 143 | const Range = require('./range') 144 | -------------------------------------------------------------------------------- /test/classes/range.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const Range = require('../../classes/range') 5 | const Comparator = require('../../classes/comparator') 6 | const rangeIntersection = require('../fixtures/range-intersection.js') 7 | 8 | const rangeInclude = require('../fixtures/range-include.js') 9 | const rangeExclude = require('../fixtures/range-exclude.js') 10 | const rangeParse = require('../fixtures/range-parse.js') 11 | 12 | test('range tests', t => { 13 | t.plan(rangeInclude.length) 14 | rangeInclude.forEach(([range, ver, options]) => { 15 | const r = new Range(range, options) 16 | t.ok(r.test(ver), `${range} satisfied by ${ver}`) 17 | }) 18 | }) 19 | 20 | test('range parsing', t => { 21 | t.plan(rangeParse.length) 22 | rangeParse.forEach(([range, expect, options]) => 23 | t.test(`${range} ${expect} ${JSON.stringify(options)}`, t => { 24 | if (expect === null) { 25 | t.throws(() => new Range(range, options), TypeError, `invalid range: ${range}`) 26 | } else { 27 | t.equal(new Range(range, options).range || '*', expect, `${range} => ${expect}`) 28 | t.equal(new Range(range, options).range, new Range(expect).range, 29 | 'parsing both yields same result') 30 | } 31 | t.end() 32 | })) 33 | }) 34 | 35 | test('throw for empty comparator set, even in loose mode', t => { 36 | t.throws(() => new Range('sadf||asdf', { loose: true }), 37 | TypeError('Invalid SemVer Range: sadf||asdf')) 38 | t.end() 39 | }) 40 | 41 | test('convert comparator to range', t => { 42 | const c = new Comparator('>=1.2.3') 43 | const r = new Range(c) 44 | t.equal(r.raw, c.value, 'created range from comparator') 45 | t.end() 46 | }) 47 | 48 | test('range as argument to range ctor', t => { 49 | const loose = new Range('1.2.3', { loose: true }) 50 | t.equal(new Range(loose, { loose: true }), loose, 'loose option') 51 | t.equal(new Range(loose, true), loose, 'loose boolean') 52 | t.not(new Range(loose), loose, 'created new range if not matched') 53 | 54 | const incPre = new Range('1.2.3', { includePrerelease: true }) 55 | t.equal(new Range(incPre, { includePrerelease: true }), incPre, 56 | 'include prerelease, option match returns argument') 57 | t.not(new Range(incPre), incPre, 58 | 'include prerelease, option mismatch does not return argument') 59 | 60 | t.end() 61 | }) 62 | 63 | test('negative range tests', t => { 64 | t.plan(rangeExclude.length) 65 | rangeExclude.forEach(([range, ver, options]) => { 66 | const r = new Range(range, options) 67 | t.notOk(r.test(ver), `${range} not satisfied by ${ver}`) 68 | }) 69 | }) 70 | 71 | test('strict vs loose ranges', (t) => { 72 | [ 73 | ['>=01.02.03', '>=1.2.3'], 74 | ['~1.02.03beta', '>=1.2.3-beta <1.3.0-0'], 75 | ].forEach(([loose, comps]) => { 76 | t.throws(() => new Range(loose)) 77 | t.equal(new Range(loose, true).range, comps) 78 | }) 79 | t.end() 80 | }) 81 | 82 | test('tostrings', (t) => { 83 | t.equal(new Range('>= v1.2.3').toString(), '>=1.2.3') 84 | t.end() 85 | }) 86 | 87 | test('formatted value is calculated lazily and cached', (t) => { 88 | const r = new Range('>= v1.2.3') 89 | t.equal(r.formatted, undefined) 90 | t.equal(r.format(), '>=1.2.3') 91 | t.equal(r.formatted, '>=1.2.3') 92 | t.equal(r.format(), '>=1.2.3') 93 | t.end() 94 | }) 95 | 96 | test('ranges intersect', (t) => { 97 | rangeIntersection.forEach(([r0, r1, expect]) => { 98 | t.test(`${r0} <~> ${r1}`, t => { 99 | const range0 = new Range(r0) 100 | const range1 = new Range(r1) 101 | 102 | t.equal(range0.intersects(range1), expect, 103 | `${r0} <~> ${r1} objects`) 104 | t.equal(range1.intersects(range0), expect, 105 | `${r1} <~> ${r0} objects`) 106 | t.end() 107 | }) 108 | }) 109 | t.end() 110 | }) 111 | 112 | test('missing range parameter in range intersect', (t) => { 113 | t.throws(() => { 114 | new Range('1.0.0').intersects() 115 | }, new TypeError('a Range is required'), 116 | 'throws type error') 117 | t.end() 118 | }) 119 | 120 | test('cache', (t) => { 121 | const cached = Symbol('cached') 122 | const r1 = new Range('1.0.0') 123 | r1.set[0][cached] = true 124 | const r2 = new Range('1.0.0') 125 | t.equal(r1.set[0][cached], true) 126 | t.equal(r2.set[0][cached], true) // Will be true, showing it's cached. 127 | t.end() 128 | }) 129 | -------------------------------------------------------------------------------- /test/fixtures/range-include.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [range, version, options] 4 | // version should be included by range 5 | module.exports = [ 6 | ['1.0.0 - 2.0.0', '1.2.3'], 7 | ['^1.2.3+build', '1.2.3'], 8 | ['^1.2.3+build', '1.3.0'], 9 | ['1.2.3-pre+asdf - 2.4.3-pre+asdf', '1.2.3'], 10 | ['1.2.3pre+asdf - 2.4.3-pre+asdf', '1.2.3', true], 11 | ['1.2.3-pre+asdf - 2.4.3pre+asdf', '1.2.3', true], 12 | ['1.2.3pre+asdf - 2.4.3pre+asdf', '1.2.3', true], 13 | ['1.2.3-pre+asdf - 2.4.3-pre+asdf', '1.2.3-pre.2'], 14 | ['1.2.3-pre+asdf - 2.4.3-pre+asdf', '2.4.3-alpha'], 15 | ['1.2.3+asdf - 2.4.3+asdf', '1.2.3'], 16 | ['1.0.0', '1.0.0'], 17 | ['>=*', '0.2.4'], 18 | ['', '1.0.0'], 19 | ['*', '1.2.3', {}], 20 | ['*', 'v1.2.3', { loose: 123 }], 21 | ['>=1.0.0', '1.0.0', /asdf/], 22 | ['>=1.0.0', '1.0.1', { loose: null }], 23 | ['>=1.0.0', '1.1.0', { loose: 0 }], 24 | ['>1.0.0', '1.0.1', { loose: undefined }], 25 | ['>1.0.0', '1.1.0'], 26 | ['<=2.0.0', '2.0.0'], 27 | ['<=2.0.0', '1.9999.9999'], 28 | ['<=2.0.0', '0.2.9'], 29 | ['<2.0.0', '1.9999.9999'], 30 | ['<2.0.0', '0.2.9'], 31 | ['>= 1.0.0', '1.0.0'], 32 | ['>= 1.0.0', '1.0.1'], 33 | ['>= 1.0.0', '1.1.0'], 34 | ['> 1.0.0', '1.0.1'], 35 | ['> 1.0.0', '1.1.0'], 36 | ['<= 2.0.0', '2.0.0'], 37 | ['<= 2.0.0', '1.9999.9999'], 38 | ['<= 2.0.0', '0.2.9'], 39 | ['< 2.0.0', '1.9999.9999'], 40 | ['<\t2.0.0', '0.2.9'], 41 | ['>=0.1.97', 'v0.1.97', true], 42 | ['>=0.1.97', '0.1.97'], 43 | ['0.1.20 || 1.2.4', '1.2.4'], 44 | ['>=0.2.3 || <0.0.1', '0.0.0'], 45 | ['>=0.2.3 || <0.0.1', '0.2.3'], 46 | ['>=0.2.3 || <0.0.1', '0.2.4'], 47 | ['||', '1.3.4'], 48 | ['2.x.x', '2.1.3'], 49 | ['1.2.x', '1.2.3'], 50 | ['1.2.x || 2.x', '2.1.3'], 51 | ['1.2.x || 2.x', '1.2.3'], 52 | ['x', '1.2.3'], 53 | ['2.*.*', '2.1.3'], 54 | ['1.2.*', '1.2.3'], 55 | ['1.2.* || 2.*', '2.1.3'], 56 | ['1.2.* || 2.*', '1.2.3'], 57 | ['*', '1.2.3'], 58 | ['2', '2.1.2'], 59 | ['2.3', '2.3.1'], 60 | ['~0.0.1', '0.0.1'], 61 | ['~0.0.1', '0.0.2'], 62 | ['~x', '0.0.9'], // >=2.4.0 <2.5.0 63 | ['~2', '2.0.9'], // >=2.4.0 <2.5.0 64 | ['~2.4', '2.4.0'], // >=2.4.0 <2.5.0 65 | ['~2.4', '2.4.5'], 66 | ['~>3.2.1', '3.2.2'], // >=3.2.1 <3.3.0, 67 | ['~1', '1.2.3'], // >=1.0.0 <2.0.0 68 | ['~>1', '1.2.3'], 69 | ['~> 1', '1.2.3'], 70 | ['~1.0', '1.0.2'], // >=1.0.0 <1.1.0, 71 | ['~ 1.0', '1.0.2'], 72 | ['~ 1.0.3', '1.0.12'], 73 | ['~ 1.0.3alpha', '1.0.12', { loose: true }], 74 | ['>=1', '1.0.0'], 75 | ['>= 1', '1.0.0'], 76 | ['<1.2', '1.1.1'], 77 | ['< 1.2', '1.1.1'], 78 | ['~v0.5.4-pre', '0.5.5'], 79 | ['~v0.5.4-pre', '0.5.4'], 80 | ['=0.7.x', '0.7.2'], 81 | ['<=0.7.x', '0.7.2'], 82 | ['>=0.7.x', '0.7.2'], 83 | ['<=0.7.x', '0.6.2'], 84 | ['~1.2.1 >=1.2.3', '1.2.3'], 85 | ['~1.2.1 =1.2.3', '1.2.3'], 86 | ['~1.2.1 1.2.3', '1.2.3'], 87 | ['~1.2.1 >=1.2.3 1.2.3', '1.2.3'], 88 | ['~1.2.1 1.2.3 >=1.2.3', '1.2.3'], 89 | ['>=1.2.1 1.2.3', '1.2.3'], 90 | ['1.2.3 >=1.2.1', '1.2.3'], 91 | ['>=1.2.3 >=1.2.1', '1.2.3'], 92 | ['>=1.2.1 >=1.2.3', '1.2.3'], 93 | ['>=1.2', '1.2.8'], 94 | ['^1.2.3', '1.8.1'], 95 | ['^0.1.2', '0.1.2'], 96 | ['^0.1', '0.1.2'], 97 | ['^0.0.1', '0.0.1'], 98 | ['^1.2', '1.4.2'], 99 | ['^1.2 ^1', '1.4.2'], 100 | ['^1.2.3-alpha', '1.2.3-pre'], 101 | ['^1.2.0-alpha', '1.2.0-pre'], 102 | ['^0.0.1-alpha', '0.0.1-beta'], 103 | ['^0.0.1-alpha', '0.0.1'], 104 | ['^0.1.1-alpha', '0.1.1-beta'], 105 | ['^x', '1.2.3'], 106 | ['x - 1.0.0', '0.9.7'], 107 | ['x - 1.x', '0.9.7'], 108 | ['1.0.0 - x', '1.9.7'], 109 | ['1.x - x', '1.9.7'], 110 | ['<=7.x', '7.9.9'], 111 | ['2.x', '2.0.0-pre.0', { includePrerelease: true }], 112 | ['2.x', '2.1.0-pre.0', { includePrerelease: true }], 113 | ['1.1.x', '1.1.0-a', { includePrerelease: true }], 114 | ['1.1.x', '1.1.1-a', { includePrerelease: true }], 115 | ['*', '1.0.0-rc1', { includePrerelease: true }], 116 | ['^1.0.0-0', '1.0.1-rc1', { includePrerelease: true }], 117 | ['^1.0.0-rc2', '1.0.1-rc1', { includePrerelease: true }], 118 | ['^1.0.0', '1.0.1-rc1', { includePrerelease: true }], 119 | ['^1.0.0', '1.1.0-rc1', { includePrerelease: true }], 120 | ['1 - 2', '2.0.0-pre', { includePrerelease: true }], 121 | ['1 - 2', '1.0.0-pre', { includePrerelease: true }], 122 | ['1.0 - 2', '1.0.0-pre', { includePrerelease: true }], 123 | 124 | ['=0.7.x', '0.7.0-asdf', { includePrerelease: true }], 125 | ['>=0.7.x', '0.7.0-asdf', { includePrerelease: true }], 126 | ['<=0.7.x', '0.7.0-asdf', { includePrerelease: true }], 127 | 128 | ['>=1.0.0 <=1.1.0', '1.1.0-pre', { includePrerelease: true }], 129 | ] 130 | -------------------------------------------------------------------------------- /test/fixtures/range-parse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [range, canonical result, options] 4 | // null result means it's not a valid range 5 | // '*' is the return value from functions.validRange(), but 6 | // new Range().range will be '' in those cases 7 | const { MAX_SAFE_INTEGER } = require('../../internal/constants') 8 | module.exports = [ 9 | ['1.0.0 - 2.0.0', '>=1.0.0 <=2.0.0'], 10 | ['1.0.0 - 2.0.0', '>=1.0.0-0 <2.0.1-0', { includePrerelease: true }], 11 | ['1 - 2', '>=1.0.0 <3.0.0-0'], 12 | ['1 - 2', '>=1.0.0-0 <3.0.0-0', { includePrerelease: true }], 13 | ['1.0 - 2.0', '>=1.0.0 <2.1.0-0'], 14 | ['1.0 - 2.0', '>=1.0.0-0 <2.1.0-0', { includePrerelease: true }], 15 | ['1.0.0', '1.0.0', { loose: false }], 16 | ['>=*', '*'], 17 | ['', '*'], 18 | ['*', '*'], 19 | ['>=1.0.0', '>=1.0.0'], 20 | ['>1.0.0', '>1.0.0'], 21 | ['<=2.0.0', '<=2.0.0'], 22 | ['1', '>=1.0.0 <2.0.0-0'], 23 | ['<2.0.0', '<2.0.0'], 24 | ['>= 1.0.0', '>=1.0.0'], 25 | ['>= 1.0.0', '>=1.0.0'], 26 | ['>= 1.0.0', '>=1.0.0'], 27 | ['> 1.0.0', '>1.0.0'], 28 | ['> 1.0.0', '>1.0.0'], 29 | ['<= 2.0.0', '<=2.0.0'], 30 | ['<= 2.0.0', '<=2.0.0'], 31 | ['<= 2.0.0', '<=2.0.0'], 32 | ['< 2.0.0', '<2.0.0'], 33 | ['<\t2.0.0', '<2.0.0'], 34 | ['>=0.1.97', '>=0.1.97'], 35 | ['0.1.20 || 1.2.4', '0.1.20||1.2.4'], 36 | ['>=0.2.3 || <0.0.1', '>=0.2.3||<0.0.1'], 37 | ['||', '*'], 38 | ['2.x.x', '>=2.0.0 <3.0.0-0'], 39 | ['1.2.x', '>=1.2.0 <1.3.0-0'], 40 | ['1.2.x || 2.x', '>=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0'], 41 | ['x', '*'], 42 | ['2.*.*', '>=2.0.0 <3.0.0-0'], 43 | ['1.2.*', '>=1.2.0 <1.3.0-0'], 44 | ['1.2.* || 2.*', '>=1.2.0 <1.3.0-0||>=2.0.0 <3.0.0-0'], 45 | ['2', '>=2.0.0 <3.0.0-0'], 46 | ['2.3', '>=2.3.0 <2.4.0-0'], 47 | ['~2.4', '>=2.4.0 <2.5.0-0'], 48 | ['~>3.2.1', '>=3.2.1 <3.3.0-0'], 49 | ['~1', '>=1.0.0 <2.0.0-0'], 50 | ['~>1', '>=1.0.0 <2.0.0-0'], 51 | ['~> 1', '>=1.0.0 <2.0.0-0'], 52 | ['~1.0', '>=1.0.0 <1.1.0-0'], 53 | ['~ 1.0', '>=1.0.0 <1.1.0-0'], 54 | ['^0', '<1.0.0-0'], 55 | ['^ 1', '>=1.0.0 <2.0.0-0'], 56 | ['^0.1', '>=0.1.0 <0.2.0-0'], 57 | ['^1.0', '>=1.0.0 <2.0.0-0'], 58 | ['^1.2', '>=1.2.0 <2.0.0-0'], 59 | ['^0.0.1', '>=0.0.1 <0.0.2-0'], 60 | ['^0.0.1-beta', '>=0.0.1-beta <0.0.2-0'], 61 | ['^0.1.2', '>=0.1.2 <0.2.0-0'], 62 | ['^1.2.3', '>=1.2.3 <2.0.0-0'], 63 | ['^1.2.3-beta.4', '>=1.2.3-beta.4 <2.0.0-0'], 64 | ['<1', '<1.0.0-0'], 65 | ['< 1', '<1.0.0-0'], 66 | ['>=1', '>=1.0.0'], 67 | ['>= 1', '>=1.0.0'], 68 | ['<1.2', '<1.2.0-0'], 69 | ['< 1.2', '<1.2.0-0'], 70 | ['>01.02.03', '>1.2.3', true], 71 | ['>01.02.03', null], 72 | ['~1.2.3beta', '>=1.2.3-beta <1.3.0-0', { loose: true }], 73 | ['~1.2.3beta', null], 74 | ['^ 1.2 ^ 1', '>=1.2.0 <2.0.0-0 >=1.0.0'], 75 | ['1.2 - 3.4.5', '>=1.2.0 <=3.4.5'], 76 | ['1.2.3 - 3.4', '>=1.2.3 <3.5.0-0'], 77 | ['1.2 - 3.4', '>=1.2.0 <3.5.0-0'], 78 | ['>1', '>=2.0.0'], 79 | ['>1.2', '>=1.3.0'], 80 | ['>X', '<0.0.0-0'], 81 | ['<X', '<0.0.0-0'], 82 | ['<x <* || >* 2.x', '<0.0.0-0'], 83 | ['>x 2.x || * || <x', '*'], 84 | ['>=09090', null], 85 | ['>=09090', '>=9090.0.0', true], 86 | ['>=09090-0', null, { includePrerelease: true }], 87 | ['>=09090-0', null, { loose: true, includePrerelease: true }], 88 | [`^${MAX_SAFE_INTEGER}.0.0`, null], 89 | [`=${MAX_SAFE_INTEGER}.0.0`, `${MAX_SAFE_INTEGER}.0.0`], 90 | [`^${MAX_SAFE_INTEGER - 1}.0.0`, `>=${MAX_SAFE_INTEGER - 1}.0.0 <${MAX_SAFE_INTEGER}.0.0-0`], 91 | // x-ranges with build metadata 92 | ['1.x.x+build >2.x+build', '>=1.0.0 <2.0.0-0 >=3.0.0', null], 93 | ['>=1.x+build <2.x.x+build', '>=1.0.0 <2.0.0-0', null], 94 | ['1.x.x+build || 2.x.x+build', '>=1.0.0 <2.0.0-0||>=2.0.0 <3.0.0-0', null], 95 | ['1.x+build.123', '>=1.0.0 <2.0.0-0', null], 96 | ['1.x.x+meta-data', '>=1.0.0 <2.0.0-0', null], 97 | ['1.x.x+build.123 >2.x.x+meta-data', '>=1.0.0 <2.0.0-0 >=3.0.0', null], 98 | ['1.x.x+build <2.x.x+meta', '>=1.0.0 <2.0.0-0', null], 99 | ['>1.x+build <=2.x.x+meta', '>=2.0.0 <3.0.0-0', null], 100 | [' 1.x.x+build >2.x.x+build ', '>=1.0.0 <2.0.0-0 >=3.0.0', null], 101 | ['^1.x+build', '>=1.0.0 <2.0.0-0', null], 102 | ['^1.x.x+build', '>=1.0.0 <2.0.0-0', null], 103 | ['^1.2.x+build', '>=1.2.0 <2.0.0-0', null], 104 | ['^1.x+meta-data', '>=1.0.0 <2.0.0-0', null], 105 | ['^1.x.x+build.123', '>=1.0.0 <2.0.0-0', null], 106 | ['~1.x+build', '>=1.0.0 <2.0.0-0', null], 107 | ['~1.x.x+build', '>=1.0.0 <2.0.0-0', null], 108 | ['~1.2.x+build', '>=1.2.0 <1.3.0-0', null], 109 | ['~1.x+meta-data', '>=1.0.0 <2.0.0-0', null], 110 | ['~1.x.x+build.123', '>=1.0.0 <2.0.0-0', null], 111 | ['^1.x.x+build || ~2.x.x+meta', '>=1.0.0 <2.0.0-0||>=2.0.0 <3.0.0-0', null], 112 | ['~1.x.x+build >2.x+meta', '>=1.0.0 <2.0.0-0 >=3.0.0', null], 113 | ['^1.x+build.123 <2.x.x+meta-data', '>=1.0.0 <2.0.0-0', null], 114 | // // x-ranges with prerelease and build 115 | ['1.x.x-alpha+build', '>=1.0.0 <2.0.0-0', null], 116 | ['>1.x.x-alpha+build', '>=2.0.0', null], 117 | ['>=1.x.x-alpha+build <2.x.x+build', '>=1.0.0 <2.0.0-0', null], 118 | ['1.x.x-alpha+build || 2.x.x+build', '>=1.0.0 <2.0.0-0||>=2.0.0 <3.0.0-0', null], 119 | ] 120 | -------------------------------------------------------------------------------- /test/ranges/subset.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const t = require('tap') 4 | const subset = require('../../ranges/subset.js') 5 | const Range = require('../../classes/range') 6 | 7 | // sub, dom, expect, [options] 8 | const cases = [ 9 | ['1.2.3', '1.2.3', true], 10 | ['1.2.3', '1.x', true], 11 | ['1.2.3 1.2.4', '1.2.3', true], 12 | ['1.2.3 1.2.4', '1.2.9', true], // null set is subset of everything 13 | ['1.2.3', '>1.2.0', true], 14 | ['1.2.3 2.3.4 || 2.3.4', '3', false], 15 | ['^1.2.3-pre.0', '1.x', false], 16 | ['^1.2.3-pre.0', '1.x', true, { includePrerelease: true }], 17 | ['>2 <1', '3', true], 18 | ['1 || 2 || 3', '>=1.0.0', true], 19 | 20 | // everything is a subset of * 21 | ['1.2.3', '*', true], 22 | ['^1.2.3', '*', true], 23 | ['^1.2.3-pre.0', '*', false], 24 | ['^1.2.3-pre.0', '*', true, { includePrerelease: true }], 25 | ['1 || 2 || 3', '*', true], 26 | 27 | // prerelease edge cases 28 | ['^1.2.3-pre.0', '>=1.0.0', false], 29 | ['^1.2.3-pre.0', '>=1.0.0', true, { includePrerelease: true }], 30 | ['^1.2.3-pre.0', '>=1.2.3-pre.0', true], 31 | ['^1.2.3-pre.0', '>=1.2.3-pre.0', true, { includePrerelease: true }], 32 | ['>1.2.3-pre.0', '>=1.2.3-pre.0', true], 33 | ['>1.2.3-pre.0', '>1.2.3-pre.0 || 2', true], 34 | ['1 >1.2.3-pre.0', '>1.2.3-pre.0', true], 35 | ['1 <=1.2.3-pre.0', '>=1.0.0-0', false], 36 | ['1 <=1.2.3-pre.0', '>=1.0.0-0', true, { includePrerelease: true }], 37 | ['1 <=1.2.3-pre.0', '<=1.2.3-pre.0', true], 38 | ['1 <=1.2.3-pre.0', '<=1.2.3-pre.0', true, { includePrerelease: true }], 39 | ['<1.2.3-pre.0', '<=1.2.3-pre.0', true], 40 | ['<1.2.3-pre.0', '<1.2.3-pre.0 || 2', true], 41 | ['1 <1.2.3-pre.0', '<1.2.3-pre.0', true], 42 | 43 | ['*', '*', true], 44 | ['', '*', true], 45 | ['*', '', true], 46 | ['', '', true], 47 | 48 | // >=0.0.0 is like * in non-prerelease mode 49 | // >=0.0.0-0 is like * in prerelease mode 50 | ['*', '>=0.0.0-0', true, { includePrerelease: true }], 51 | 52 | // true because these are identical in non-PR mode 53 | ['*', '>=0.0.0', true], 54 | 55 | // false because * includes 0.0.0-0 in PR mode 56 | ['*', '>=0.0.0', false, { includePrerelease: true }], 57 | 58 | // true because * doesn't include 0.0.0-0 in non-PR mode 59 | ['*', '>=0.0.0-0', true], 60 | 61 | ['^2 || ^3 || ^4', '>=1', true], 62 | ['^2 || ^3 || ^4', '>1', true], 63 | ['^2 || ^3 || ^4', '>=2', true], 64 | ['^2 || ^3 || ^4', '>=3', false], 65 | ['>=1', '^2 || ^3 || ^4', false], 66 | ['>1', '^2 || ^3 || ^4', false], 67 | ['>=2', '^2 || ^3 || ^4', false], 68 | ['>=3', '^2 || ^3 || ^4', false], 69 | ['^1', '^2 || ^3 || ^4', false], 70 | ['^2', '^2 || ^3 || ^4', true], 71 | ['^3', '^2 || ^3 || ^4', true], 72 | ['^4', '^2 || ^3 || ^4', true], 73 | ['1.x', '^2 || ^3 || ^4', false], 74 | ['2.x', '^2 || ^3 || ^4', true], 75 | ['3.x', '^2 || ^3 || ^4', true], 76 | ['4.x', '^2 || ^3 || ^4', true], 77 | 78 | ['>=1.0.0 <=1.0.0 || 2.0.0', '1.0.0 || 2.0.0', true], 79 | ['<=1.0.0 >=1.0.0 || 2.0.0', '1.0.0 || 2.0.0', true], 80 | ['>=1.0.0', '1.0.0', false], 81 | ['>=1.0.0 <2.0.0', '<2.0.0', true], 82 | ['>=1.0.0 <2.0.0', '>0.0.0', true], 83 | ['>=1.0.0 <=1.0.0', '1.0.0', true], 84 | ['>=1.0.0 <=1.0.0', '2.0.0', false], 85 | ['<2.0.0', '>=1.0.0 <2.0.0', false], 86 | ['>=1.0.0', '>=1.0.0 <2.0.0', false], 87 | ['>=1.0.0 <2.0.0', '<2.0.0', true], 88 | ['>=1.0.0 <2.0.0', '>=1.0.0', true], 89 | ['>=1.0.0 <2.0.0', '>1.0.0', false], 90 | ['>=1.0.0 <=2.0.0', '<2.0.0', false], 91 | ['>=1.0.0', '<1.0.0', false], 92 | ['<=1.0.0', '>1.0.0', false], 93 | ['<=1.0.0 >1.0.0', '>1.0.0', true], 94 | ['1.0.0 >1.0.0', '>1.0.0', true], 95 | ['1.0.0 <1.0.0', '>1.0.0', true], 96 | ['<1 <2 <3', '<4', true], 97 | ['<3 <2 <1', '<4', true], 98 | ['>1 >2 >3', '>0', true], 99 | ['>3 >2 >1', '>0', true], 100 | ['<=1 <=2 <=3', '<4', true], 101 | ['<=3 <=2 <=1', '<4', true], 102 | ['>=1 >=2 >=3', '>0', true], 103 | ['>=3 >=2 >=1', '>0', true], 104 | ['>=3 >=2 >=1', '>=3 >=2 >=1', true], 105 | ['>2.0.0', '>=2.0.0', true], 106 | ] 107 | 108 | t.plan(cases.length + 1) 109 | cases.forEach(([sub, dom, expect, options]) => { 110 | const msg = `${sub || "''"} ⊂ ${dom || "''"} = ${expect}` + 111 | (options ? ' ' + Object.keys(options).join(',') : '') 112 | t.equal(subset(sub, dom, options), expect, msg) 113 | }) 114 | 115 | t.test('range should be subset of itself in obj or string mode', t => { 116 | const range = '^1' 117 | t.equal(subset(range, range), true) 118 | t.equal(subset(range, new Range(range)), true) 119 | t.equal(subset(new Range(range), range), true) 120 | t.equal(subset(new Range(range), new Range(range)), true) 121 | 122 | // test with using the same actual object 123 | const r = new Range(range) 124 | t.equal(subset(r, r), true) 125 | 126 | // different range object with same set array 127 | const r2 = new Range(range) 128 | r2.set = r.set 129 | t.equal(subset(r2, r), true) 130 | t.equal(subset(r, r2), true) 131 | 132 | // different range with set with same simple set arrays 133 | const r3 = new Range(range) 134 | r3.set = [...r.set] 135 | t.equal(subset(r3, r), true) 136 | t.equal(subset(r, r3), true) 137 | 138 | // different range with set with simple sets with same comp objects 139 | const r4 = new Range(range) 140 | r4.set = r.set.map(s => [...s]) 141 | t.equal(subset(r4, r), true) 142 | t.equal(subset(r, r4), true) 143 | t.end() 144 | }) 145 | -------------------------------------------------------------------------------- /.github/workflows/post-dependabot.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: Post Dependabot 4 | 5 | on: pull_request 6 | 7 | permissions: 8 | contents: write 9 | 10 | jobs: 11 | template-oss: 12 | name: template-oss 13 | if: github.repository_owner == 'npm' && github.actor == 'dependabot[bot]' 14 | runs-on: ubuntu-latest 15 | defaults: 16 | run: 17 | shell: bash 18 | steps: 19 | - name: Checkout 20 | uses: actions/checkout@v4 21 | with: 22 | ref: ${{ github.event.pull_request.head.ref }} 23 | - name: Setup Git User 24 | run: | 25 | git config --global user.email "npm-cli+bot@github.com" 26 | git config --global user.name "npm CLI robot" 27 | - name: Setup Node 28 | uses: actions/setup-node@v4 29 | id: node 30 | with: 31 | node-version: 22.x 32 | check-latest: contains('22.x', '.x') 33 | - name: Install Latest npm 34 | uses: ./.github/actions/install-latest-npm 35 | with: 36 | node: ${{ steps.node.outputs.node-version }} 37 | - name: Install Dependencies 38 | run: npm i --ignore-scripts --no-audit --no-fund 39 | - name: Fetch Dependabot Metadata 40 | id: metadata 41 | uses: dependabot/fetch-metadata@v1 42 | with: 43 | github-token: ${{ secrets.GITHUB_TOKEN }} 44 | 45 | # Dependabot can update multiple directories so we output which directory 46 | # it is acting on so we can run the command for the correct root or workspace 47 | - name: Get Dependabot Directory 48 | if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss') 49 | id: flags 50 | run: | 51 | dependabot_dir="${{ steps.metadata.outputs.directory }}" 52 | if [[ "$dependabot_dir" == "/" || "$dependabot_dir" == "/main" ]]; then 53 | echo "workspace=-iwr" >> $GITHUB_OUTPUT 54 | else 55 | # strip leading slash from directory so it works as a 56 | # a path to the workspace flag 57 | echo "workspace=--workspace ${dependabot_dir#/}" >> $GITHUB_OUTPUT 58 | fi 59 | 60 | - name: Apply Changes 61 | if: steps.flags.outputs.workspace 62 | id: apply 63 | run: | 64 | npm run template-oss-apply ${{ steps.flags.outputs.workspace }} 65 | if [[ `git status --porcelain` ]]; then 66 | echo "changes=true" >> $GITHUB_OUTPUT 67 | fi 68 | # This only sets the conventional commit prefix. This workflow can't reliably determine 69 | # what the breaking change is though. If a BREAKING CHANGE message is required then 70 | # this PR check will fail and the commit will be amended with stafftools 71 | if [[ "${{ steps.metadata.outputs.update-type }}" == "version-update:semver-major" ]]; then 72 | prefix='feat!' 73 | else 74 | prefix='chore' 75 | fi 76 | echo "message=$prefix: postinstall for dependabot template-oss PR" >> $GITHUB_OUTPUT 77 | 78 | # This step will fail if template-oss has made any workflow updates. It is impossible 79 | # for a workflow to update other workflows. In the case it does fail, we continue 80 | # and then try to apply only a portion of the changes in the next step 81 | - name: Push All Changes 82 | if: steps.apply.outputs.changes 83 | id: push 84 | continue-on-error: true 85 | env: 86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 87 | run: | 88 | git commit -am "${{ steps.apply.outputs.message }}" 89 | git push 90 | 91 | # If the previous step failed, then reset the commit and remove any workflow changes 92 | # and attempt to commit and push again. This is helpful because we will have a commit 93 | # with the correct prefix that we can then --amend with @npmcli/stafftools later. 94 | - name: Push All Changes Except Workflows 95 | if: steps.apply.outputs.changes && steps.push.outcome == 'failure' 96 | env: 97 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 98 | run: | 99 | git reset HEAD~ 100 | git checkout HEAD -- .github/workflows/ 101 | git clean -fd .github/workflows/ 102 | git commit -am "${{ steps.apply.outputs.message }}" 103 | git push 104 | 105 | # Check if all the necessary template-oss changes were applied. Since we continued 106 | # on errors in one of the previous steps, this check will fail if our follow up 107 | # only applied a portion of the changes and we need to followup manually. 108 | # 109 | # Note that this used to run `lint` and `postlint` but that will fail this action 110 | # if we've also shipped any linting changes separate from template-oss. We do 111 | # linting in another action, so we want to fail this one only if there are 112 | # template-oss changes that could not be applied. 113 | - name: Check Changes 114 | if: steps.apply.outputs.changes 115 | run: | 116 | npm exec --offline ${{ steps.flags.outputs.workspace }} -- template-oss-check 117 | 118 | - name: Fail on Breaking Change 119 | if: steps.apply.outputs.changes && startsWith(steps.apply.outputs.message, 'feat!') 120 | run: | 121 | echo "This PR has a breaking change. Run 'npx -p @npmcli/stafftools gh template-oss-fix'" 122 | echo "for more information on how to fix this with a BREAKING CHANGE footer." 123 | exit 1 124 | -------------------------------------------------------------------------------- /bin/semver.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | // Standalone semver comparison program. 3 | // Exits successfully and prints matching version(s) if 4 | // any supplied version is valid and passes all tests. 5 | 6 | 'use strict' 7 | 8 | const argv = process.argv.slice(2) 9 | 10 | let versions = [] 11 | 12 | const range = [] 13 | 14 | let inc = null 15 | 16 | const version = require('../package.json').version 17 | 18 | let loose = false 19 | 20 | let includePrerelease = false 21 | 22 | let coerce = false 23 | 24 | let rtl = false 25 | 26 | let identifier 27 | 28 | let identifierBase 29 | 30 | const semver = require('../') 31 | const parseOptions = require('../internal/parse-options') 32 | 33 | let reverse = false 34 | 35 | let options = {} 36 | 37 | const main = () => { 38 | if (!argv.length) { 39 | return help() 40 | } 41 | while (argv.length) { 42 | let a = argv.shift() 43 | const indexOfEqualSign = a.indexOf('=') 44 | if (indexOfEqualSign !== -1) { 45 | const value = a.slice(indexOfEqualSign + 1) 46 | a = a.slice(0, indexOfEqualSign) 47 | argv.unshift(value) 48 | } 49 | switch (a) { 50 | case '-rv': case '-rev': case '--rev': case '--reverse': 51 | reverse = true 52 | break 53 | case '-l': case '--loose': 54 | loose = true 55 | break 56 | case '-p': case '--include-prerelease': 57 | includePrerelease = true 58 | break 59 | case '-v': case '--version': 60 | versions.push(argv.shift()) 61 | break 62 | case '-i': case '--inc': case '--increment': 63 | switch (argv[0]) { 64 | case 'major': case 'minor': case 'patch': case 'prerelease': 65 | case 'premajor': case 'preminor': case 'prepatch': 66 | case 'release': 67 | inc = argv.shift() 68 | break 69 | default: 70 | inc = 'patch' 71 | break 72 | } 73 | break 74 | case '--preid': 75 | identifier = argv.shift() 76 | break 77 | case '-r': case '--range': 78 | range.push(argv.shift()) 79 | break 80 | case '-n': 81 | identifierBase = argv.shift() 82 | if (identifierBase === 'false') { 83 | identifierBase = false 84 | } 85 | break 86 | case '-c': case '--coerce': 87 | coerce = true 88 | break 89 | case '--rtl': 90 | rtl = true 91 | break 92 | case '--ltr': 93 | rtl = false 94 | break 95 | case '-h': case '--help': case '-?': 96 | return help() 97 | default: 98 | versions.push(a) 99 | break 100 | } 101 | } 102 | 103 | options = parseOptions({ loose, includePrerelease, rtl }) 104 | 105 | versions = versions.map((v) => { 106 | return coerce ? (semver.coerce(v, options) || { version: v }).version : v 107 | }).filter((v) => { 108 | return semver.valid(v) 109 | }) 110 | if (!versions.length) { 111 | return fail() 112 | } 113 | if (inc && (versions.length !== 1 || range.length)) { 114 | return failInc() 115 | } 116 | 117 | for (let i = 0, l = range.length; i < l; i++) { 118 | versions = versions.filter((v) => { 119 | return semver.satisfies(v, range[i], options) 120 | }) 121 | if (!versions.length) { 122 | return fail() 123 | } 124 | } 125 | versions 126 | .sort((a, b) => semver[reverse ? 'rcompare' : 'compare'](a, b, options)) 127 | .map(v => semver.clean(v, options)) 128 | .map(v => inc ? semver.inc(v, inc, options, identifier, identifierBase) : v) 129 | .forEach(v => console.log(v)) 130 | } 131 | 132 | const failInc = () => { 133 | console.error('--inc can only be used on a single version with no range') 134 | fail() 135 | } 136 | 137 | const fail = () => process.exit(1) 138 | 139 | const help = () => console.log( 140 | `SemVer ${version} 141 | 142 | A JavaScript implementation of the https://semver.org/ specification 143 | Copyright Isaac Z. Schlueter 144 | 145 | Usage: semver [options] <version> [<version> [...]] 146 | Prints valid versions sorted by SemVer precedence 147 | 148 | Options: 149 | -r --range <range> 150 | Print versions that match the specified range. 151 | 152 | -i --increment [<level>] 153 | Increment a version by the specified level. Level can 154 | be one of: major, minor, patch, premajor, preminor, 155 | prepatch, prerelease, or release. Default level is 'patch'. 156 | Only one version may be specified. 157 | 158 | --preid <identifier> 159 | Identifier to be used to prefix premajor, preminor, 160 | prepatch or prerelease version increments. 161 | 162 | -l --loose 163 | Interpret versions and ranges loosely 164 | 165 | -p --include-prerelease 166 | Always include prerelease versions in range matching 167 | 168 | -c --coerce 169 | Coerce a string into SemVer if possible 170 | (does not imply --loose) 171 | 172 | --rtl 173 | Coerce version strings right to left 174 | 175 | --ltr 176 | Coerce version strings left to right (default) 177 | 178 | -n <base> 179 | Base number to be used for the prerelease identifier. 180 | Can be either 0 or 1, or false to omit the number altogether. 181 | Defaults to 0. 182 | 183 | Program exits successfully if any valid version satisfies 184 | all supplied ranges, and prints all satisfying versions. 185 | 186 | If no satisfying versions are found, then exits failure. 187 | 188 | Versions are printed in ascending order, so supplying 189 | multiple versions to the utility will just sort them.`) 190 | 191 | main() 192 | -------------------------------------------------------------------------------- /.github/workflows/ci-release.yml: -------------------------------------------------------------------------------- 1 | # This file is automatically added by @npmcli/template-oss. Do not edit. 2 | 3 | name: CI - Release 4 | 5 | on: 6 | workflow_dispatch: 7 | inputs: 8 | ref: 9 | required: true 10 | type: string 11 | default: main 12 | workflow_call: 13 | inputs: 14 | ref: 15 | required: true 16 | type: string 17 | check-sha: 18 | required: true 19 | type: string 20 | 21 | permissions: 22 | contents: read 23 | checks: write 24 | 25 | jobs: 26 | lint-all: 27 | name: Lint All 28 | if: github.repository_owner == 'npm' 29 | runs-on: ubuntu-latest 30 | defaults: 31 | run: 32 | shell: bash 33 | steps: 34 | - name: Checkout 35 | uses: actions/checkout@v4 36 | with: 37 | ref: ${{ inputs.ref }} 38 | - name: Setup Git User 39 | run: | 40 | git config --global user.email "npm-cli+bot@github.com" 41 | git config --global user.name "npm CLI robot" 42 | - name: Create Check 43 | id: create-check 44 | if: ${{ inputs.check-sha }} 45 | uses: ./.github/actions/create-check 46 | with: 47 | name: "Lint All" 48 | token: ${{ secrets.GITHUB_TOKEN }} 49 | sha: ${{ inputs.check-sha }} 50 | - name: Setup Node 51 | uses: actions/setup-node@v4 52 | id: node 53 | with: 54 | node-version: 22.x 55 | check-latest: contains('22.x', '.x') 56 | - name: Install Latest npm 57 | uses: ./.github/actions/install-latest-npm 58 | with: 59 | node: ${{ steps.node.outputs.node-version }} 60 | - name: Install Dependencies 61 | run: npm i --ignore-scripts --no-audit --no-fund 62 | - name: Lint 63 | run: npm run lint --ignore-scripts 64 | - name: Post Lint 65 | run: npm run postlint --ignore-scripts 66 | - name: Conclude Check 67 | uses: LouisBrunner/checks-action@v1.6.0 68 | if: steps.create-check.outputs.check-id && always() 69 | with: 70 | token: ${{ secrets.GITHUB_TOKEN }} 71 | conclusion: ${{ job.status }} 72 | check_id: ${{ steps.create-check.outputs.check-id }} 73 | 74 | test-all: 75 | name: Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }} 76 | if: github.repository_owner == 'npm' 77 | strategy: 78 | fail-fast: false 79 | matrix: 80 | platform: 81 | - name: Linux 82 | os: ubuntu-latest 83 | shell: bash 84 | - name: macOS 85 | os: macos-latest 86 | shell: bash 87 | - name: macOS 88 | os: macos-13 89 | shell: bash 90 | - name: Windows 91 | os: windows-latest 92 | shell: cmd 93 | node-version: 94 | - 10.0.0 95 | - 10.x 96 | - 12.x 97 | - 14.x 98 | - 16.x 99 | - 18.x 100 | - 20.x 101 | - 22.x 102 | exclude: 103 | - platform: { name: macOS, os: macos-latest, shell: bash } 104 | node-version: 10.0.0 105 | - platform: { name: macOS, os: macos-latest, shell: bash } 106 | node-version: 10.x 107 | - platform: { name: macOS, os: macos-latest, shell: bash } 108 | node-version: 12.x 109 | - platform: { name: macOS, os: macos-latest, shell: bash } 110 | node-version: 14.x 111 | - platform: { name: macOS, os: macos-13, shell: bash } 112 | node-version: 16.x 113 | - platform: { name: macOS, os: macos-13, shell: bash } 114 | node-version: 18.x 115 | - platform: { name: macOS, os: macos-13, shell: bash } 116 | node-version: 20.x 117 | - platform: { name: macOS, os: macos-13, shell: bash } 118 | node-version: 22.x 119 | runs-on: ${{ matrix.platform.os }} 120 | defaults: 121 | run: 122 | shell: ${{ matrix.platform.shell }} 123 | steps: 124 | - name: Checkout 125 | uses: actions/checkout@v4 126 | with: 127 | ref: ${{ inputs.ref }} 128 | - name: Setup Git User 129 | run: | 130 | git config --global user.email "npm-cli+bot@github.com" 131 | git config --global user.name "npm CLI robot" 132 | - name: Create Check 133 | id: create-check 134 | if: ${{ inputs.check-sha }} 135 | uses: ./.github/actions/create-check 136 | with: 137 | name: "Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}" 138 | token: ${{ secrets.GITHUB_TOKEN }} 139 | sha: ${{ inputs.check-sha }} 140 | - name: Setup Node 141 | uses: actions/setup-node@v4 142 | id: node 143 | with: 144 | node-version: ${{ matrix.node-version }} 145 | check-latest: contains(matrix.node-version, '.x') 146 | - name: Install Latest npm 147 | uses: ./.github/actions/install-latest-npm 148 | with: 149 | node: ${{ steps.node.outputs.node-version }} 150 | - name: Install Dependencies 151 | run: npm i --ignore-scripts --no-audit --no-fund 152 | - name: Add Problem Matcher 153 | run: echo "::add-matcher::.github/matchers/tap.json" 154 | - name: Test 155 | run: npm test --ignore-scripts 156 | - name: Conclude Check 157 | uses: LouisBrunner/checks-action@v1.6.0 158 | if: steps.create-check.outputs.check-id && always() 159 | with: 160 | token: ${{ secrets.GITHUB_TOKEN }} 161 | conclusion: ${{ job.status }} 162 | check_id: ${{ steps.create-check.outputs.check-id }} 163 | -------------------------------------------------------------------------------- /test/classes/semver.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const SemVer = require('../../classes/semver') 5 | const increments = require('../fixtures/increments.js') 6 | const comparisons = require('../fixtures/comparisons.js') 7 | const equality = require('../fixtures/equality.js') 8 | const invalidVersions = require('../fixtures/invalid-versions.js') 9 | const validVersions = require('../fixtures/valid-versions.js') 10 | 11 | test('valid versions', t => { 12 | t.plan(validVersions.length) 13 | validVersions.forEach(([v, major, minor, patch, prerelease, build]) => t.test(v, t => { 14 | const s = new SemVer(v) 15 | t.strictSame(s.major, major) 16 | t.strictSame(s.minor, minor) 17 | t.strictSame(s.patch, patch) 18 | t.strictSame(s.prerelease, prerelease) 19 | t.strictSame(s.build, build) 20 | t.strictSame(s.raw, v) 21 | t.end() 22 | })) 23 | }) 24 | 25 | test('comparisons', t => { 26 | t.plan(comparisons.length) 27 | comparisons.forEach(([v0, v1, opt]) => t.test(`${v0} ${v1}`, t => { 28 | const s0 = new SemVer(v0, opt) 29 | const s1 = new SemVer(v1, opt) 30 | t.equal(s0.compare(s1), 1) 31 | t.equal(s0.compare(v1), 1) 32 | t.equal(s1.compare(s0), -1) 33 | t.equal(s1.compare(v0), -1) 34 | t.equal(s0.compare(v0), 0) 35 | t.equal(s1.compare(v1), 0) 36 | t.end() 37 | })) 38 | }) 39 | 40 | test('equality', t => { 41 | t.plan(equality.length) 42 | equality.forEach(([v0, v1, loose]) => t.test(`${v0} ${v1} ${loose}`, t => { 43 | const s0 = new SemVer(v0, loose) 44 | const s1 = new SemVer(v1, loose) 45 | t.equal(s0.compare(s1), 0) 46 | t.equal(s1.compare(s0), 0) 47 | t.equal(s0.compare(v1), 0) 48 | t.equal(s1.compare(v0), 0) 49 | t.equal(s0.compare(s0), 0) 50 | t.equal(s1.compare(s1), 0) 51 | t.equal(s0.comparePre(s1), 0, 'comparePre just to hit that code path') 52 | t.end() 53 | })) 54 | }) 55 | 56 | test('toString equals parsed version', t => { 57 | t.equal(String(new SemVer('v1.2.3')), '1.2.3') 58 | t.end() 59 | }) 60 | 61 | test('throws when presented with garbage', t => { 62 | t.plan(invalidVersions.length) 63 | invalidVersions.forEach(([v, msg, opts]) => 64 | t.throws(() => new SemVer(v, opts), msg)) 65 | }) 66 | 67 | test('return SemVer arg to ctor if options match', t => { 68 | const s = new SemVer('1.2.3', { loose: true, includePrerelease: true }) 69 | t.equal(new SemVer(s, { loose: true, includePrerelease: true }), s, 70 | 'get same object when options match') 71 | t.not(new SemVer(s), s, 'get new object when options match') 72 | t.end() 73 | }) 74 | 75 | test('really big numeric prerelease value', (t) => { 76 | const r = new SemVer(`1.2.3-beta.${Number.MAX_SAFE_INTEGER}0`) 77 | t.strictSame(r.prerelease, ['beta', '90071992547409910']) 78 | t.end() 79 | }) 80 | 81 | test('invalid version numbers', (t) => { 82 | ['1.2.3.4', 'NOT VALID', 1.2, null, 'Infinity.NaN.Infinity'].forEach((v) => { 83 | t.throws( 84 | () => { 85 | new SemVer(v) // eslint-disable-line no-new 86 | }, 87 | { 88 | name: 'TypeError', 89 | message: 90 | typeof v === 'string' 91 | ? `Invalid Version: ${v}` 92 | : `Invalid version. Must be a string. Got type "${typeof v}".`, 93 | } 94 | ) 95 | }) 96 | 97 | t.end() 98 | }) 99 | 100 | test('incrementing', t => { 101 | t.plan(increments.length) 102 | increments.forEach(([ 103 | version, 104 | inc, 105 | expect, 106 | options, 107 | id, 108 | base, 109 | ]) => t.test(`${version} ${inc} ${id || ''}`.trim(), t => { 110 | if (expect === null) { 111 | t.plan(1) 112 | t.throws(() => new SemVer(version, options).inc(inc, id, base)) 113 | } else { 114 | t.plan(2) 115 | const incremented = new SemVer(version, options).inc(inc, id, base) 116 | t.equal(incremented.version, expect) 117 | if (incremented.build.length) { 118 | t.equal(incremented.raw, `${expect}+${incremented.build.join('.')}`) 119 | } else { 120 | t.equal(incremented.raw, expect) 121 | } 122 | } 123 | })) 124 | }) 125 | 126 | test('invalid increments', (t) => { 127 | t.throws( 128 | () => new SemVer('1.2.3').inc('prerelease', '', false), 129 | Error('invalid increment argument: identifier is empty') 130 | ) 131 | t.throws( 132 | () => new SemVer('1.2.3-dev').inc('prerelease', 'dev', false), 133 | Error('invalid increment argument: identifier already exists') 134 | ) 135 | t.throws( 136 | () => new SemVer('1.2.3').inc('prerelease', 'invalid/preid'), 137 | Error('invalid identifier: invalid/preid') 138 | ) 139 | 140 | t.end() 141 | }) 142 | 143 | test('increment side-effects', (t) => { 144 | const v = new SemVer('1.0.0') 145 | try { 146 | v.inc('prerelease', 'hot/mess') 147 | } catch (er) { 148 | // ignore but check that the version has not changed 149 | } 150 | t.equal(v.toString(), '1.0.0') 151 | t.end() 152 | }) 153 | 154 | test('compare main vs pre', (t) => { 155 | const s = new SemVer('1.2.3') 156 | t.equal(s.compareMain('2.3.4'), -1) 157 | t.equal(s.compareMain('1.2.4'), -1) 158 | t.equal(s.compareMain('0.1.2'), 1) 159 | t.equal(s.compareMain('1.2.2'), 1) 160 | t.equal(s.compareMain('1.2.3-pre'), 0) 161 | 162 | const p = new SemVer('1.2.3-alpha.0.pr.1') 163 | t.equal(p.comparePre('9.9.9-alpha.0.pr.1'), 0) 164 | t.equal(p.comparePre('1.2.3'), -1) 165 | t.equal(p.comparePre('1.2.3-alpha.0.pr.2'), -1) 166 | t.equal(p.comparePre('1.2.3-alpha.0.2'), 1) 167 | 168 | t.end() 169 | }) 170 | 171 | test('compareBuild', (t) => { 172 | const noBuild = new SemVer('1.0.0') 173 | const build0 = new SemVer('1.0.0+0') 174 | const build1 = new SemVer('1.0.0+1') 175 | const build10 = new SemVer('1.0.0+1.0') 176 | t.equal(noBuild.compareBuild(build0), -1) 177 | t.equal(build0.compareBuild(build0), 0) 178 | t.equal(build0.compareBuild(noBuild), 1) 179 | 180 | t.equal(build0.compareBuild('1.0.0+0.0'), -1) 181 | t.equal(build0.compareBuild(build1), -1) 182 | t.equal(build1.compareBuild(build0), 1) 183 | t.equal(build10.compareBuild(build1), 1) 184 | 185 | t.end() 186 | }) 187 | -------------------------------------------------------------------------------- /test/functions/coerce.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const { test } = require('tap') 4 | const coerce = require('../../functions/coerce') 5 | const parse = require('../../functions/parse') 6 | const valid = require('../../functions/valid') 7 | 8 | test('coerce tests', (t) => { 9 | // Expected to be null (cannot be coerced). 10 | const coerceToNull = [ 11 | null, 12 | { version: '1.2.3' }, 13 | function () { 14 | return '1.2.3' 15 | }, 16 | '', 17 | '.', 18 | 'version one', 19 | '9'.repeat(16), 20 | '1'.repeat(17), 21 | `a${'9'.repeat(16)}`, 22 | `a${'1'.repeat(17)}`, 23 | `${'9'.repeat(16)}a`, 24 | `${'1'.repeat(17)}a`, 25 | `${'9'.repeat(16)}.4.7.4`, 26 | `${'9'.repeat(16)}.${'2'.repeat(16)}.${'3'.repeat(16)}`, 27 | `${'1'.repeat(16)}.${'9'.repeat(16)}.${'3'.repeat(16)}`, 28 | `${'1'.repeat(16)}.${'2'.repeat(16)}.${'9'.repeat(16)}`, 29 | ] 30 | coerceToNull.forEach((input) => { 31 | const msg = `coerce(${input}) should be null` 32 | t.same(coerce(input), null, msg) 33 | }) 34 | 35 | // Expected to be valid. 36 | const coerceToValid = [ 37 | [parse('1.2.3'), '1.2.3'], 38 | ['.1', '1.0.0'], 39 | ['.1.', '1.0.0'], 40 | ['..1', '1.0.0'], 41 | ['.1.1', '1.1.0'], 42 | ['1.', '1.0.0'], 43 | ['1.0', '1.0.0'], 44 | ['1.0.0', '1.0.0'], 45 | ['0', '0.0.0'], 46 | ['0.0', '0.0.0'], 47 | ['0.0.0', '0.0.0'], 48 | ['0.1', '0.1.0'], 49 | ['0.0.1', '0.0.1'], 50 | ['0.1.1', '0.1.1'], 51 | ['1', '1.0.0'], 52 | ['1.2', '1.2.0'], 53 | ['1.2.3', '1.2.3'], 54 | ['1.2.3.4', '1.2.3'], 55 | ['13', '13.0.0'], 56 | ['35.12', '35.12.0'], 57 | ['35.12.18', '35.12.18'], 58 | ['35.12.18.24', '35.12.18'], 59 | ['v1', '1.0.0'], 60 | ['v1.2', '1.2.0'], 61 | ['v1.2.3', '1.2.3'], 62 | ['v1.2.3.4', '1.2.3'], 63 | [' 1', '1.0.0'], 64 | ['1 ', '1.0.0'], 65 | ['1 0', '1.0.0'], 66 | ['1 1', '1.0.0'], 67 | ['1.1 1', '1.1.0'], 68 | ['1.1-1', '1.1.0'], 69 | ['1.1-1', '1.1.0'], 70 | ['a1', '1.0.0'], 71 | ['a1a', '1.0.0'], 72 | ['1a', '1.0.0'], 73 | ['version 1', '1.0.0'], 74 | ['version1', '1.0.0'], 75 | ['version1.0', '1.0.0'], 76 | ['version1.1', '1.1.0'], 77 | ['42.6.7.9.3-alpha', '42.6.7'], 78 | ['v2', '2.0.0'], 79 | ['v3.4 replaces v3.3.1', '3.4.0'], 80 | ['4.6.3.9.2-alpha2', '4.6.3'], 81 | [`${'1'.repeat(17)}.2`, '2.0.0'], 82 | [`${'1'.repeat(17)}.2.3`, '2.3.0'], 83 | [`1.${'2'.repeat(17)}.3`, '1.0.0'], 84 | [`1.2.${'3'.repeat(17)}`, '1.2.0'], 85 | [`${'1'.repeat(17)}.2.3.4`, '2.3.4'], 86 | [`1.${'2'.repeat(17)}.3.4`, '1.0.0'], 87 | [`1.2.${'3'.repeat(17)}.4`, '1.2.0'], 88 | [`${'1'.repeat(17)}.${'2'.repeat(16)}.${'3'.repeat(16)}`, 89 | `${'2'.repeat(16)}.${'3'.repeat(16)}.0`], 90 | [`${'1'.repeat(16)}.${'2'.repeat(17)}.${'3'.repeat(16)}`, 91 | `${'1'.repeat(16)}.0.0`], 92 | [`${'1'.repeat(16)}.${'2'.repeat(16)}.${'3'.repeat(17)}`, 93 | `${'1'.repeat(16)}.${'2'.repeat(16)}.0`], 94 | [`11${'.1'.repeat(126)}`, '11.1.1'], 95 | ['1'.repeat(16), `${'1'.repeat(16)}.0.0`], 96 | [`a${'1'.repeat(16)}`, `${'1'.repeat(16)}.0.0`], 97 | [`${'1'.repeat(16)}.2.3.4`, `${'1'.repeat(16)}.2.3`], 98 | [`1.${'2'.repeat(16)}.3.4`, `1.${'2'.repeat(16)}.3`], 99 | [`1.2.${'3'.repeat(16)}.4`, `1.2.${'3'.repeat(16)}`], 100 | [`${'1'.repeat(16)}.${'2'.repeat(16)}.${'3'.repeat(16)}`, 101 | `${'1'.repeat(16)}.${'2'.repeat(16)}.${'3'.repeat(16)}`], 102 | [`1.2.3.${'4'.repeat(252)}.5`, '1.2.3'], 103 | [`1.2.3.${'4'.repeat(1024)}`, '1.2.3'], 104 | [`${'1'.repeat(17)}.4.7.4`, '4.7.4'], 105 | [10, '10.0.0'], 106 | ['1.2.3/a/b/c/2.3.4', '2.3.4', { rtl: true }], 107 | ['1.2.3.4.5.6', '4.5.6', { rtl: true }], 108 | ['1.2.3.4.5/6', '6.0.0', { rtl: true }], 109 | ['1.2.3.4./6', '6.0.0', { rtl: true }], 110 | ['1.2.3.4/6', '6.0.0', { rtl: true }], 111 | ['1.2.3./6', '6.0.0', { rtl: true }], 112 | ['1.2.3/6', '6.0.0', { rtl: true }], 113 | ['1.2.3.4', '2.3.4', { rtl: true }], 114 | ['1.2.3.4xyz', '2.3.4', { rtl: true }], 115 | 116 | ['1-rc.5', '1.0.0-rc.5', { includePrerelease: true }], 117 | ['1.2-rc.5', '1.2.0-rc.5', { includePrerelease: true }], 118 | ['1.2.3-rc.5', '1.2.3-rc.5', { includePrerelease: true }], 119 | ['1.2.3-rc.5/a', '1.2.3-rc.5', { includePrerelease: true }], 120 | ['1.2.3.4-rc.5', '1.2.3', { includePrerelease: true }], 121 | ['1.2.3.4+rev.6', '1.2.3', { includePrerelease: true }], 122 | 123 | ['1.0.0-1a', '1.0.0-1a', { includePrerelease: true }], 124 | ['1.0.0-alpha.12ab', '1.0.0-alpha.12ab', { includePrerelease: true }], 125 | ['1.0.0-alpha.1234.23cd', '1.0.0-alpha.1234.23cd', { includePrerelease: true }], 126 | ['1.0.0-nightly.abc123', '1.0.0-nightly.abc123', { includePrerelease: true }], 127 | ['1.0.0-nightly.abcdef', '1.0.0-nightly.abcdef', { includePrerelease: true }], 128 | ['1.0.0-nightly.123456', '1.0.0-nightly.123456', { includePrerelease: true }], 129 | 130 | ['1+rev.6', '1.0.0+rev.6', { includePrerelease: true }], 131 | ['1.2+rev.6', '1.2.0+rev.6', { includePrerelease: true }], 132 | ['1.2.3+rev.6', '1.2.3+rev.6', { includePrerelease: true }], 133 | ['1.2.3+rev.6/a', '1.2.3+rev.6', { includePrerelease: true }], 134 | ['1.2.3.4-rc.5', '1.2.3', { includePrerelease: true }], 135 | ['1.2.3.4+rev.6', '1.2.3', { includePrerelease: true }], 136 | 137 | ['1-rc.5+rev.6', '1.0.0-rc.5+rev.6', { includePrerelease: true }], 138 | ['1.2-rc.5+rev.6', '1.2.0-rc.5+rev.6', { includePrerelease: true }], 139 | ['1.2.3-rc.5+rev.6', '1.2.3-rc.5+rev.6', { includePrerelease: true }], 140 | ['1.2.3-rc.5+rev.6/a', '1.2.3-rc.5+rev.6', { includePrerelease: true }], 141 | 142 | ['1.2-rc.5+rev.6', '1.2.0-rc.5+rev.6', { rtl: true, includePrerelease: true }], 143 | ['1.2.3-rc.5+rev.6', '1.2.3-rc.5+rev.6', { rtl: true, includePrerelease: true }], 144 | ['1.2.3.4-rc.5+rev.6', '2.3.4-rc.5+rev.6', { rtl: true, includePrerelease: true }], 145 | ['1.2.3.4-rc.5', '2.3.4-rc.5', { rtl: true, includePrerelease: true }], 146 | ['1.2.3.4+rev.6', '2.3.4+rev.6', { rtl: true, includePrerelease: true }], 147 | ['1.2.3.4-rc.5+rev.6/7', '7.0.0', { rtl: true, includePrerelease: true }], 148 | ['1.2.3.4-rc/7.5+rev.6', '7.5.0+rev.6', { rtl: true, includePrerelease: true }], 149 | ['1.2.3.4/7-rc.5+rev.6', '7.0.0-rc.5+rev.6', { rtl: true, includePrerelease: true }], 150 | ] 151 | coerceToValid.forEach(([input, expected, options]) => { 152 | const coerceExpression = `coerce(${input}, ${JSON.stringify(options)})` 153 | const coercedVersion = coerce(input, options) || {} 154 | const expectedVersion = parse(expected) 155 | t.equal(expectedVersion.compare(coercedVersion), 0, 156 | `${coerceExpression} should be equal to ${expectedVersion}`) 157 | t.equal(expectedVersion.compareBuild(coercedVersion), 0, 158 | `${coerceExpression} build should be equal to ${expectedVersion}`) 159 | }) 160 | 161 | t.same(valid(coerce('42.6.7.9.3-alpha')), '42.6.7') 162 | t.same(valid(coerce('42.6.7-alpha+rev.1', { includePrerelease: true })), '42.6.7-alpha') 163 | t.same(valid(coerce('v2')), '2.0.0') 164 | 165 | t.end() 166 | }) 167 | -------------------------------------------------------------------------------- /test/fixtures/increments.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // [version, inc, result, options, identifier, identifierBase] 4 | // inc(version, inc, options, identifier, identifierBase) -> result 5 | module.exports = [ 6 | ['1.2.3', 'major', '2.0.0'], 7 | ['1.2.3', 'minor', '1.3.0'], 8 | ['1.2.3', 'patch', '1.2.4'], 9 | ['1.2.3tag', 'major', '2.0.0', true], 10 | ['1.2.3-tag', 'major', '2.0.0'], 11 | ['1.2.3', 'fake', null], 12 | ['1.2.0-0', 'patch', '1.2.0'], 13 | ['fake', 'major', null], 14 | ['1.2.3-4', 'major', '2.0.0'], 15 | ['1.2.3-4', 'minor', '1.3.0'], 16 | ['1.2.3-4', 'patch', '1.2.3'], 17 | ['1.2.3-alpha.0.beta', 'major', '2.0.0'], 18 | ['1.2.3-alpha.0.beta', 'minor', '1.3.0'], 19 | ['1.2.3-alpha.0.beta', 'patch', '1.2.3'], 20 | ['1.2.4', 'prerelease', '1.2.5-0'], 21 | ['1.2.3-0', 'prerelease', '1.2.3-1'], 22 | ['1.2.3-alpha.0', 'prerelease', '1.2.3-alpha.1'], 23 | ['1.2.3-alpha.1', 'prerelease', '1.2.3-alpha.2'], 24 | ['1.2.3-alpha.2', 'prerelease', '1.2.3-alpha.3'], 25 | ['1.2.3-alpha.0.beta', 'prerelease', '1.2.3-alpha.1.beta'], 26 | ['1.2.3-alpha.1.beta', 'prerelease', '1.2.3-alpha.2.beta'], 27 | ['1.2.3-alpha.2.beta', 'prerelease', '1.2.3-alpha.3.beta'], 28 | ['1.2.3-alpha.10.0.beta', 'prerelease', '1.2.3-alpha.10.1.beta'], 29 | ['1.2.3-alpha.10.1.beta', 'prerelease', '1.2.3-alpha.10.2.beta'], 30 | ['1.2.3-alpha.10.2.beta', 'prerelease', '1.2.3-alpha.10.3.beta'], 31 | ['1.2.3-alpha.10.beta.0', 'prerelease', '1.2.3-alpha.10.beta.1'], 32 | ['1.2.3-alpha.10.beta.1', 'prerelease', '1.2.3-alpha.10.beta.2'], 33 | ['1.2.3-alpha.10.beta.2', 'prerelease', '1.2.3-alpha.10.beta.3'], 34 | ['1.2.3-alpha.9.beta', 'prerelease', '1.2.3-alpha.10.beta'], 35 | ['1.2.3-alpha.10.beta', 'prerelease', '1.2.3-alpha.11.beta'], 36 | ['1.2.3-alpha.11.beta', 'prerelease', '1.2.3-alpha.12.beta'], 37 | ['1.0.0', 'prepatch', '1.0.1-alpha.1.1a.0', null, 'alpha.1.1a'], 38 | ['1.2.0', 'prepatch', '1.2.1-0'], 39 | ['1.2.0-1', 'prepatch', '1.2.1-0'], 40 | ['1.2.0', 'preminor', '1.3.0-0'], 41 | ['1.2.3-1', 'preminor', '1.3.0-0'], 42 | ['1.2.0', 'premajor', '2.0.0-0'], 43 | ['1.2.3-1', 'premajor', '2.0.0-0'], 44 | ['1.2.0-1', 'minor', '1.2.0'], 45 | ['1.0.0-1', 'major', '1.0.0'], 46 | ['1.0.0-1', 'release', '1.0.0'], 47 | ['1.2.0-1', 'release', '1.2.0'], 48 | ['1.2.3-1', 'release', '1.2.3'], 49 | ['1.2.3', 'release', null], 50 | 51 | // [version, inc, result, identifierIndex, loose, identifier] 52 | ['1.2.3', 'major', '2.0.0', false, 'dev'], 53 | ['1.2.3', 'minor', '1.3.0', false, 'dev'], 54 | ['1.2.3', 'patch', '1.2.4', false, 'dev'], 55 | ['1.2.3tag', 'major', '2.0.0', true, 'dev'], 56 | ['1.2.3-tag', 'major', '2.0.0', false, 'dev'], 57 | ['1.2.3', 'fake', null, false, 'dev'], 58 | ['1.2.0-0', 'patch', '1.2.0', false, 'dev'], 59 | ['fake', 'major', null, false, 'dev'], 60 | ['1.2.3-4', 'major', '2.0.0', false, 'dev'], 61 | ['1.2.3-4', 'minor', '1.3.0', false, 'dev'], 62 | ['1.2.3-4', 'patch', '1.2.3', false, 'dev'], 63 | ['1.2.3-alpha.0.beta', 'major', '2.0.0', false, 'dev'], 64 | ['1.2.3-alpha.0.beta', 'minor', '1.3.0', false, 'dev'], 65 | ['1.2.3-alpha.0.beta', 'patch', '1.2.3', false, 'dev'], 66 | ['1.2.4', 'prerelease', '1.2.5-dev.0', false, 'dev'], 67 | ['1.2.3-0', 'prerelease', '1.2.3-dev.0', false, 'dev'], 68 | ['1.2.3-alpha.0', 'prerelease', '1.2.3-dev.0', false, 'dev'], 69 | ['1.2.3-alpha.0', 'prerelease', '1.2.3-alpha.1', false, 'alpha'], 70 | ['1.2.3-alpha.0.beta', 'prerelease', '1.2.3-dev.0', false, 'dev'], 71 | ['1.2.3-alpha.0.beta', 'prerelease', '1.2.3-alpha.1.beta', false, 'alpha'], 72 | ['1.2.3-alpha.10.0.beta', 'prerelease', '1.2.3-dev.0', false, 'dev'], 73 | ['1.2.3-alpha.10.0.beta', 'prerelease', '1.2.3-alpha.10.1.beta', false, 'alpha'], 74 | ['1.2.3-alpha.10.1.beta', 'prerelease', '1.2.3-alpha.10.2.beta', false, 'alpha'], 75 | ['1.2.3-alpha.10.2.beta', 'prerelease', '1.2.3-alpha.10.3.beta', false, 'alpha'], 76 | ['1.2.3-alpha.10.beta.0', 'prerelease', '1.2.3-dev.0', false, 'dev'], 77 | ['1.2.3-alpha.10.beta.0', 'prerelease', '1.2.3-alpha.10.beta.1', false, 'alpha'], 78 | ['1.2.3-alpha.10.beta.1', 'prerelease', '1.2.3-alpha.10.beta.2', false, 'alpha'], 79 | ['1.2.3-alpha.10.beta.2', 'prerelease', '1.2.3-alpha.10.beta.3', false, 'alpha'], 80 | ['1.2.3-alpha.9.beta', 'prerelease', '1.2.3-dev.0', false, 'dev'], 81 | ['1.2.3-alpha.9.beta', 'prerelease', '1.2.3-alpha.10.beta', false, 'alpha'], 82 | ['1.2.3-alpha.10.beta', 'prerelease', '1.2.3-alpha.11.beta', false, 'alpha'], 83 | ['1.2.3-alpha.11.beta', 'prerelease', '1.2.3-alpha.12.beta', false, 'alpha'], 84 | ['1.2.0', 'prepatch', '1.2.1-dev.0', false, 'dev'], 85 | ['1.2.0-1', 'prepatch', '1.2.1-dev.0', false, 'dev'], 86 | ['1.2.0', 'preminor', '1.3.0-dev.0', false, 'dev'], 87 | ['1.2.3-1', 'preminor', '1.3.0-dev.0', false, 'dev'], 88 | ['1.2.0', 'premajor', '2.0.0-dev.0', false, 'dev'], 89 | ['1.2.3-1', 'premajor', '2.0.0-dev.0', false, 'dev'], 90 | ['1.2.3-1', 'premajor', '2.0.0-dev.1', false, 'dev', 1], 91 | ['1.2.0-1', 'minor', '1.2.0', false, 'dev'], 92 | ['1.0.0-1', 'major', '1.0.0', 'dev'], 93 | ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.0', false, 'dev'], 94 | ['1.2.3-0', 'prerelease', '1.2.3-1.0', false, '1'], 95 | ['1.2.3-1.0', 'prerelease', '1.2.3-1.1', false, '1'], 96 | ['1.2.3-1.1', 'prerelease', '1.2.3-1.2', false, '1'], 97 | ['1.2.3-1.1', 'prerelease', '1.2.3-2.0', false, '2'], 98 | 99 | // [version, inc, result, identifierIndex, loose, identifier, identifierBase] 100 | ['1.2.0-1', 'prerelease', '1.2.0-alpha.0', false, 'alpha', '0'], 101 | ['1.2.1', 'prerelease', '1.2.2-alpha.0', false, 'alpha', '0'], 102 | ['0.2.0', 'prerelease', '0.2.1-alpha.0', false, 'alpha', '0'], 103 | ['1.2.2', 'prerelease', '1.2.3-alpha.1', false, 'alpha', '1'], 104 | ['1.2.3', 'prerelease', '1.2.4-alpha.1', false, 'alpha', '1'], 105 | ['1.2.4', 'prerelease', '1.2.5-alpha.1', false, 'alpha', '1'], 106 | ['1.2.0', 'prepatch', '1.2.1-dev.1', false, 'dev', '1'], 107 | ['1.2.0-1', 'prepatch', '1.2.1-dev.1', false, 'dev', '1'], 108 | ['1.2.0', 'premajor', '2.0.0-dev.0', false, 'dev', '0'], 109 | ['1.2.3-1', 'premajor', '2.0.0-dev.0', false, 'dev', '0'], 110 | ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.0', false, 'dev', '0'], 111 | ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.1', false, 'dev', '1'], 112 | ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.bar.0', false, '', '0'], 113 | ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.bar.1', false, '', '1'], 114 | ['1.2.0', 'preminor', '1.3.0-dev.1', false, 'dev', '1'], 115 | ['1.2.3-1', 'preminor', '1.3.0-dev.0', false, 'dev'], 116 | ['1.2.0', 'prerelease', '1.2.1-1', false, '', '1'], 117 | 118 | ['1.2.0-1', 'prerelease', '1.2.0-alpha', false, 'alpha', false], 119 | ['1.2.1', 'prerelease', '1.2.2-alpha', false, 'alpha', false], 120 | ['1.2.2', 'prerelease', '1.2.3-alpha', false, 'alpha', false], 121 | ['1.2.0', 'prepatch', '1.2.1-dev', false, 'dev', false], 122 | ['1.2.0-1', 'prepatch', '1.2.1-dev', false, 'dev', false], 123 | ['1.2.0', 'premajor', '2.0.0-dev', false, 'dev', false], 124 | ['1.2.3-1', 'premajor', '2.0.0-dev', false, 'dev', false], 125 | ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev', false, 'dev', false], 126 | ['1.2.3-dev.bar', 'prerelease', '1.2.3-dev.baz', false, 'dev.baz', false], 127 | ['1.2.0', 'preminor', '1.3.0-dev', false, 'dev', false], 128 | ['1.2.3-1', 'preminor', '1.3.0-dev', false, 'dev', false], 129 | ['1.2.3-dev', 'prerelease', null, false, 'dev', false], 130 | ['1.2.0-dev', 'premajor', '2.0.0-dev', false, 'dev', false], 131 | ['1.2.0-dev', 'preminor', '1.3.0-beta', false, 'beta', false], 132 | ['1.2.0-dev', 'prepatch', '1.2.1-dev', false, 'dev', false], 133 | ['1.2.0', 'prerelease', null, false, '', false], 134 | ['1.0.0-rc.1+build.4', 'prerelease', '1.0.0-rc.2', 'rc', false], 135 | ['1.2.0', 'prerelease', null, false, 'invalid/preid'], 136 | ['1.2.0', 'prerelease', null, false, 'invalid+build'], 137 | ['1.2.0beta', 'prerelease', null, { loose: true }, 'invalid/preid'], 138 | ] 139 | -------------------------------------------------------------------------------- /ranges/subset.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const Range = require('../classes/range.js') 4 | const Comparator = require('../classes/comparator.js') 5 | const { ANY } = Comparator 6 | const satisfies = require('../functions/satisfies.js') 7 | const compare = require('../functions/compare.js') 8 | 9 | // Complex range `r1 || r2 || ...` is a subset of `R1 || R2 || ...` iff: 10 | // - Every simple range `r1, r2, ...` is a null set, OR 11 | // - Every simple range `r1, r2, ...` which is not a null set is a subset of 12 | // some `R1, R2, ...` 13 | // 14 | // Simple range `c1 c2 ...` is a subset of simple range `C1 C2 ...` iff: 15 | // - If c is only the ANY comparator 16 | // - If C is only the ANY comparator, return true 17 | // - Else if in prerelease mode, return false 18 | // - else replace c with `[>=0.0.0]` 19 | // - If C is only the ANY comparator 20 | // - if in prerelease mode, return true 21 | // - else replace C with `[>=0.0.0]` 22 | // - Let EQ be the set of = comparators in c 23 | // - If EQ is more than one, return true (null set) 24 | // - Let GT be the highest > or >= comparator in c 25 | // - Let LT be the lowest < or <= comparator in c 26 | // - If GT and LT, and GT.semver > LT.semver, return true (null set) 27 | // - If any C is a = range, and GT or LT are set, return false 28 | // - If EQ 29 | // - If GT, and EQ does not satisfy GT, return true (null set) 30 | // - If LT, and EQ does not satisfy LT, return true (null set) 31 | // - If EQ satisfies every C, return true 32 | // - Else return false 33 | // - If GT 34 | // - If GT.semver is lower than any > or >= comp in C, return false 35 | // - If GT is >=, and GT.semver does not satisfy every C, return false 36 | // - If GT.semver has a prerelease, and not in prerelease mode 37 | // - If no C has a prerelease and the GT.semver tuple, return false 38 | // - If LT 39 | // - If LT.semver is greater than any < or <= comp in C, return false 40 | // - If LT is <=, and LT.semver does not satisfy every C, return false 41 | // - If GT.semver has a prerelease, and not in prerelease mode 42 | // - If no C has a prerelease and the LT.semver tuple, return false 43 | // - Else return true 44 | 45 | const subset = (sub, dom, options = {}) => { 46 | if (sub === dom) { 47 | return true 48 | } 49 | 50 | sub = new Range(sub, options) 51 | dom = new Range(dom, options) 52 | let sawNonNull = false 53 | 54 | OUTER: for (const simpleSub of sub.set) { 55 | for (const simpleDom of dom.set) { 56 | const isSub = simpleSubset(simpleSub, simpleDom, options) 57 | sawNonNull = sawNonNull || isSub !== null 58 | if (isSub) { 59 | continue OUTER 60 | } 61 | } 62 | // the null set is a subset of everything, but null simple ranges in 63 | // a complex range should be ignored. so if we saw a non-null range, 64 | // then we know this isn't a subset, but if EVERY simple range was null, 65 | // then it is a subset. 66 | if (sawNonNull) { 67 | return false 68 | } 69 | } 70 | return true 71 | } 72 | 73 | const minimumVersionWithPreRelease = [new Comparator('>=0.0.0-0')] 74 | const minimumVersion = [new Comparator('>=0.0.0')] 75 | 76 | const simpleSubset = (sub, dom, options) => { 77 | if (sub === dom) { 78 | return true 79 | } 80 | 81 | if (sub.length === 1 && sub[0].semver === ANY) { 82 | if (dom.length === 1 && dom[0].semver === ANY) { 83 | return true 84 | } else if (options.includePrerelease) { 85 | sub = minimumVersionWithPreRelease 86 | } else { 87 | sub = minimumVersion 88 | } 89 | } 90 | 91 | if (dom.length === 1 && dom[0].semver === ANY) { 92 | if (options.includePrerelease) { 93 | return true 94 | } else { 95 | dom = minimumVersion 96 | } 97 | } 98 | 99 | const eqSet = new Set() 100 | let gt, lt 101 | for (const c of sub) { 102 | if (c.operator === '>' || c.operator === '>=') { 103 | gt = higherGT(gt, c, options) 104 | } else if (c.operator === '<' || c.operator === '<=') { 105 | lt = lowerLT(lt, c, options) 106 | } else { 107 | eqSet.add(c.semver) 108 | } 109 | } 110 | 111 | if (eqSet.size > 1) { 112 | return null 113 | } 114 | 115 | let gtltComp 116 | if (gt && lt) { 117 | gtltComp = compare(gt.semver, lt.semver, options) 118 | if (gtltComp > 0) { 119 | return null 120 | } else if (gtltComp === 0 && (gt.operator !== '>=' || lt.operator !== '<=')) { 121 | return null 122 | } 123 | } 124 | 125 | // will iterate one or zero times 126 | for (const eq of eqSet) { 127 | if (gt && !satisfies(eq, String(gt), options)) { 128 | return null 129 | } 130 | 131 | if (lt && !satisfies(eq, String(lt), options)) { 132 | return null 133 | } 134 | 135 | for (const c of dom) { 136 | if (!satisfies(eq, String(c), options)) { 137 | return false 138 | } 139 | } 140 | 141 | return true 142 | } 143 | 144 | let higher, lower 145 | let hasDomLT, hasDomGT 146 | // if the subset has a prerelease, we need a comparator in the superset 147 | // with the same tuple and a prerelease, or it's not a subset 148 | let needDomLTPre = lt && 149 | !options.includePrerelease && 150 | lt.semver.prerelease.length ? lt.semver : false 151 | let needDomGTPre = gt && 152 | !options.includePrerelease && 153 | gt.semver.prerelease.length ? gt.semver : false 154 | // exception: <1.2.3-0 is the same as <1.2.3 155 | if (needDomLTPre && needDomLTPre.prerelease.length === 1 && 156 | lt.operator === '<' && needDomLTPre.prerelease[0] === 0) { 157 | needDomLTPre = false 158 | } 159 | 160 | for (const c of dom) { 161 | hasDomGT = hasDomGT || c.operator === '>' || c.operator === '>=' 162 | hasDomLT = hasDomLT || c.operator === '<' || c.operator === '<=' 163 | if (gt) { 164 | if (needDomGTPre) { 165 | if (c.semver.prerelease && c.semver.prerelease.length && 166 | c.semver.major === needDomGTPre.major && 167 | c.semver.minor === needDomGTPre.minor && 168 | c.semver.patch === needDomGTPre.patch) { 169 | needDomGTPre = false 170 | } 171 | } 172 | if (c.operator === '>' || c.operator === '>=') { 173 | higher = higherGT(gt, c, options) 174 | if (higher === c && higher !== gt) { 175 | return false 176 | } 177 | } else if (gt.operator === '>=' && !satisfies(gt.semver, String(c), options)) { 178 | return false 179 | } 180 | } 181 | if (lt) { 182 | if (needDomLTPre) { 183 | if (c.semver.prerelease && c.semver.prerelease.length && 184 | c.semver.major === needDomLTPre.major && 185 | c.semver.minor === needDomLTPre.minor && 186 | c.semver.patch === needDomLTPre.patch) { 187 | needDomLTPre = false 188 | } 189 | } 190 | if (c.operator === '<' || c.operator === '<=') { 191 | lower = lowerLT(lt, c, options) 192 | if (lower === c && lower !== lt) { 193 | return false 194 | } 195 | } else if (lt.operator === '<=' && !satisfies(lt.semver, String(c), options)) { 196 | return false 197 | } 198 | } 199 | if (!c.operator && (lt || gt) && gtltComp !== 0) { 200 | return false 201 | } 202 | } 203 | 204 | // if there was a < or >, and nothing in the dom, then must be false 205 | // UNLESS it was limited by another range in the other direction. 206 | // Eg, >1.0.0 <1.0.1 is still a subset of <2.0.0 207 | if (gt && hasDomLT && !lt && gtltComp !== 0) { 208 | return false 209 | } 210 | 211 | if (lt && hasDomGT && !gt && gtltComp !== 0) { 212 | return false 213 | } 214 | 215 | // we needed a prerelease range in a specific tuple, but didn't get one 216 | // then this isn't a subset. eg >=1.2.3-pre is not a subset of >=1.0.0, 217 | // because it includes prereleases in the 1.2.3 tuple 218 | if (needDomGTPre || needDomLTPre) { 219 | return false 220 | } 221 | 222 | return true 223 | } 224 | 225 | // >=1.2.3 is lower than >1.2.3 226 | const higherGT = (a, b, options) => { 227 | if (!a) { 228 | return b 229 | } 230 | const comp = compare(a.semver, b.semver, options) 231 | return comp > 0 ? a 232 | : comp < 0 ? b 233 | : b.operator === '>' && a.operator === '>=' ? b 234 | : a 235 | } 236 | 237 | // <=1.2.3 is higher than <1.2.3 238 | const lowerLT = (a, b, options) => { 239 | if (!a) { 240 | return b 241 | } 242 | const comp = compare(a.semver, b.semver, options) 243 | return comp < 0 ? a 244 | : comp > 0 ? b 245 | : b.operator === '<' && a.operator === '<=' ? b 246 | : a 247 | } 248 | 249 | module.exports = subset 250 | --------------------------------------------------------------------------------