Last active
April 6, 2025 21:56
-
-
Save sybrew/fd5b447d1a9ccd4a3344d8267828d7a1 to your computer and use it in GitHub Desktop.
Semver sorter
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* Sorts an array of version objects based on their semantic version numbers, including pre-release versions. | |
* | |
* This function parses each version string using a regular expression to extract its numeric, pre-release, | |
* and build components and then compares them to order the array. | |
* | |
* It supports complex version such as (in order) "1.4.9", "1.5.0-alpha", "1.5.0-alpha2", and "1.5.0", ensuring | |
* that pre-release versions are sorted correctly relative to final releases. | |
* | |
* @param {Array<Object>} versions - An array of version objects where each object contains a "version" property of type string. | |
* @returns {Array<Object>} - A new array of version objects sorted in ascending order based on semantic versioning. | |
*/ | |
function sortVersions( versions = [] ) { | |
// Copied from https://semver.org/ | |
const versionRegex = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/; | |
const parseVersion = ( version ) => { | |
const match = version.match( versionRegex ); | |
if ( ! match ) return [ 0, 0, 0, 0 ]; | |
const major = parseInt( match[ 1 ], 10 ); | |
const minor = parseInt( match[ 2 ], 10 ); | |
const patch = parseInt( match[ 3 ], 10 ); | |
const mapping = { dev: 0, alpha: 1, a: 1, beta: 2, b: 2, rc: 3 }; | |
let prerelease = []; | |
if ( match[ 4 ] ) { | |
const parts = match[ 4 ].split( '.' ); | |
parts.forEach( part => { | |
const letterMatch = part.match( /^[a-zA-Z]+/ ); | |
if ( letterMatch ) { | |
const letter = letterMatch[0].toLowerCase(); | |
const numberPart = part.slice( letter.length ); | |
prerelease.push( mapping[ letter ] ); | |
prerelease.push( numberPart ? parseInt( numberPart, 10 ) : 0 ); | |
} else { | |
prerelease.push( -1 ); | |
} | |
} ); | |
} else { | |
prerelease.push( 4 ); // Final release indicator (higher than pre-releases) | |
} | |
const build = match[ 5 ] ? match[ 5 ].split( '.' ).map( s => parseInt( s, 10 ) ) : []; | |
return [ major, minor, patch, ...prerelease, ...build ]; | |
} | |
return versions.sort( ( a, b ) => { | |
const versionA = parseVersion( a.version ); | |
const versionB = parseVersion( b.version ); | |
for ( let i = 0; i < Math.max( versionA.length, versionB.length ); i++ ) | |
if ( versionA[ i ] !== versionB[ i ] ) | |
return versionA[ i ] - versionB[ i ]; | |
return 0; | |
} ); | |
} | |
// --- Test: | |
const exampleVersions = [ | |
{ version: '1.5.3-beta', data: '1.5.3-beta' }, | |
{ version: '1.5.3', data: '1.5.3' }, | |
{ version: '1.5.2', data: '1.5.2' }, | |
{ version: '1.4.9-beta', data: '1.4.9-beta' }, | |
{ version: '1.4.9-beta2', data: '1.4.9-beta2' }, | |
{ version: '1.4.9', data: '1.4.9' }, | |
{ version: '1.4.9-rc1', data: '1.4.9-rc1' }, | |
{ version: '1.5.1', data: '1.5.1' }, | |
{ version: '1.5.3-alpha', data: '1.5.3-alpha' }, | |
{ version: '1.5.0', data: '1.5.0' }, | |
{ version: '1.4.9-rc', data: '1.4.9-rc' }, | |
{ version: '1.4.8', data: '1.4.8' }, | |
{ version: '1.4.7', data: '1.4.7' }, | |
]; | |
console.log( sortVersions(exampleVersions ) ); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment