├── .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 |
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 | ['* 2.x', [['<0.0.0-0']]],
81 | ['>x 2.x || * || {
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 | ['* 2.x', '<0.0.0-0'],
83 | ['>x 2.x || * || =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] [ [...]]
146 | Prints valid versions sorted by SemVer precedence
147 |
148 | Options:
149 | -r --range
150 | Print versions that match the specified range.
151 |
152 | -i --increment []
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
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
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 |
--------------------------------------------------------------------------------