Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const semverCompareLoose = require('semver/functions/compare-loose')
const semverCompareBuild = require('semver/functions/compare-build')
const semverSort = require('semver/functions/sort')
const semverRsort = require('semver/functions/rsort')
const semverTruncate = require('semver/functions/truncate')

// low-level comparators between versions
const semverGt = require('semver/functions/gt')
Expand Down Expand Up @@ -456,6 +457,12 @@ strings that they parse.
or comparators intersect.
* `parse(v)`: Attempt to parse a string as a semantic version, returning either
a `SemVer` object or `null`.
* `truncate(v, releaseType)`: Return the version with components _lower_
than `releaseType` dropped off, e.g.:
* `major` removes build & prerelease info and sets minor & patch to 0.
* `minor` removes build & prerelease info, and sets patch to 0
* `patch` removes build & prerelease info
* All prerelease types remove build info only

### Comparison

Expand Down Expand Up @@ -657,6 +664,7 @@ The following modules are available:
* `require('semver/functions/rsort')`
* `require('semver/functions/satisfies')`
* `require('semver/functions/sort')`
* `require('semver/functions/truncate')`
* `require('semver/functions/valid')`
* `require('semver/ranges/gtr')`
* `require('semver/ranges/intersects')`
Expand Down
48 changes: 48 additions & 0 deletions functions/truncate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
'use strict'

const parse = require('./parse')
const constants = require('../internal/constants')
const SemVer = require('../classes/semver')

const truncate = (version, truncation, options) => {
if (!constants.RELEASE_TYPES.includes(truncation)) {
return null
}

const clonedVersion = cloneInputVersion(version, options)
return clonedVersion && doTruncation(clonedVersion, truncation)
}

const cloneInputVersion = (version, options) => {
const versionStringToParse = (
version instanceof SemVer ? version.version : version
)

return parse(versionStringToParse, options)
}

const doTruncation = (version, truncation) => {
if (isPrerelease(truncation)) {
return version.version
}

version.prerelease = []

switch (truncation) {
case 'major':
version.minor = 0
version.patch = 0
break
case 'minor':
version.patch = 0
break
}

return version.format()
}

const isPrerelease = (type) => {
return type.startsWith('pre')
}

module.exports = truncate
2 changes: 2 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const gte = require('./functions/gte')
const lte = require('./functions/lte')
const cmp = require('./functions/cmp')
const coerce = require('./functions/coerce')
const truncate = require('./functions/truncate')
const Comparator = require('./classes/comparator')
const Range = require('./classes/range')
const satisfies = require('./functions/satisfies')
Expand Down Expand Up @@ -66,6 +67,7 @@ module.exports = {
lte,
cmp,
coerce,
truncate,
Comparator,
Range,
satisfies,
Expand Down
39 changes: 39 additions & 0 deletions test/fixtures/truncations.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
'use strict'

// [version, releaseType, result]
// truncate(version, type) -> result
module.exports = [
['1.2.3-foo', 'patch', '1.2.3'],
['1.2.3', 'patch', '1.2.3'],
['1.2.3', 'minor', '1.2.0'],
['1.2.3', 'major', '1.0.0'],

// invalid inputs
['1.2.3', 'fake', null],
['fake', 'major', null],

// additional pre-release, build, and pre+build inputs
['4.5.6-rc2', 'prerelease', '4.5.6-rc2'],
['4.5.6-rc2', 'prepatch', '4.5.6-rc2'],
['4.5.6-rc2', 'preminor', '4.5.6-rc2'],
['4.5.6-rc2', 'premajor', '4.5.6-rc2'],
['4.5.6-rc2', 'patch', '4.5.6'],
['4.5.6-rc2', 'minor', '4.5.0'],
['4.5.6-rc2', 'major', '4.0.0'],

['4.5.6+dadb0d', 'prerelease', '4.5.6'],
['4.5.6+dadb0d', 'prepatch', '4.5.6'],
['4.5.6+dadb0d', 'preminor', '4.5.6'],
['4.5.6+dadb0d', 'premajor', '4.5.6'],
['4.5.6+dadb0d', 'patch', '4.5.6'],
['4.5.6+dadb0d', 'minor', '4.5.0'],
['4.5.6+dadb0d', 'major', '4.0.0'],

['4.5.6-rc2+dadb0d', 'prerelease', '4.5.6-rc2'],
['4.5.6-rc2+dadb0d', 'prepatch', '4.5.6-rc2'],
['4.5.6-rc2+dadb0d', 'preminor', '4.5.6-rc2'],
['4.5.6-rc2+dadb0d', 'premajor', '4.5.6-rc2'],
['4.5.6-rc2+dadb0d', 'patch', '4.5.6'],
['4.5.6-rc2+dadb0d', 'minor', '4.5.0'],
['4.5.6-rc2+dadb0d', 'major', '4.0.0'],
]
45 changes: 45 additions & 0 deletions test/functions/truncate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict'

const { test } = require('tap')
const truncate = require('../../functions/truncate')
const parse = require('../../functions/parse')
const truncations = require('../fixtures/truncations.js')
const validVersions = require('../fixtures/valid-versions.js')

// Freezing SemVer object inputs to truncate ensures that the truncate function
// does not mutate them
const parseAndFreezeSemVerObject = (version) => {
const parsed = parse(version)
Object.freeze(parsed)
return parsed
}

test('truncate fixture versions test', (t) => {
truncations.forEach(([pre, truncation, expected]) => {
const actual = truncate(pre, truncation)
const cmd = `truncate(${pre}, ${truncation})`
t.equal(actual, expected, `${cmd} === ${expected}`)

const parsed = parseAndFreezeSemVerObject(pre)
const semverTruncated = truncate(parsed, truncation)
t.equal(semverTruncated, expected, `${cmd} works on Semver object inputs`)
})

t.end()
})

test('truncate pre* only removes build info', (t) => {
['prerelease', 'prepatch', 'preminor', 'premajor'].forEach(what => {
validVersions.forEach((v) => {
const versionToTruncate = v[0]
const parsed = parseAndFreezeSemVerObject(versionToTruncate)
const expected = parsed.version
const actual = truncate(versionToTruncate, what)

const cmd = `truncate(${versionToTruncate}, ${what})`
t.equal(actual, expected, `${cmd} === ${expected}`)
t.same(parse(actual).build, [], `${cmd} build info should be removed`)
})
})
t.end()
})
Loading