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
9 changes: 0 additions & 9 deletions src/constants.mjs
Original file line number Diff line number Diff line change
@@ -1,12 +1,3 @@
export const STYLE_SEMVER = 'semver'
export const STYLE_TIMEVER = 'timever'
export const STYLE_AUTO = 'auto'
export const TIMEVER_REGEX = /(\d{8})[.-](\d{6})Z(?:-(alpha|beta|rc)\.(\d)+)?$/
export const TIMEVER_DATE_POS = 1
export const TIMEVER_TIME_POS = 2
export const TIMEVER_PRETYPE_POS = 3
export const TIMEVER_PREVER_POS = 4

export const xRangeRE =
// v single tuple v doublpe tuple v triple tuple v quad/pre-release tuple
// v if there's more than a single digit, it can't lead with a zero
Expand Down
2 changes: 0 additions & 2 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
export * from './ext-constants'
export * from './filter-valid-version-or-range'
export * from './first-past'
export * from './is-time-version'
export * from './min-version'
export * from './next-version'
export * from './prerelease'
export * from './valid-version-or-range'
export * from './version-compare'
export * from './version-style'
export * from './x-sort'
7 changes: 0 additions & 7 deletions src/is-time-version.mjs

This file was deleted.

80 changes: 11 additions & 69 deletions src/next-version.mjs
Original file line number Diff line number Diff line change
@@ -1,32 +1,11 @@
import createError from 'http-errors'
import semver from 'semver'

import { STYLE_AUTO, STYLE_SEMVER, STYLE_TIMEVER } from './constants'
import { increments, prereleaseIncrements, releaseTypes } from './ext-constants'
import { versionStyle } from './version-style'

const makeTS = ({ date = new Date() } = {}) => {
const timestamp = date.getUTCFullYear()
+ (date.getUTCMonth() + 1 + '').padStart(2, '0')
+ (date.getUTCDate() + '').padStart(2, '0')
+ '.'
+ (date.getUTCHours() + '').padStart(2, '0')
+ (date.getUTCMinutes() + '').padStart(2, '0')
+ (date.getUTCSeconds() + '').padStart(2, '0')
+ 'Z'

return timestamp
}

/**
* Given a current version, increment, and optional style, generates the next version string. The version style is
* typically identified automatically but can be specified explicitly (if switching styles, for example). Supports
* 'semver' and 'timever' style. See [semver specification](https://semver.org/). Timever style consists of:
* - a major version integer (indicating compatibility series)
* - a UTC timestamp in the form of 'YYYYMMDD.HHMMSSZ'
* - the timestamp is generated during the execution of this method.
*
* For semver style, this method follows the [semver spec](https://semver.org) except that:
* Given a current version, increment generates the next version string. This method follows the
* [semver spec](https://semver.org) except that:
* - `increment` 'prerelease' cannot be used with non-prerelease versions (results in execption)
* - supports 'pretype' and specific pre-release sequence 'alpha' -> 'beta' -> 'rc' -> released
*
Expand All @@ -37,7 +16,7 @@ const makeTS = ({ date = new Date() } = {}) => {
* means to increment the pre-release ID from 'alpha' -> 'beta' -> 'rc' -> none. Passing in a `currVer` with any
* other prerelease ID with a 'pretype' increment will result in undefined results
*/
const nextVersion = ({ currVer, date, increment, loose = false, style = STYLE_AUTO }) => {
const nextVersion = ({ currVer, increment, loose = false }) => {
if (increment !== undefined && !increments.includes(increment)) {
throw createError.BadRequest(`Invalid increment '${increment}' specified.`)
}
Expand Down Expand Up @@ -65,21 +44,12 @@ const nextVersion = ({ currVer, date, increment, loose = false, style = STYLE_AU
}
}

if (style === STYLE_AUTO || style === undefined) {
style = versionStyle(currVer)
}

// alpha -> beta -> rc
if (increment === 'pretype') {
if (currPrerelease === 'alpha') nextVer = currVer.replace(/([0-1.Z-]+)alpha(\.\d+)?/, '$1beta.0')
else if (currPrerelease === 'beta') nextVer = currVer.replace(/([0-1.Z-]+)beta(\.\d+)?/, '$1rc.0')
else if (currPrerelease === 'rc') { // is special in the case of timever + pretype
if (style === STYLE_SEMVER) {
nextVer = currVer.replace(/([0-9.Z]+)-rc(?:\.\d+)?/, '$1')
}
else {
nextVer = currVer.replace(/(\d+)\.\d{8}\.\d{6}Z-rc\.\d+/, '$1.' + makeTS())
}
nextVer = currVer.replace(/([0-9.Z]+)-rc(?:\.\d+)?/, '$1')
}

return nextVer // we're done
Expand All @@ -94,49 +64,21 @@ const nextVersion = ({ currVer, date, increment, loose = false, style = STYLE_AU
}

if (increment === 'gold') {
if (style === STYLE_SEMVER) {
nextVer = currVer.replace(/^([\d.Z]+)-(?:alpha|beta|rc)(?:\.\d+)?/, '$1')
}
else { // style === STYLE_TIMEVER
nextVer = currVer.replace(/(\d+)\.\d{8}\.\d{6}Z-(?:alpha|beta|rc)\.\d+/, '$1.' + makeTS())
}
nextVer = currVer.replace(/^([\d.Z]+)-(?:alpha|beta|rc)(?:\.\d+)?/, '$1')
}
else if (style === STYLE_SEMVER) {
else {
nextVer = currVer.replace(/^([\d.Z-]+)(?:alpha|beta|rc)(?:\.\d+)?/, `$1${increment}.0`)
}
else { // style === STYLE_TIMEVER
nextVer = currVer.replace(/(\d+)\.\d{8}\.\d{6}Z-(?:alpha|beta|rc)\.\d+/, '$1.' + makeTS() + increment + '.0')
}

return nextVer
}

if (style === STYLE_SEMVER) {
// if we're going 'pre', but currVer is a pre-style, then we need to specify the first stage in the pre, aka, alpha
nextVer = increment.startsWith('pre') && currVer.match(/^[\d.Z-]+$/)
? semver.inc(currVer, increment, 'alpha')
: semver.inc(currVer, increment)
}
else { // it's 'timever' style
const timestamp = makeTS({ date })

const [currMajor] = currVer.split('.')
const nextMajor = increment === 'major' || increment === 'premajor' ? '' + (parseInt(currMajor) + 1) : currMajor
if (currPrerelease === null && increment.startsWith('pre')) {
nextVer = nextMajor + '.' + timestamp + '-alpha.0'
}
else if (currPrerelease !== null && increment === 'prerelease') {
const currPreReleaseString = (currVer.match(/(?:alpha|beta|rc)\.(\d+)$/) || [null, null])[1]
const currPreRelease = parseInt(currPreReleaseString)
const currBare = currVer.replace(/\d+$/, '')
nextVer = currBare + (currPreRelease + 1)
}
else {
nextVer = nextMajor + '.' + timestamp + (currPrerelease ? '-' + currPrerelease : '')
}
}
// if we're going 'pre', but currVer is a pre-style, then we need to specify the first stage in the pre, aka, alpha
nextVer = increment.startsWith('pre') && currVer.match(/^[\d.Z-]+$/)
? semver.inc(currVer, increment, 'alpha')
: semver.inc(currVer, increment)

return nextVer
}

export { makeTS, nextVersion, STYLE_AUTO, STYLE_TIMEVER, STYLE_SEMVER }
export { nextVersion }
15 changes: 1 addition & 14 deletions src/prerelease.mjs
Original file line number Diff line number Diff line change
@@ -1,20 +1,7 @@
import semver from 'semver'

import { TIMEVER_REGEX, TIMEVER_PRETYPE_POS, TIMEVER_PREVER_POS } from './constants'
import { isTimeVersion } from './is-time-version'

const prerelease = (version) => {
if (isTimeVersion(version)) {
const match = version.match(TIMEVER_REGEX)
const preType = match[TIMEVER_PRETYPE_POS]
if (preType !== undefined) {
return [preType, parseInt(match[TIMEVER_PREVER_POS])]
}
else return null
}
else {
return semver.prerelease(version)
}
return semver.prerelease(version)
}

export { prerelease }
138 changes: 1 addition & 137 deletions src/test/next-version.test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,7 @@
/* global beforeAll describe expect test */
/* global describe expect test */

import * as ver from '../next-version'

describe('makeTS', () => {
test('Jan 3, 2023 0513.25 => 20230303.051325Z', () => {
const date = new Date('2023-03-03T05:13:25.000Z')

expect(ver.makeTS({ date })).toBe('20230303.051325Z')
})
})

describe('nextVersion', () => {
describe('handles semver style', () => {
test.each([
Expand Down Expand Up @@ -68,132 +60,4 @@ describe('nextVersion', () => {
const currVer = `1.0.0-${currPrerelease}`
expect(() => ver.version({ currVer, increment : 'prerelease' }))
})

describe('handles timever style', () => {
let preTS, resultReleased, resultPreInit, resultPreOngoing, resultPretype, postTS
const startTimeVer = '1.20230103.051325Z'
const testReleased = [
[startTimeVer, undefined, '1'],
[startTimeVer, 'patch', '1'],
[startTimeVer, 'minor', '1'],
[startTimeVer, 'major', '2']
]
const testPreInit = [
[startTimeVer, 'prepatch', '1', '-alpha.0'],
[startTimeVer, 'preminor', '1', '-alpha.0'],
[startTimeVer, 'premajor', '2', '-alpha.0']
]
const testPreOngoing = [
[startTimeVer + '-alpha.1', 'prerelease', '-alpha.2'],
[startTimeVer + '-beta.1', 'prerelease', '-beta.2'],
[startTimeVer + '-rc.1', 'prerelease', '-rc.2']
]
const testPretype = [
[startTimeVer + '-alpha.1', 'pretype', startTimeVer + '-beta.0'],
[startTimeVer + '-beta.1', 'pretype', startTimeVer + '-rc.0']
]
beforeAll(async() => {
const start = new Date()
preTS = ver.makeTS(start)
await new Promise(resolve => setTimeout(resolve, 1000))
resultReleased = testReleased
.map(([currVer, increment, nextMajor]) => [ver.nextVersion({ currVer, increment }), nextMajor])
resultPreInit = testPreInit
.map(([currVer, increment, result]) => [ver.nextVersion({ currVer, increment })])
resultPreOngoing = testPreOngoing
.map(([currVer, increment, nextPre]) => [ver.nextVersion({ currVer, increment })])
resultPretype = testPretype
.map(([currVer, increment]) => [ver.nextVersion({ currVer, increment })])
await new Promise(resolve => setTimeout(resolve, 1000))
const end = new Date()
postTS = ver.makeTS(end)
})

// TODO: it would be nice to do these as some kind of loop, but since we do all our calculations at once (in the
// beforeAll) in order to minimize the sleep times, '.each' won't work because the values are calculated before the
// before.
describe('released increment', () => {
test(`${testReleased[0][0]} + ${testReleased[0][1]} -> ${testReleased[0][2]}.<newTS>`, () => {
expect(resultReleased[0][0]).toMatch(/1\.\d{8}.\d{6}Z/)
const raw = ['1.' + preTS, resultReleased[0][0], `${testReleased[0][2]}.${postTS}`]
const sorted = [...raw].sort()
expect(raw).toEqual(sorted)
})

test(`${testReleased[1][0]} + ${testReleased[1][1]} -> ${testReleased[1][2]}.<newTS>`, () => {
expect(resultReleased[1][0]).toMatch(/1\.\d{8}.\d{6}Z/)
const raw = ['1.' + preTS, resultReleased[1][0], `${testReleased[1][2]}.${postTS}`]
const sorted = [...raw].sort()
expect(raw).toEqual(sorted)
})

test(`${testReleased[2][0]} + ${testReleased[2][1]} -> ${testReleased[2][2]}.<newTS>`, () => {
expect(resultReleased[2][0]).toMatch(/1\.\d{8}.\d{6}Z/)
const raw = ['1.' + preTS, resultReleased[2][0], `${testReleased[2][2]}.${postTS}`]
const sorted = [...raw].sort()
expect(raw).toEqual(sorted)
})

test(`${testReleased[3][0]} + ${testReleased[3][1]} -> ${testReleased[3][2]}.<newTS>`, () => {
expect(resultReleased[3][0]).toMatch(/2\.\d{8}.\d{6}Z/)
const raw = ['1.' + preTS, resultReleased[3][0], `${testReleased[3][2]}.${postTS}`]
const sorted = [...raw].sort()
expect(raw).toEqual(sorted)
})
})

describe('initial pre-increment', () => {
test(`${testPreInit[0][0]} + ${testPreInit[0][1]} -> 1.<newTS>${testPreInit[0][3]}`, () => {
expect(resultPreInit[0][0]).toMatch(/1\.\d{8}.\d{6}Z-alpha\.0/)
const raw = ['1.' + preTS, resultPreInit[0][0], `${testPreInit[0][2]}.${postTS}`]
const sorted = [...raw].sort()
expect(raw).toEqual(sorted)
})

test(`${testPreInit[1][0]} + ${testPreInit[1][1]} -> ${testPreInit[1][2]}.<newTS>${testPreInit[1][3]}`, () => {
expect(resultPreInit[1][0]).toMatch(/1\.\d{8}.\d{6}Z-alpha\.0/)
const raw = ['1.' + preTS, resultPreInit[1][0], `${testPreInit[1][2]}.${postTS}`]
const sorted = [...raw].sort()
expect(raw).toEqual(sorted)
})

test(`${testPreInit[2][0]} + ${testPreInit[2][1]} -> ${testPreInit[2][2]}.<newTS>${testPreInit[2][3]}`, () => {
expect(resultPreInit[2][0]).toMatch(/2\.\d{8}.\d{6}Z-alpha\.0/)
const raw = ['1.' + preTS, resultPreInit[2][0], `${testPreInit[2][2]}.${postTS}`]
const sorted = [...raw].sort()
expect(raw).toEqual(sorted)
})
})

describe('ongoing pre-increment', () => {
test(`${testPreOngoing[0][0]} + ${testPreOngoing[0][1]} -> ${testPreOngoing[0][0].slice(0, -1) + '2'}`, () => {
expect(resultPreOngoing[0][0]).toBe(testPreOngoing[0][0].slice(0, -1) + '2')
})

test(`${testPreOngoing[1][0]} + ${testPreOngoing[1][1]} -> ${testPreOngoing[1][0].slice(0, -1) + '2'}`, () => {
expect(resultPreOngoing[1][0]).toBe(testPreOngoing[1][0].slice(0, -1) + '2')
})

test(`${testPreOngoing[2][0]} + ${testPreOngoing[2][1]} -> ${testPreOngoing[2][0].slice(0, -1) + '2'}`, () => {
expect(resultPreOngoing[2][0]).toBe(testPreOngoing[2][0].slice(0, -1) + '2')
})
})

describe('pretype', () => {
test(`${testPretype[0][0]} + ${testPretype[0][1]} -> ${testPretype[0][2]}`, () =>
expect(resultPretype[0][0]).toBe(testPretype[0][2]))

test(`${testPretype[1][0]} + ${testPretype[1][1]} -> ${testPretype[1][2]}`, () =>
expect(resultPretype[1][0]).toBe(testPretype[1][2]))

test(`${startTimeVer}-rc.1 + pretype -> 1.<newTS>`, () => {
const currVer = startTimeVer + '-rc.1'
const newVer = ver.nextVersion({ currVer, increment : 'pretype' })
expect(newVer).toMatch(/^\d+.\d{8}.\d{6}Z$/)
expect(newVer).not.toBe(startTimeVer)
const sorted = [startTimeVer, newVer].sort()
expect(sorted[0]).toBe(startTimeVer)
})
})
})
})
5 changes: 1 addition & 4 deletions src/test/prerelease.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,6 @@ describe('prerelease', () => {
test.each([
['1.0.0', null],
['1.0.0-beta.0', ['beta', 0]],
['1.0.0-alpha.2', ['alpha', 2]],
['20230303.051512Z', null],
['20230304.051512Z-beta.0', ['beta', 0]],
['20230304.051512Z-alpha.2', ['alpha', 2]]
['1.0.0-alpha.2', ['alpha', 2]]
])('version %p yields %s', (version, expected) => expect(prerelease(version)).toEqual(expected))
})
22 changes: 6 additions & 16 deletions src/test/version-compare.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,21 +9,16 @@ describe('maxVersion', () => {
[['1.0.0-alpha.0', '1.0.0'], '1.0.0'],
[['1.0.0-alpha.0', '1.0.0-beta.0', '1.0.0-rc.0', '1.0.0'], '1.0.0'],
[['1.0.0-alpha.2', '1.0.0-alpha.13'], '1.0.0-alpha.13'],
[['2.0.0-alpha.0', '1.0.0'], '2.0.0-alpha.0'],
[['20230303.051512Z', '20230304.051512Z-alpha.0'], '20230304.051512Z-alpha.0'],
[['20230304.051512Z-beta.0', '20230304.051512Z-alpha.0'], '20230304.051512Z-beta.0'],
[['20230304.051512Z-beta.0', '20230304.051512Z-rc.0'], '20230304.051512Z-rc.0']
[['2.0.0-alpha.0', '1.0.0'], '2.0.0-alpha.0']
])('versions %p -> %s', (versions, expected) => expect(maxVersion({ versions })).toBe(expected))

test('[] => null', () => expect(maxVersion({ versions : [] })).toBe(null))

test('mixed version types raises exception',
() => expect(() => maxVersion({ versions : ['1.0.0', '20230501-101010Z'] })).toThrow())
() => expect(() => maxVersion({ versions : ['1.0.0', 'not-a-valid-vesion'] })).toThrow())

test.each([
[['1.0.0', '20230501-101010Z', 'abc'], '1.0.0'],
[['abc', '1.0.0', '20230501-101010Z'], '1.0.0'],
[['abc', '20230501-101010Z', '1.0.0'], '20230501-101010Z']
[['1.0.0', 'not-a-valid-vesion', 'abc'], '1.0.0']
])("'ignoreNonVersions' filters non-version strings from the version list",
(versions, expected) => expect(maxVersion({ ignoreNonVersions : true, versions })).toBe(expected))
})
Expand All @@ -35,21 +30,16 @@ describe('minVersion', () => {
[['1.0.0-alpha.0', '1.0.0'], '1.0.0-alpha.0'],
[['1.0.0-alpha.0', '1.0.0-beta.0', '1.0.0-rc.0', '1.0.0'], '1.0.0-alpha.0'],
[['2.0.0-alpha.0', '1.0.0'], '1.0.0'],
[['1.0.0-alpha.2', '1.0.0-alpha.13'], '1.0.0-alpha.2'],
[['20230303.051512Z', '20230304.051512Z-alpha.0'], '20230303.051512Z'],
[['20230304.051512Z-beta.0', '20230304.051512Z-alpha.0'], '20230304.051512Z-alpha.0'],
[['20230304.051512Z-beta.0', '20230304.051512Z-rc.0'], '20230304.051512Z-beta.0']
[['1.0.0-alpha.2', '1.0.0-alpha.13'], '1.0.0-alpha.2']
])('versions %p -> %s', (versions, expected) => expect(minVersion({ versions })).toBe(expected))

test('[] => null', () => expect(minVersion({ versions : [] })).toBe(null))

test('mixed version types raises exception',
() => expect(() => minVersion({ versions : ['1.0.0', '20230501-101010Z'] })).toThrow())
() => expect(() => minVersion({ versions : ['1.0.0', 'not-a-valid-vesion'] })).toThrow())

test.each([
[['1.0.0', '20230501-101010Z', 'abc'], '1.0.0'],
[['abc', '1.0.0', '20230501-101010Z'], '1.0.0'],
[['abc', '20230501-101010Z', '1.0.0'], '20230501-101010Z']
[['1.0.0', 'not-a-valid-vesion', 'abc'], '1.0.0']
])("'ignoreNonVersions' filters non-version strings from the version list",
(versions, expected) => expect(minVersion({ ignoreNonVersions : true, versions })).toBe(expected))
})
Loading