Move "good" badge helpers from lib/ to services/ (#3101)
This moves a few helpers from `lib/` to `services/`: build-status.js build-status.spec.js color-formatters.js color-formatters.spec.js contributor-count.js licenses.js licenses.spec.js php-version.js php-version.spec.js text-formatters.js text-formatters.spec.js version.js version.spec.js And one from `lib/` to `core/`: unhandled-rejection.spec.js The diff is long, but the changes are straightforward. Ref #2832
This commit is contained in:
@@ -1,66 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const Joi = require('joi')
|
||||
|
||||
const greenStatuses = [
|
||||
'fixed',
|
||||
'passed',
|
||||
'passing',
|
||||
'succeeded',
|
||||
'success',
|
||||
'successful',
|
||||
]
|
||||
|
||||
const orangeStatuses = ['partially succeeded', 'unstable', 'timeout']
|
||||
|
||||
const redStatuses = ['error', 'failed', 'failing']
|
||||
|
||||
const otherStatuses = [
|
||||
'building',
|
||||
'canceled',
|
||||
'cancelled',
|
||||
'expired',
|
||||
'no tests',
|
||||
'not built',
|
||||
'not run',
|
||||
'pending',
|
||||
'processing',
|
||||
'queued',
|
||||
'running',
|
||||
'scheduled',
|
||||
'skipped',
|
||||
'starting',
|
||||
'stopped',
|
||||
'waiting',
|
||||
]
|
||||
|
||||
const isBuildStatus = Joi.equal(
|
||||
greenStatuses
|
||||
.concat(orangeStatuses)
|
||||
.concat(redStatuses)
|
||||
.concat(otherStatuses)
|
||||
)
|
||||
|
||||
function renderBuildStatusBadge({ label, status }) {
|
||||
let message
|
||||
let color
|
||||
if (greenStatuses.includes(status)) {
|
||||
message = 'passing'
|
||||
color = 'brightgreen'
|
||||
} else if (orangeStatuses.includes(status)) {
|
||||
message = status === 'partially succeeded' ? 'passing' : status
|
||||
color = 'orange'
|
||||
} else if (redStatuses.includes(status)) {
|
||||
message = status === 'failed' ? 'failing' : status
|
||||
color = 'red'
|
||||
} else {
|
||||
message = status
|
||||
}
|
||||
return {
|
||||
label,
|
||||
message,
|
||||
color,
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { isBuildStatus, renderBuildStatusBadge }
|
||||
@@ -1,85 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { expect } = require('chai')
|
||||
const { test, given, forCases } = require('sazerac')
|
||||
const { renderBuildStatusBadge } = require('./build-status')
|
||||
|
||||
test(renderBuildStatusBadge, () => {
|
||||
given({ label: 'build', status: 'passed' }).expect({
|
||||
label: 'build',
|
||||
message: 'passing',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
given({ label: 'build', status: 'success' }).expect({
|
||||
label: 'build',
|
||||
message: 'passing',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
given({ label: 'build', status: 'partially succeeded' }).expect({
|
||||
label: 'build',
|
||||
message: 'passing',
|
||||
color: 'orange',
|
||||
})
|
||||
given({ label: 'build', status: 'failed' }).expect({
|
||||
label: 'build',
|
||||
message: 'failing',
|
||||
color: 'red',
|
||||
})
|
||||
given({ label: 'build', status: 'error' }).expect({
|
||||
label: 'build',
|
||||
message: 'error',
|
||||
color: 'red',
|
||||
})
|
||||
})
|
||||
|
||||
test(renderBuildStatusBadge, () => {
|
||||
forCases([
|
||||
given({ status: 'fixed' }),
|
||||
given({ status: 'passed' }),
|
||||
given({ status: 'passing' }),
|
||||
given({ status: 'succeeded' }),
|
||||
given({ status: 'success' }),
|
||||
given({ status: 'successful' }),
|
||||
]).assert('should be brightgreen', b =>
|
||||
expect(b).to.include({ color: 'brightgreen' })
|
||||
)
|
||||
})
|
||||
|
||||
test(renderBuildStatusBadge, () => {
|
||||
forCases([
|
||||
given({ status: 'partially succeeded' }),
|
||||
given({ status: 'timeout' }),
|
||||
given({ status: 'unstable' }),
|
||||
]).assert('should be orange', b => expect(b).to.include({ color: 'orange' }))
|
||||
})
|
||||
|
||||
test(renderBuildStatusBadge, () => {
|
||||
forCases([
|
||||
given({ status: 'error' }),
|
||||
given({ status: 'failed' }),
|
||||
given({ status: 'failing' }),
|
||||
]).assert('should be red', b => expect(b).to.include({ color: 'red' }))
|
||||
})
|
||||
|
||||
test(renderBuildStatusBadge, () => {
|
||||
forCases([
|
||||
given({ status: 'building' }),
|
||||
given({ status: 'canceled' }),
|
||||
given({ status: 'cancelled' }),
|
||||
given({ status: 'expired' }),
|
||||
given({ status: 'no tests' }),
|
||||
given({ status: 'not built' }),
|
||||
given({ status: 'not run' }),
|
||||
given({ status: 'pending' }),
|
||||
given({ status: 'processing' }),
|
||||
given({ status: 'queued' }),
|
||||
given({ status: 'running' }),
|
||||
given({ status: 'scheduled' }),
|
||||
given({ status: 'skipped' }),
|
||||
given({ status: 'starting' }),
|
||||
given({ status: 'stopped' }),
|
||||
given({ status: 'waiting' }),
|
||||
]).assert('should have undefined color', b =>
|
||||
expect(b).to.include({ color: undefined })
|
||||
)
|
||||
})
|
||||
@@ -1,117 +0,0 @@
|
||||
/**
|
||||
* Commonly-used functions for determining the colour to use for a badge,
|
||||
* including colours based off download count, version number, etc.
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const moment = require('moment')
|
||||
|
||||
function version(version) {
|
||||
if (typeof version !== 'string' && typeof version !== 'number') {
|
||||
throw new Error(`Can't generate a version color for ${version}`)
|
||||
}
|
||||
version = `${version}`
|
||||
let first = version[0]
|
||||
if (first === 'v') {
|
||||
first = version[1]
|
||||
}
|
||||
if (first === '0' || /alpha|beta|snapshot|dev|pre/i.test(version)) {
|
||||
return 'orange'
|
||||
} else {
|
||||
return 'blue'
|
||||
}
|
||||
}
|
||||
|
||||
function downloadCount(downloads) {
|
||||
return floorCount(downloads, 10, 100, 1000)
|
||||
}
|
||||
|
||||
function coveragePercentage(percentage) {
|
||||
return floorCount(percentage, 80, 90, 100)
|
||||
}
|
||||
|
||||
function floorCount(value, yellow, yellowgreen, green) {
|
||||
if (value <= 0) {
|
||||
return 'red'
|
||||
} else if (value < yellow) {
|
||||
return 'yellow'
|
||||
} else if (value < yellowgreen) {
|
||||
return 'yellowgreen'
|
||||
} else if (value < green) {
|
||||
return 'green'
|
||||
} else {
|
||||
return 'brightgreen'
|
||||
}
|
||||
}
|
||||
|
||||
function letterScore(score) {
|
||||
if (score === 'A') {
|
||||
return 'brightgreen'
|
||||
} else if (score === 'B') {
|
||||
return 'green'
|
||||
} else if (score === 'C') {
|
||||
return 'yellowgreen'
|
||||
} else if (score === 'D') {
|
||||
return 'yellow'
|
||||
} else if (score === 'E') {
|
||||
return 'orange'
|
||||
} else {
|
||||
return 'red'
|
||||
}
|
||||
}
|
||||
|
||||
function colorScale(steps, colors, reversed) {
|
||||
if (steps === undefined) {
|
||||
throw Error('When invoking colorScale, steps should be provided.')
|
||||
}
|
||||
|
||||
const defaultColors = {
|
||||
1: ['red', 'brightgreen'],
|
||||
2: ['red', 'yellow', 'brightgreen'],
|
||||
3: ['red', 'yellow', 'green', 'brightgreen'],
|
||||
4: ['red', 'yellow', 'yellowgreen', 'green', 'brightgreen'],
|
||||
5: ['red', 'orange', 'yellow', 'yellowgreen', 'green', 'brightgreen'],
|
||||
}
|
||||
|
||||
if (typeof colors === 'undefined') {
|
||||
if (steps.length in defaultColors) {
|
||||
colors = defaultColors[steps.length]
|
||||
} else {
|
||||
throw Error(`No default colors for ${steps.length} steps.`)
|
||||
}
|
||||
}
|
||||
|
||||
if (steps.length !== colors.length - 1) {
|
||||
throw Error(
|
||||
'When colors are provided, there should be n + 1 colors for n steps.'
|
||||
)
|
||||
}
|
||||
|
||||
if (reversed) {
|
||||
colors = Array.from(colors).reverse()
|
||||
}
|
||||
|
||||
return value => {
|
||||
const stepIndex = steps.findIndex(step => value < step)
|
||||
|
||||
// For the final step, stepIndex is -1, so in all cases this expression
|
||||
// works swimmingly.
|
||||
return colors.slice(stepIndex)[0]
|
||||
}
|
||||
}
|
||||
|
||||
function age(date) {
|
||||
const colorByAge = colorScale([7, 30, 180, 365, 730], undefined, true)
|
||||
const daysElapsed = moment().diff(moment(date), 'days')
|
||||
return colorByAge(daysElapsed)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
version,
|
||||
downloadCount,
|
||||
coveragePercentage,
|
||||
floorCount,
|
||||
letterScore,
|
||||
colorScale,
|
||||
age,
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given, forCases } = require('sazerac')
|
||||
const { expect } = require('chai')
|
||||
const {
|
||||
coveragePercentage,
|
||||
colorScale,
|
||||
letterScore,
|
||||
age,
|
||||
version,
|
||||
} = require('./color-formatters')
|
||||
|
||||
describe('Color formatters', function() {
|
||||
const byPercentage = colorScale([Number.EPSILON, 80, 90, 100])
|
||||
|
||||
test(byPercentage, () => {
|
||||
given(-1).expect('red')
|
||||
given(0).expect('red')
|
||||
given(0.5).expect('yellow')
|
||||
given(1).expect('yellow')
|
||||
given(50).expect('yellow')
|
||||
given(80).expect('yellowgreen')
|
||||
given(85).expect('yellowgreen')
|
||||
given(90).expect('green')
|
||||
given(100).expect('brightgreen')
|
||||
given(101).expect('brightgreen')
|
||||
|
||||
forCases(
|
||||
[-1, 0, 0.5, 1, 50, 80, 85, 90, 100, 101].map(v =>
|
||||
given(v).expect(coveragePercentage(v))
|
||||
)
|
||||
).should("return '%s', for parity with coveragePercentage()")
|
||||
})
|
||||
|
||||
context('when reversed', function() {
|
||||
test(colorScale([7, 30, 180, 365, 730], undefined, true), () => {
|
||||
given(3).expect('brightgreen')
|
||||
given(7).expect('green')
|
||||
given(10).expect('green')
|
||||
given(60).expect('yellowgreen')
|
||||
given(250).expect('yellow')
|
||||
given(400).expect('orange')
|
||||
given(800).expect('red')
|
||||
})
|
||||
})
|
||||
|
||||
test(letterScore, () => {
|
||||
given('A').expect('brightgreen')
|
||||
given('B').expect('green')
|
||||
given('C').expect('yellowgreen')
|
||||
given('D').expect('yellow')
|
||||
given('E').expect('orange')
|
||||
given('F').expect('red')
|
||||
given('Z').expect('red')
|
||||
})
|
||||
|
||||
const monthsAgo = months => {
|
||||
const result = new Date()
|
||||
// This looks wack but it works.
|
||||
result.setMonth(result.getMonth() - months)
|
||||
return result
|
||||
}
|
||||
test(age, () => {
|
||||
given(Date.now())
|
||||
.describe('when given the current timestamp')
|
||||
.expect('brightgreen')
|
||||
given(new Date())
|
||||
.describe('when given the current Date')
|
||||
.expect('brightgreen')
|
||||
given(new Date(2001, 1, 1))
|
||||
.describe('when given a Date many years ago')
|
||||
.expect('red')
|
||||
given(monthsAgo(2))
|
||||
.describe('when given a Date two months ago')
|
||||
.expect('yellowgreen')
|
||||
given(monthsAgo(15))
|
||||
.describe('when given a Date 15 months ago')
|
||||
.expect('orange')
|
||||
})
|
||||
|
||||
test(version, () => {
|
||||
forCases([given('1.0'), given(9), given(1.0)]).expect('blue')
|
||||
|
||||
forCases([
|
||||
given(0.1),
|
||||
given('0.9'),
|
||||
given('1.0-Beta'),
|
||||
given('1.1-alpha'),
|
||||
given('6.0-SNAPSHOT'),
|
||||
given('1.0.1-dev'),
|
||||
given('2.1.6-prerelease'),
|
||||
]).expect('orange')
|
||||
|
||||
expect(() => version(null)).to.throw(
|
||||
Error,
|
||||
"Can't generate a version color for null"
|
||||
)
|
||||
expect(() => version(undefined)).to.throw(
|
||||
Error,
|
||||
"Can't generate a version color for undefined"
|
||||
)
|
||||
expect(() => version(true)).to.throw(
|
||||
Error,
|
||||
"Can't generate a version color for true"
|
||||
)
|
||||
expect(() => version({})).to.throw(
|
||||
Error,
|
||||
"Can't generate a version color for [object Object]"
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,26 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { metric } = require('./text-formatters')
|
||||
|
||||
function contributorColor(contributorCount) {
|
||||
if (contributorCount > 2) {
|
||||
return 'brightgreen'
|
||||
} else if (contributorCount === 2) {
|
||||
return 'yellow'
|
||||
} else {
|
||||
return 'red'
|
||||
}
|
||||
}
|
||||
|
||||
function renderContributorBadge({ label, contributorCount }) {
|
||||
return {
|
||||
label,
|
||||
message: metric(contributorCount),
|
||||
color: contributorColor(contributorCount),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
contributorColor,
|
||||
renderContributorBadge,
|
||||
}
|
||||
@@ -1,97 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { toArray } = require('./badge-data')
|
||||
|
||||
const licenseTypes = {
|
||||
// permissive licenses - not public domain and not copyleft
|
||||
permissive: {
|
||||
spdxLicenseIds: [
|
||||
'AFL-3.0',
|
||||
'Apache-2.0',
|
||||
'Artistic-2.0',
|
||||
'BSD-2-Clause',
|
||||
'BSD-3-Clause',
|
||||
'BSD-3-Clause-Clear',
|
||||
'BSL-1.0',
|
||||
'CC-BY-4.0',
|
||||
'ECL-2.0',
|
||||
'ISC',
|
||||
'MIT',
|
||||
'MS-PL',
|
||||
'NCSA',
|
||||
'PostgreSQL',
|
||||
'Zlib',
|
||||
],
|
||||
color: 'green',
|
||||
priority: '2',
|
||||
},
|
||||
// copyleft licenses require 'Disclose source' (https://choosealicense.com/appendix/#disclose-source)
|
||||
// or 'Same license' (https://choosealicense.com/appendix/#same-license)
|
||||
copyleft: {
|
||||
spdxLicenseIds: [
|
||||
'AGPL-3.0',
|
||||
'CC-BY-SA-4.0',
|
||||
'EPL-1.0',
|
||||
'EUPL-1.1',
|
||||
'GPL-2.0',
|
||||
'GPL-3.0',
|
||||
'LGPL-2.1',
|
||||
'LGPL-3.0',
|
||||
'LPPL-1.3c',
|
||||
'MPL-2.0',
|
||||
'MS-RL',
|
||||
'OFL-1.1',
|
||||
'OSL-3.0',
|
||||
],
|
||||
color: 'orange',
|
||||
priority: '1',
|
||||
},
|
||||
// public domain licenses do not require 'License and copyright notice' (https://choosealicense.com/appendix/#include-copyright)
|
||||
'public-domain': {
|
||||
spdxLicenseIds: ['CC0-1.0', 'Unlicense', 'WTFPL'],
|
||||
color: '7cd958',
|
||||
priority: '3',
|
||||
},
|
||||
}
|
||||
|
||||
const licenseToColorMap = {}
|
||||
Object.keys(licenseTypes).forEach(licenseType => {
|
||||
const { spdxLicenseIds, color, priority } = licenseTypes[licenseType]
|
||||
spdxLicenseIds.forEach(license => {
|
||||
licenseToColorMap[license] = { color, priority }
|
||||
})
|
||||
})
|
||||
const defaultLicenseColor = 'lightgrey'
|
||||
const licenseToColor = spdxId => {
|
||||
if (!Array.isArray(spdxId)) {
|
||||
return (
|
||||
(licenseToColorMap[spdxId] && licenseToColorMap[spdxId].color) ||
|
||||
defaultLicenseColor
|
||||
)
|
||||
}
|
||||
const licenseType = spdxId
|
||||
.filter(i => licenseToColorMap[i])
|
||||
.map(i => licenseToColorMap[i])
|
||||
.reduce((a, b) => (a.priority > b.priority ? a : b), {
|
||||
color: defaultLicenseColor,
|
||||
priority: 0,
|
||||
})
|
||||
return licenseType.color
|
||||
}
|
||||
|
||||
function renderLicenseBadge({ license, licenses }) {
|
||||
if (licenses === undefined) {
|
||||
licenses = toArray(license)
|
||||
}
|
||||
|
||||
if (licenses.length === 0) {
|
||||
return { message: 'missing', color: 'red' }
|
||||
}
|
||||
|
||||
return {
|
||||
message: licenses.join(', '),
|
||||
color: licenseToColor(licenses),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { licenseToColor, renderLicenseBadge }
|
||||
@@ -1,46 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given, forCases } = require('sazerac')
|
||||
const { licenseToColor, renderLicenseBadge } = require('./licenses')
|
||||
|
||||
describe('license helpers', function() {
|
||||
test(licenseToColor, () => {
|
||||
given('MIT').expect('green')
|
||||
given('MPL-2.0').expect('orange')
|
||||
given('Unlicense').expect('7cd958')
|
||||
given('unknown-license').expect('lightgrey')
|
||||
given(null).expect('lightgrey')
|
||||
|
||||
given(['CC0-1.0', 'MPL-2.0']).expect('7cd958')
|
||||
given(['MPL-2.0', 'CC0-1.0']).expect('7cd958')
|
||||
given(['MIT', 'MPL-2.0']).expect('green')
|
||||
given(['MPL-2.0', 'MIT']).expect('green')
|
||||
given(['OFL-1.1', 'MPL-2.0']).expect('orange')
|
||||
given(['MPL-2.0', 'OFL-1.1']).expect('orange')
|
||||
given(['CC0-1.0', 'MIT', 'MPL-2.0']).expect('7cd958')
|
||||
given(['UNKNOWN-1.0', 'MIT']).expect('green')
|
||||
given(['UNKNOWN-1.0', 'UNKNOWN-2.0']).expect('lightgrey')
|
||||
})
|
||||
|
||||
test(renderLicenseBadge, () => {
|
||||
forCases([
|
||||
given({ license: undefined }),
|
||||
given({ licenses: [] }),
|
||||
given({}),
|
||||
]).expect({
|
||||
message: 'missing',
|
||||
color: 'red',
|
||||
})
|
||||
forCases([
|
||||
given({ license: 'WTFPL' }),
|
||||
given({ licenses: ['WTFPL'] }),
|
||||
]).expect({
|
||||
message: 'WTFPL',
|
||||
color: '7cd958',
|
||||
})
|
||||
given({ licenses: ['MPL-2.0', 'MIT'] }).expect({
|
||||
message: 'MPL-2.0, MIT',
|
||||
color: 'green',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,247 +0,0 @@
|
||||
/**
|
||||
* Utilities relating to PHP version numbers. This compares version numbers
|
||||
* using the algorithm followed by Composer (see
|
||||
* https://getcomposer.org/doc/04-schema.md#version).
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const { promisify } = require('util')
|
||||
const request = require('request')
|
||||
const { listCompare } = require('./version')
|
||||
const { omitv } = require('./text-formatters')
|
||||
const { regularUpdate } = require('./regular-update')
|
||||
|
||||
// Return a negative value if v1 < v2,
|
||||
// zero if v1 = v2, a positive value otherwise.
|
||||
function asciiVersionCompare(v1, v2) {
|
||||
if (v1 < v2) {
|
||||
return -1
|
||||
} else if (v1 > v2) {
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// Take a version without the starting v.
|
||||
// eg, '1.0.x-beta'
|
||||
// Return { numbers: [1,0,something big], modifier: 2, modifierCount: 1 }
|
||||
function numberedVersionData(version) {
|
||||
// A version has a numbered part and a modifier part
|
||||
// (eg, 1.0.0-patch, 2.0.x-dev).
|
||||
const parts = version.split('-')
|
||||
const numbered = parts[0]
|
||||
|
||||
// Aliases that get caught here.
|
||||
if (numbered === 'dev') {
|
||||
return {
|
||||
numbers: parts[1],
|
||||
modifier: 5,
|
||||
modifierCount: 1,
|
||||
}
|
||||
}
|
||||
|
||||
let modifierLevel = 3
|
||||
let modifierLevelCount = 0
|
||||
|
||||
if (parts.length > 1) {
|
||||
const modifier = parts[parts.length - 1]
|
||||
const firstLetter = modifier.charCodeAt(0)
|
||||
let modifierLevelCountString
|
||||
|
||||
// Modifiers: alpha < beta < RC < normal < patch < dev
|
||||
if (firstLetter === 97) {
|
||||
// a
|
||||
modifierLevel = 0
|
||||
if (/^alpha/.test(modifier)) {
|
||||
modifierLevelCountString = +modifier.slice(5)
|
||||
} else {
|
||||
modifierLevelCountString = +modifier.slice(1)
|
||||
}
|
||||
} else if (firstLetter === 98) {
|
||||
// b
|
||||
modifierLevel = 1
|
||||
if (/^beta/.test(modifier)) {
|
||||
modifierLevelCountString = +modifier.slice(4)
|
||||
} else {
|
||||
modifierLevelCountString = +modifier.slice(1)
|
||||
}
|
||||
} else if (firstLetter === 82) {
|
||||
// R
|
||||
modifierLevel = 2
|
||||
modifierLevelCountString = +modifier.slice(2)
|
||||
} else if (firstLetter === 112) {
|
||||
// p
|
||||
modifierLevel = 4
|
||||
if (/^patch/.test(modifier)) {
|
||||
modifierLevelCountString = +modifier.slice(5)
|
||||
} else {
|
||||
modifierLevelCountString = +modifier.slice(1)
|
||||
}
|
||||
} else if (firstLetter === 100) {
|
||||
// d
|
||||
modifierLevel = 5
|
||||
if (/^dev/.test(modifier)) {
|
||||
modifierLevelCountString = +modifier.slice(3)
|
||||
} else {
|
||||
modifierLevelCountString = +modifier.slice(1)
|
||||
}
|
||||
}
|
||||
|
||||
// If we got the empty string, it defaults to a modifier count of 1.
|
||||
if (!modifierLevelCountString) {
|
||||
modifierLevelCount = 1
|
||||
} else {
|
||||
modifierLevelCount = +modifierLevelCountString
|
||||
}
|
||||
}
|
||||
|
||||
// Try to convert to a list of numbers.
|
||||
function toNum(s) {
|
||||
let n = +s
|
||||
if (Number.isNaN(n)) {
|
||||
n = 0xffffffff
|
||||
}
|
||||
return n
|
||||
}
|
||||
const numberList = numbered.split('.').map(toNum)
|
||||
|
||||
return {
|
||||
numbers: numberList,
|
||||
modifier: modifierLevel,
|
||||
modifierCount: modifierLevelCount,
|
||||
}
|
||||
}
|
||||
|
||||
// Return a negative value if v1 < v2,
|
||||
// zero if v1 = v2,
|
||||
// a positive value otherwise.
|
||||
//
|
||||
// See https://getcomposer.org/doc/04-schema.md#version
|
||||
// and https://github.com/badges/shields/issues/319#issuecomment-74411045
|
||||
function compare(v1, v2) {
|
||||
// Omit the starting `v`.
|
||||
const rawv1 = omitv(v1)
|
||||
const rawv2 = omitv(v2)
|
||||
let v1data, v2data
|
||||
try {
|
||||
v1data = numberedVersionData(rawv1)
|
||||
v2data = numberedVersionData(rawv2)
|
||||
} catch (e) {
|
||||
return asciiVersionCompare(rawv1, rawv2)
|
||||
}
|
||||
|
||||
// Compare the numbered part (eg, 1.0.0 < 2.0.0).
|
||||
const numbersCompare = listCompare(v1data.numbers, v2data.numbers)
|
||||
if (numbersCompare !== 0) {
|
||||
return numbersCompare
|
||||
}
|
||||
|
||||
// Compare the modifiers (eg, alpha < beta).
|
||||
if (v1data.modifier < v2data.modifier) {
|
||||
return -1
|
||||
} else if (v1data.modifier > v2data.modifier) {
|
||||
return 1
|
||||
}
|
||||
|
||||
// Compare the modifier counts (eg, alpha1 < alpha3).
|
||||
if (v1data.modifierCount < v2data.modifierCount) {
|
||||
return -1
|
||||
} else if (v1data.modifierCount > v2data.modifierCount) {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
function latest(versions) {
|
||||
let latest = versions[0]
|
||||
for (let i = 1; i < versions.length; i++) {
|
||||
if (compare(latest, versions[i]) < 0) {
|
||||
latest = versions[i]
|
||||
}
|
||||
}
|
||||
return latest
|
||||
}
|
||||
|
||||
function isStable(version) {
|
||||
const rawVersion = omitv(version)
|
||||
let versionData
|
||||
try {
|
||||
versionData = numberedVersionData(rawVersion)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
// normal or patch
|
||||
return versionData.modifier === 3 || versionData.modifier === 4
|
||||
}
|
||||
|
||||
function minorVersion(version) {
|
||||
const result = version.match(/^(\d+)(?:\.(\d+))?(?:\.(\d+))?/)
|
||||
|
||||
if (result === null) {
|
||||
return ''
|
||||
}
|
||||
|
||||
return `${result[1]}.${result[2] ? result[2] : '0'}`
|
||||
}
|
||||
|
||||
function versionReduction(versions, phpReleases) {
|
||||
if (!versions.length) {
|
||||
return ''
|
||||
}
|
||||
|
||||
// versions intersect
|
||||
versions = Array.from(new Set(versions))
|
||||
.filter(n => phpReleases.includes(n))
|
||||
.sort()
|
||||
|
||||
// nothing to reduction
|
||||
if (versions.length < 2) {
|
||||
return versions.length ? versions[0] : ''
|
||||
}
|
||||
|
||||
const first = phpReleases.indexOf(versions[0])
|
||||
const last = phpReleases.indexOf(versions[versions.length - 1])
|
||||
|
||||
// no missed versions
|
||||
if (first + versions.length - 1 === last) {
|
||||
if (last === phpReleases.length - 1) {
|
||||
return `>= ${versions[0][2] === '0' ? versions[0][0] : versions[0]}` // 7.0 -> 7
|
||||
}
|
||||
|
||||
return `${versions[0]} - ${versions[versions.length - 1]}`
|
||||
}
|
||||
|
||||
return versions.join(', ')
|
||||
}
|
||||
|
||||
function getPhpReleases(githubApiProvider) {
|
||||
return promisify(regularUpdate)({
|
||||
url: '/repos/php/php-src/git/refs/tags',
|
||||
intervalMillis: 24 * 3600 * 1000, // 1 day
|
||||
scraper: tags =>
|
||||
Array.from(
|
||||
new Set(
|
||||
tags
|
||||
// only releases
|
||||
.filter(
|
||||
tag => tag.ref.match(/^refs\/tags\/php-\d+\.\d+\.\d+$/) != null
|
||||
)
|
||||
// get minor version of release
|
||||
.map(tag => tag.ref.match(/^refs\/tags\/php-(\d+\.\d+)\.\d+$/)[1])
|
||||
)
|
||||
),
|
||||
request: (url, options, cb) =>
|
||||
githubApiProvider.request(request, url, {}, cb),
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
compare,
|
||||
latest,
|
||||
isStable,
|
||||
minorVersion,
|
||||
versionReduction,
|
||||
getPhpReleases,
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const { compare, minorVersion, versionReduction } = require('./php-version')
|
||||
|
||||
const phpReleases = [
|
||||
'5.0',
|
||||
'5.1',
|
||||
'5.2',
|
||||
'5.3',
|
||||
'5.4',
|
||||
'5.5',
|
||||
'5.6',
|
||||
'7.0',
|
||||
'7.1',
|
||||
'7.2',
|
||||
]
|
||||
|
||||
describe('Text PHP version', function() {
|
||||
test(minorVersion, () => {
|
||||
given('7').expect('7.0')
|
||||
given('7.1').expect('7.1')
|
||||
given('5.3.3').expect('5.3')
|
||||
given('hhvm').expect('')
|
||||
})
|
||||
|
||||
test(versionReduction, () => {
|
||||
given(['5.3', '5.4', '5.5'], phpReleases).expect('5.3 - 5.5')
|
||||
given(['5.4', '5.5', '5.6', '7.0', '7.1'], phpReleases).expect('5.4 - 7.1')
|
||||
given(['5.5', '5.6', '7.0', '7.1', '7.2'], phpReleases).expect('>= 5.5')
|
||||
given(['5.5', '5.6', '7.1', '7.2'], phpReleases).expect(
|
||||
'5.5, 5.6, 7.1, 7.2'
|
||||
)
|
||||
given(['7.0', '7.1', '7.2'], phpReleases).expect('>= 7')
|
||||
given(
|
||||
['5.0', '5.1', '5.2', '5.3', '5.4', '5.5', '5.6', '7.0', '7.1', '7.2'],
|
||||
phpReleases
|
||||
).expect('>= 5')
|
||||
given(['7.1', '7.2'], phpReleases).expect('>= 7.1')
|
||||
given(['7.1'], phpReleases).expect('7.1')
|
||||
given(['8.1'], phpReleases).expect('')
|
||||
given([]).expect('')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Composer version comparison', function() {
|
||||
test(compare, () => {
|
||||
// composer version scheme ordering
|
||||
given('0.9.0', '1.0.0-alpha').expect(-1)
|
||||
given('1.0.0-alpha', '1.0.0-alpha2').expect(-1)
|
||||
given('1.0.0-alpha2', '1.0.0-beta').expect(-1)
|
||||
given('1.0.0-beta', '1.0.0-beta2').expect(-1)
|
||||
given('1.0.0-beta2', '1.0.0-RC').expect(-1)
|
||||
given('1.0.0-RC', '1.0.0-RC2').expect(-1)
|
||||
given('1.0.0-RC2', '1.0.0').expect(-1)
|
||||
given('1.0.0', '1.0.0-patch').expect(-1)
|
||||
given('1.0.0-patch', '1.0.0-dev').expect(-1)
|
||||
given('1.0.0-dev', '1.0.1').expect(-1)
|
||||
given('1.0.1', '1.0.x-dev').expect(-1)
|
||||
|
||||
// short versions should compare equal to long versions
|
||||
given('1.0.0-p', '1.0.0-patch').expect(0)
|
||||
given('1.0.0-a', '1.0.0-alpha').expect(0)
|
||||
given('1.0.0-a2', '1.0.0-alpha2').expect(0)
|
||||
given('1.0.0-b', '1.0.0-beta').expect(0)
|
||||
given('1.0.0-b2', '1.0.0-beta2').expect(0)
|
||||
|
||||
// numeric suffixes
|
||||
given('1.0.0-b1', '1.0.0-b2').expect(-1)
|
||||
given('1.0.0-b10', '1.0.0-b11').expect(-1)
|
||||
given('1.0.0-a1', '1.0.0-a2').expect(-1)
|
||||
given('1.0.0-a10', '1.0.0-a11').expect(-1)
|
||||
given('1.0.0-RC1', '1.0.0-RC2').expect(-1)
|
||||
given('1.0.0-RC10', '1.0.0-RC11').expect(-1)
|
||||
})
|
||||
})
|
||||
@@ -1,206 +0,0 @@
|
||||
/**
|
||||
* Commonly-used functions for formatting text in badge labels. Includes
|
||||
* ordinal numbers, currency codes, star ratings, versions, etc.
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const moment = require('moment')
|
||||
moment().format()
|
||||
|
||||
function starRating(rating, max = 5) {
|
||||
const flooredRating = Math.floor(rating)
|
||||
let stars = ''
|
||||
while (stars.length < flooredRating) {
|
||||
stars += '★'
|
||||
}
|
||||
const decimal = rating - flooredRating
|
||||
if (decimal >= 0.875) {
|
||||
stars += '★'
|
||||
} else if (decimal >= 0.625) {
|
||||
stars += '¾'
|
||||
} else if (decimal >= 0.375) {
|
||||
stars += '½'
|
||||
} else if (decimal >= 0.125) {
|
||||
stars += '¼'
|
||||
}
|
||||
|
||||
while (stars.length < max) {
|
||||
stars += '☆'
|
||||
}
|
||||
return stars
|
||||
}
|
||||
|
||||
// Convert ISO 4217 code to unicode string.
|
||||
function currencyFromCode(code) {
|
||||
return (
|
||||
{
|
||||
CNY: '¥',
|
||||
EUR: '€',
|
||||
GBP: '₤',
|
||||
USD: '$',
|
||||
}[code] || code
|
||||
)
|
||||
}
|
||||
|
||||
function ordinalNumber(n) {
|
||||
const s = ['ᵗʰ', 'ˢᵗ', 'ⁿᵈ', 'ʳᵈ'],
|
||||
v = n % 100
|
||||
return n + (s[(v - 20) % 10] || s[v] || s[0])
|
||||
}
|
||||
|
||||
// Given a number, string with appropriate unit in the metric system, SI.
|
||||
// Note: numbers beyond the peta- cannot be represented as integers in JS.
|
||||
const metricPrefix = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
|
||||
const metricPower = metricPrefix.map((a, i) => Math.pow(1000, i + 1))
|
||||
function metric(n) {
|
||||
for (let i = metricPrefix.length - 1; i >= 0; i--) {
|
||||
const limit = metricPower[i]
|
||||
if (n >= limit) {
|
||||
n = Math.round(n / limit)
|
||||
if (n < 1000) {
|
||||
return `${n}${metricPrefix[i]}`
|
||||
} else {
|
||||
return `1${metricPrefix[i + 1]}`
|
||||
}
|
||||
}
|
||||
}
|
||||
return `${n}`
|
||||
}
|
||||
|
||||
// Remove the starting v in a string.
|
||||
function omitv(version) {
|
||||
if (version.charCodeAt(0) === 118) {
|
||||
return version.slice(1)
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
// Add a starting v to the version unless:
|
||||
// - it does not start with a digit
|
||||
// - it is a date (yyyy-mm-dd)
|
||||
const ignoredVersionPatterns = /^[^0-9]|[0-9]{4}-[0-9]{2}-[0-9]{2}/
|
||||
function addv(version) {
|
||||
version = `${version}`
|
||||
if (version.startsWith('v') || ignoredVersionPatterns.test(version)) {
|
||||
return version
|
||||
} else {
|
||||
return `v${version}`
|
||||
}
|
||||
}
|
||||
|
||||
function maybePluralize(singular, countable, plural) {
|
||||
plural = plural || `${singular}s`
|
||||
|
||||
if (countable && countable.length === 1) {
|
||||
return singular
|
||||
} else {
|
||||
return plural
|
||||
}
|
||||
}
|
||||
|
||||
function formatDate(d) {
|
||||
const date = moment(d)
|
||||
const dateString = date.calendar(null, {
|
||||
lastDay: '[yesterday]',
|
||||
sameDay: '[today]',
|
||||
lastWeek: '[last] dddd',
|
||||
sameElse: 'MMMM YYYY',
|
||||
})
|
||||
// Trim current year from date string
|
||||
return dateString.replace(` ${moment().year()}`, '').toLowerCase()
|
||||
}
|
||||
|
||||
function formatRelativeDate(timestamp) {
|
||||
return moment()
|
||||
.to(moment.unix(parseInt(timestamp, 10)))
|
||||
.toLowerCase()
|
||||
}
|
||||
|
||||
function renderTestResultMessage({
|
||||
passed,
|
||||
failed,
|
||||
skipped,
|
||||
total,
|
||||
passedLabel,
|
||||
failedLabel,
|
||||
skippedLabel,
|
||||
isCompact,
|
||||
}) {
|
||||
const labels = { passedLabel, failedLabel, skippedLabel }
|
||||
if (total === 0) {
|
||||
return 'no tests'
|
||||
} else if (isCompact) {
|
||||
;({ passedLabel = '✔', failedLabel = '✘', skippedLabel = '➟' } = labels)
|
||||
return [
|
||||
`${passedLabel} ${passed}`,
|
||||
failed > 0 && `${failedLabel} ${failed}`,
|
||||
skipped > 0 && `${skippedLabel} ${skipped}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(' | ')
|
||||
} else {
|
||||
;({
|
||||
passedLabel = 'passed',
|
||||
failedLabel = 'failed',
|
||||
skippedLabel = 'skipped',
|
||||
} = labels)
|
||||
return [
|
||||
`${passed} ${passedLabel}`,
|
||||
failed > 0 && `${failed} ${failedLabel}`,
|
||||
skipped > 0 && `${skipped} ${skippedLabel}`,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(', ')
|
||||
}
|
||||
}
|
||||
|
||||
function renderTestResultBadge({
|
||||
passed,
|
||||
failed,
|
||||
skipped,
|
||||
total,
|
||||
passedLabel,
|
||||
failedLabel,
|
||||
skippedLabel,
|
||||
isCompact,
|
||||
}) {
|
||||
const message = renderTestResultMessage({
|
||||
passed,
|
||||
failed,
|
||||
skipped,
|
||||
total,
|
||||
passedLabel,
|
||||
failedLabel,
|
||||
skippedLabel,
|
||||
isCompact,
|
||||
})
|
||||
|
||||
let color
|
||||
if (total === 0) {
|
||||
color = 'yellow'
|
||||
} else if (failed > 0) {
|
||||
color = 'red'
|
||||
} else if (skipped > 0 && passed > 0) {
|
||||
color = 'green'
|
||||
} else if (skipped > 0) {
|
||||
color = 'yellow'
|
||||
} else {
|
||||
color = 'brightgreen'
|
||||
}
|
||||
|
||||
return { message, color }
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
starRating,
|
||||
currencyFromCode,
|
||||
ordinalNumber,
|
||||
metric,
|
||||
omitv,
|
||||
addv,
|
||||
maybePluralize,
|
||||
formatDate,
|
||||
formatRelativeDate,
|
||||
renderTestResultMessage,
|
||||
renderTestResultBadge,
|
||||
}
|
||||
@@ -1,168 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const sinon = require('sinon')
|
||||
const {
|
||||
starRating,
|
||||
currencyFromCode,
|
||||
ordinalNumber,
|
||||
metric,
|
||||
omitv,
|
||||
addv,
|
||||
maybePluralize,
|
||||
formatDate,
|
||||
formatRelativeDate,
|
||||
renderTestResultMessage,
|
||||
renderTestResultBadge,
|
||||
} = require('./text-formatters')
|
||||
|
||||
describe('Text formatters', function() {
|
||||
test(starRating, () => {
|
||||
given(4.9).expect('★★★★★')
|
||||
given(3.7).expect('★★★¾☆')
|
||||
given(2.566).expect('★★½☆☆')
|
||||
given(2.2).expect('★★¼☆☆')
|
||||
given(3).expect('★★★☆☆')
|
||||
given(2, 4).expect('★★☆☆')
|
||||
})
|
||||
|
||||
test(currencyFromCode, () => {
|
||||
given('CNY').expect('¥')
|
||||
given('EUR').expect('€')
|
||||
given('GBP').expect('₤')
|
||||
given('USD').expect('$')
|
||||
given('AUD').expect('AUD')
|
||||
})
|
||||
|
||||
test(ordinalNumber, () => {
|
||||
given(2).expect('2ⁿᵈ')
|
||||
given(11).expect('11ᵗʰ')
|
||||
given(23).expect('23ʳᵈ')
|
||||
given(131).expect('131ˢᵗ')
|
||||
})
|
||||
|
||||
test(metric, () => {
|
||||
given(999).expect('999')
|
||||
given(1000).expect('1k')
|
||||
given(999499).expect('999k')
|
||||
given(999500).expect('1M')
|
||||
given(1578896212).expect('2G')
|
||||
given(80000000000000).expect('80T')
|
||||
given(4000000000000001).expect('4P')
|
||||
given(71007000100580002000).expect('71E')
|
||||
given(1000000000000000000000).expect('1Z')
|
||||
given(2222222222222222222222222).expect('2Y')
|
||||
})
|
||||
|
||||
test(omitv, () => {
|
||||
given('hello').expect('hello')
|
||||
given('v1.0.1').expect('1.0.1')
|
||||
})
|
||||
|
||||
test(addv, () => {
|
||||
given(9).expect('v9')
|
||||
given(0.1).expect('v0.1')
|
||||
given('1.0.0').expect('v1.0.0')
|
||||
given('v0.6').expect('v0.6')
|
||||
given('hello').expect('hello')
|
||||
given('2017-05-05-Release-2.3.17').expect('2017-05-05-Release-2.3.17')
|
||||
})
|
||||
|
||||
test(maybePluralize, () => {
|
||||
given('foo', []).expect('foos')
|
||||
given('foo', [123]).expect('foo')
|
||||
given('foo', [123, 456]).expect('foos')
|
||||
given('foo', undefined).expect('foos')
|
||||
|
||||
given('box', [], 'boxes').expect('boxes')
|
||||
given('box', [123], 'boxes').expect('box')
|
||||
given('box', [123, 456], 'boxes').expect('boxes')
|
||||
given('box', undefined, 'boxes').expect('boxes')
|
||||
})
|
||||
|
||||
test(formatDate, () => {
|
||||
given(1465513200000)
|
||||
.describe('when given a timestamp in june 2016')
|
||||
.expect('june 2016')
|
||||
})
|
||||
|
||||
context('in october', function() {
|
||||
let clock
|
||||
beforeEach(function() {
|
||||
clock = sinon.useFakeTimers(new Date(2017, 9, 15).getTime())
|
||||
})
|
||||
afterEach(function() {
|
||||
clock.restore()
|
||||
})
|
||||
|
||||
test(formatDate, () => {
|
||||
given(new Date(2017, 0, 1).getTime())
|
||||
.describe('when given the beginning of this year')
|
||||
.expect('january')
|
||||
})
|
||||
})
|
||||
|
||||
context('in october', function() {
|
||||
let clock
|
||||
beforeEach(function() {
|
||||
clock = sinon.useFakeTimers(new Date(2018, 9, 29).getTime())
|
||||
})
|
||||
afterEach(function() {
|
||||
clock.restore()
|
||||
})
|
||||
|
||||
test(formatRelativeDate, () => {
|
||||
given(new Date(2018, 9, 31).getTime() / 1000)
|
||||
.describe('when given the end of october')
|
||||
.expect('in 2 days')
|
||||
})
|
||||
|
||||
test(formatRelativeDate, () => {
|
||||
given(new Date(2018, 9, 1).getTime() / 1000)
|
||||
.describe('when given the beginning of october')
|
||||
.expect('a month ago')
|
||||
})
|
||||
})
|
||||
|
||||
function renderBothStyles(props) {
|
||||
const { message: standardMessage, color } = renderTestResultBadge(props)
|
||||
const compactMessage = renderTestResultMessage({
|
||||
...props,
|
||||
isCompact: true,
|
||||
})
|
||||
return { standardMessage, compactMessage, color }
|
||||
}
|
||||
|
||||
test(renderBothStyles, () => {
|
||||
given({ passed: 12, failed: 3, skipped: 3, total: 18 }).expect({
|
||||
standardMessage: '12 passed, 3 failed, 3 skipped',
|
||||
compactMessage: '✔ 12 | ✘ 3 | ➟ 3',
|
||||
color: 'red',
|
||||
})
|
||||
given({ passed: 12, failed: 3, skipped: 0, total: 15 }).expect({
|
||||
standardMessage: '12 passed, 3 failed',
|
||||
compactMessage: '✔ 12 | ✘ 3',
|
||||
color: 'red',
|
||||
})
|
||||
given({ passed: 12, failed: 0, skipped: 3, total: 15 }).expect({
|
||||
standardMessage: '12 passed, 3 skipped',
|
||||
compactMessage: '✔ 12 | ➟ 3',
|
||||
color: 'green',
|
||||
})
|
||||
given({ passed: 0, failed: 0, skipped: 3, total: 3 }).expect({
|
||||
standardMessage: '0 passed, 3 skipped',
|
||||
compactMessage: '✔ 0 | ➟ 3',
|
||||
color: 'yellow',
|
||||
})
|
||||
given({ passed: 12, failed: 0, skipped: 0, total: 12 }).expect({
|
||||
standardMessage: '12 passed',
|
||||
compactMessage: '✔ 12',
|
||||
color: 'brightgreen',
|
||||
})
|
||||
given({ passed: 0, failed: 0, skipped: 0, total: 0 }).expect({
|
||||
standardMessage: 'no tests',
|
||||
compactMessage: 'no tests',
|
||||
color: 'yellow',
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,7 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
// Cause unhandled promise rejections to fail unit tests, and print with stack
|
||||
// traces.
|
||||
process.on('unhandledRejection', error => {
|
||||
throw error
|
||||
})
|
||||
154
lib/version.js
154
lib/version.js
@@ -1,154 +0,0 @@
|
||||
/**
|
||||
* Utilities relating to generating badges relating to version numbers. Includes
|
||||
* comparing versions to determine the latest, and determining the color to use
|
||||
* for the badge based on whether the version is a stable release.
|
||||
*
|
||||
* For utilities specific to PHP version ranges, see php-version.js.
|
||||
*/
|
||||
'use strict'
|
||||
|
||||
const semver = require('semver')
|
||||
const { addv } = require('./text-formatters')
|
||||
const { version: versionColor } = require('./color-formatters')
|
||||
|
||||
// Given a list of versions (as strings), return the latest version.
|
||||
// Return undefined if no version could be found.
|
||||
function latest(versions, { pre = false } = {}) {
|
||||
let version = ''
|
||||
let origVersions = versions
|
||||
// return all results that are likely semver compatible versions
|
||||
versions = origVersions.filter(version => /\d+\.\d+/.test(version))
|
||||
// If no semver versions then look for single numbered versions
|
||||
if (!versions.length) {
|
||||
versions = origVersions.filter(version => /\d+/.test(version))
|
||||
}
|
||||
if (!pre) {
|
||||
// remove pre-releases from array
|
||||
versions = versions.filter(version => !/\d+-\w+/.test(version))
|
||||
}
|
||||
try {
|
||||
// coerce to string then lowercase otherwise alpha > RC
|
||||
version = versions.sort((a, b) =>
|
||||
semver.rcompare(
|
||||
`${a}`.toLowerCase(),
|
||||
`${b}`.toLowerCase(),
|
||||
/* loose */ true
|
||||
)
|
||||
)[0]
|
||||
} catch (e) {
|
||||
version = latestDottedVersion(versions)
|
||||
}
|
||||
if (version === undefined || version === null) {
|
||||
origVersions = origVersions.sort()
|
||||
version = origVersions[origVersions.length - 1]
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
function listCompare(a, b) {
|
||||
const alen = a.length,
|
||||
blen = b.length
|
||||
for (let i = 0; i < alen; i++) {
|
||||
if (a[i] < b[i]) {
|
||||
return -1
|
||||
} else if (a[i] > b[i]) {
|
||||
return 1
|
||||
}
|
||||
}
|
||||
return alen - blen
|
||||
}
|
||||
|
||||
// === Private helper functions ===
|
||||
|
||||
// Take a list of string versions.
|
||||
// Return the latest, or undefined, if there are none.
|
||||
function latestDottedVersion(versions) {
|
||||
const len = versions.length
|
||||
if (len === 0) {
|
||||
return
|
||||
}
|
||||
let version = versions[0]
|
||||
for (let i = 1; i < len; i++) {
|
||||
if (compareDottedVersion(version, versions[i]) < 0) {
|
||||
version = versions[i]
|
||||
}
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
// Take string versions.
|
||||
// -1 if v1 < v2, 1 if v1 > v2, 0 otherwise.
|
||||
function compareDottedVersion(v1, v2) {
|
||||
const parts1 = /([0-9.]+)(.*)$/.exec(v1)
|
||||
const parts2 = /([0-9.]+)(.*)$/.exec(v2)
|
||||
if (parts1 != null && parts2 != null) {
|
||||
const numbers1 = parts1[1]
|
||||
const numbers2 = parts2[1]
|
||||
const distinguisher1 = parts1[2]
|
||||
const distinguisher2 = parts2[2]
|
||||
const numlist1 = numbers1.split('.').map(e => +e)
|
||||
const numlist2 = numbers2.split('.').map(e => +e)
|
||||
const cmp = listCompare(numlist1, numlist2)
|
||||
if (cmp !== 0) {
|
||||
return cmp
|
||||
} else {
|
||||
return distinguisher1 < distinguisher2
|
||||
? -1
|
||||
: distinguisher1 > distinguisher2
|
||||
? 1
|
||||
: 0
|
||||
}
|
||||
}
|
||||
return v1 < v2 ? -1 : v1 > v2 ? 1 : 0
|
||||
}
|
||||
|
||||
// Slice the specified number of dotted parts from the given semver version.
|
||||
// e.g. slice('2.4.7', 'minor') -> '2.4'
|
||||
function slice(v, releaseType) {
|
||||
if (!semver.valid(v, /* loose */ true)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const major = semver.major(v, /* loose */ true)
|
||||
const minor = semver.minor(v, /* loose */ true)
|
||||
const patch = semver.patch(v, /* loose */ true)
|
||||
const prerelease = semver.prerelease(v, /* loose */ true)
|
||||
|
||||
const dottedParts = {
|
||||
major: [major],
|
||||
minor: [major, minor],
|
||||
patch: [major, minor, patch],
|
||||
}[releaseType]
|
||||
|
||||
if (dottedParts === undefined) {
|
||||
throw Error(`Unknown releaseType: ${releaseType}`)
|
||||
}
|
||||
|
||||
const dotted = dottedParts.join('.')
|
||||
if (prerelease) {
|
||||
return `${dotted}-${prerelease.join('.')}`
|
||||
} else {
|
||||
return dotted
|
||||
}
|
||||
}
|
||||
|
||||
function rangeStart(v) {
|
||||
const range = new semver.Range(v, /* loose */ true)
|
||||
return range.set[0][0].semver.version
|
||||
}
|
||||
|
||||
function renderVersionBadge({ version, tag, defaultLabel }) {
|
||||
return {
|
||||
label: tag ? `${defaultLabel}@${tag}` : undefined,
|
||||
message: addv(version),
|
||||
color: versionColor(version),
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
latest,
|
||||
listCompare,
|
||||
slice,
|
||||
rangeStart,
|
||||
renderVersionBadge,
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
'use strict'
|
||||
|
||||
const { test, given } = require('sazerac')
|
||||
const { latest, slice, rangeStart, renderVersionBadge } = require('./version')
|
||||
const includePre = true
|
||||
|
||||
describe('Version helpers', function() {
|
||||
test(latest, () => {
|
||||
// semver-compatible versions.
|
||||
given(['1.0.0', '1.0.2', '1.0.1']).expect('1.0.2')
|
||||
given(['1.0.0', '2.0.0', '3.0.0']).expect('3.0.0')
|
||||
given(['0.0.1', '0.0.10', '0.0.2', '0.0.20']).expect('0.0.20')
|
||||
|
||||
// "not-quite-valid" semver versions
|
||||
given(['1.0.00', '1.0.02', '1.0.01']).expect('1.0.02')
|
||||
given(['1.0.05', '2.0.05', '3.0.05']).expect('3.0.05')
|
||||
given(['0.0.01', '0.0.010', '0.0.02', '0.0.020']).expect('0.0.020')
|
||||
|
||||
// Mixed style versions. - include pre-releases
|
||||
given(['1.0.0', 'v1.0.2', 'r1.0.1', 'release-2.0.0', 'v1.0.1-alpha.1'], {
|
||||
pre: includePre,
|
||||
}).expect('release-2.0.0')
|
||||
given(['1.0.0', 'v2.0.0', 'r1.0.1', 'release-1.0.3', 'v1.0.1-alpha.1'], {
|
||||
pre: includePre,
|
||||
}).expect('v2.0.0')
|
||||
given(['2.0.0', 'v1.0.3', 'r1.0.1', 'release-1.0.3', 'v1.0.1-alpha.1'], {
|
||||
pre: includePre,
|
||||
}).expect('2.0.0')
|
||||
given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3', 'v1.0.1-alpha.1'], {
|
||||
pre: includePre,
|
||||
}).expect('r2.0.0')
|
||||
given(['1.0.0', 'v1.0.2', 'r2.0.0', 'release-1.0.3', 'v2.0.1-alpha.1'], {
|
||||
pre: includePre,
|
||||
}).expect('v2.0.1-alpha.1')
|
||||
|
||||
// Versions with 'v' prefix.
|
||||
given(['v1.0.0', 'v1.0.2', 'v1.0.1']).expect('v1.0.2')
|
||||
given(['v1.0.0', 'v3.0.0', 'v2.0.0']).expect('v3.0.0')
|
||||
|
||||
// Simple (2 number) versions.
|
||||
given(['0.1', '0.3', '0.2']).expect('0.3')
|
||||
given(['0.1', '0.5', '0.12', '0.21']).expect('0.21')
|
||||
given(['1.0', '2.0', '3.0']).expect('3.0')
|
||||
|
||||
// Simple (one-number) versions
|
||||
given(['2', '10', '1']).expect('10')
|
||||
|
||||
// Include pre-releases
|
||||
given(
|
||||
[
|
||||
'v1.0.1-alpha.2',
|
||||
'v1.0.1-alpha.1',
|
||||
'v1.0.1-beta.3',
|
||||
'v1.0.1-beta.1',
|
||||
'v1.0.1-RC.1',
|
||||
'v1.0.1-RC.2',
|
||||
'v1.0.0',
|
||||
],
|
||||
{ pre: includePre }
|
||||
).expect('v1.0.1-RC.2')
|
||||
given(
|
||||
[
|
||||
'v1.0.1-alpha.2',
|
||||
'v1.0.1-alpha.1',
|
||||
'v1.0.1-beta.3',
|
||||
'v1.0.1-beta.1',
|
||||
'v1.0.1-RC.1',
|
||||
'v1.0.1-RC.2',
|
||||
'v1.0.1',
|
||||
],
|
||||
{ pre: includePre }
|
||||
).expect('v1.0.1')
|
||||
// Exclude pre-releases
|
||||
given([
|
||||
'v1.0.1-alpha.2',
|
||||
'v1.0.1-alpha.1',
|
||||
'v1.0.1-beta.3',
|
||||
'v1.0.1-beta.1',
|
||||
'v1.0.1-RC.1',
|
||||
'v1.0.1-RC.2',
|
||||
'v1.0.0',
|
||||
]).expect('v1.0.0')
|
||||
given([
|
||||
'v1.0.1-alpha.2',
|
||||
'v1.0.1-alpha.1',
|
||||
'v1.0.1-beta.3',
|
||||
'v1.0.1-beta.1',
|
||||
'v1.0.1-RC.1',
|
||||
'v1.0.1-RC.2',
|
||||
'v1.0.1',
|
||||
]).expect('v1.0.1')
|
||||
|
||||
// Versions with 'release-' prefix
|
||||
given([
|
||||
'release-1.0.0',
|
||||
'release-1.0.2',
|
||||
'release-1.0.20',
|
||||
'release-1.0.3',
|
||||
]).expect('release-1.0.20')
|
||||
|
||||
// Semver mixed with non semver versions
|
||||
given(['1.0.0', '1.0.2', '1.1', '1.0', 'notaversion2', '12bcde4']).expect(
|
||||
'1.1'
|
||||
)
|
||||
})
|
||||
|
||||
test(slice, () => {
|
||||
given('2.4.7', 'major').expect('2')
|
||||
given('2.4.7', 'minor').expect('2.4')
|
||||
given('2.4.7', 'patch').expect('2.4.7')
|
||||
given('02.4.7', 'major').expect('2')
|
||||
given('2.04.7', 'minor').expect('2.4')
|
||||
given('2.4.07', 'patch').expect('2.4.7')
|
||||
given('2.4.7-alpha.1', 'major').expect('2-alpha.1')
|
||||
given('2.4.7-alpha.1', 'minor').expect('2.4-alpha.1')
|
||||
given('2.4.7-alpha.1', 'patch').expect('2.4.7-alpha.1')
|
||||
})
|
||||
|
||||
test(rangeStart, () => {
|
||||
given('^2.4.7').expect('2.4.7')
|
||||
})
|
||||
|
||||
test(renderVersionBadge, () => {
|
||||
given({ version: '1.2.3' }).expect({
|
||||
label: undefined,
|
||||
message: 'v1.2.3',
|
||||
color: 'blue',
|
||||
})
|
||||
given({ version: '1.2.3', tag: 'next', defaultLabel: 'npm' }).expect({
|
||||
label: 'npm@next',
|
||||
message: 'v1.2.3',
|
||||
color: 'blue',
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user