Packages

semver

Parse, compare, sort, bump and range-match semantic versions such as 1.4.0-rc.1.

import "std:semver"

A semantic version has three numbers and, optionally, extras: 1.4.2, 2.0.0-rc.1, 1.0.0+build.5. The numbers are major, minor and patch. std:semver understands them, so you can compare versions the way a human would, where 1.10.0 is newer than 1.2.0 (compare them as text and you get the wrong answer).

Use it for update checks, plugin compatibility, release tooling, and anything that reads a version from a file or an API.

Most functions return a result, because a string may not be a version at all.

Checking and parsing#

isValid says whether text is a version. parse takes it apart:

main.osl
import "std:semver"

log semver.isValid("1.4.0-rc.1")
log semver.isValid("1.2")
log semver.isValid("v2.0.1")

auto v = semver.parse("1.4.0-rc.1+build.5").unwrap()
log v.major v.minor v.patch
log v.prerelease
log v.build
Click the code to edit it

Three numbers are required, so 1.2 is not valid. A leading v is accepted and ignored. prerelease and build are null when a version has none.

A bad string is a ParseError:

main.osl
import "std:semver"

auto bad = semver.parse("latest")
log bad.isErr()
log bad.unwrapErr().kind
Click the code to edit it

Comparing#

Never compare versions as text. compare returns -1, 0 or 1, and gt, gte, lt, lte, eq and neq return booleans:

main.osl
import "std:semver"

log "1.10.0" > "1.2.0"
log semver.gt("1.10.0", "1.2.0").unwrap()
log semver.compare("1.2.3", "1.10.0").unwrap()
log semver.lt("1.0.0-alpha", "1.0.0").unwrap()
log semver.eq("1.0.0+a", "1.0.0+b").unwrap()
Click the code to edit it

Two rules are worth knowing. A pre-release is lower than its release (1.0.0-alpha comes before 1.0.0). Build metadata after the + is ignored, so the last two versions are equal.

Update checks#

A real use: tell the user whether an update is available. The unwrapOr keeps one bad value from stopping the program:

main.osl
import "std:semver"

def updateMessage(string current, string latest) string (
  if semver.gt(latest, current).unwrapOr(false) (
    return "update available: " ++ current ++ " to " ++ latest
  )
  return "up to date"
)

log updateMessage("1.9.0", "1.10.0")
log updateMessage("2.0.0", "2.0.0")
log updateMessage("2.0.0", "oops")
Click the code to edit it

Bumping#

inc(version, part) raises "major", "minor" or "patch" and resets the parts after it:

main.osl
import "std:semver"

log semver.inc("1.4.2", "patch").unwrap()
log semver.inc("1.4.2", "minor").unwrap()
log semver.inc("1.4.2", "major").unwrap()
Click the code to edit it

Sorting and picking#

sort puts a list in ascending order, and max and min return the highest and lowest. They return the strings as you wrote them:

main.osl
import "std:semver"

string[] tags = ["1.10.0", "1.2.0", "1.2.0-beta", "0.9.9"]
log semver.sort(tags).unwrap()
log semver.max(tags).unwrap()
log semver.min(tags).unwrap()
Click the code to edit it

One invalid entry makes the whole call fail with a ParseError, so filter first with tags.filter(t -> semver.isValid(t)) when the list comes from outside.

Ranges#

satisfies(version, range) is how dependencies say what they accept. It uses npm-style ranges:

RangeAccepts
^1.2.01.2.0 up to, but not including, 2.0.0
~1.4.01.4.0 up to, but not including, 1.5.0
>=1.2.0 <1.5.0both comparisons must hold
1.2.xany patch of 1.2
1.2.x || 1.3.xeither side
main.osl
import "std:semver"

log semver.satisfies("1.4.2", "^1.2.0").unwrap()
log semver.satisfies("2.0.0", "^1.2.0").unwrap()
log semver.satisfies("1.4.9", "~1.4.0").unwrap()
log semver.satisfies("1.5.0", ">=1.2.0 <1.5.0").unwrap()
log semver.satisfies("1.3.0", "1.2.x || 1.3.x").unwrap()
Click the code to edit it

Ranges compare by version order, so a pre-release counts as lower than its release: semver.satisfies("1.0.0-beta", "^1.0.0") is false. Unlike npm, satisfies doesn't exclude pre-releases of later versions, so 1.9.0-beta does satisfy ^1.0.0. Filter pre-releases out yourself (v.prerelease == null after semver.parse) if stable-only matters.

Combined, these make a compatibility check:

main.osl
import "std:semver"

object plugins = {
  charts: "1.3.0",
  export: "2.0.1",
  themes: "1.9.4"
}
string need = "^1.2.0"

for name in plugins.getKeys().toSorted() (
  string version = plugins[name]
  string verdict = "incompatible"
  if semver.satisfies(version, need).unwrapOr(false) ( verdict = "ok" )
  log name version verdict
)
Click the code to edit it
Quick check

What does semver.satisfies("1.5.0", "~1.4.0") give?

Your turn

Newest compatible version

Print the newest version in tags that satisfies ^1.0.0. Right now it prints the newest of all, which is 2.0.0.

main.osl
Ctrl Enter runs
Expected output
1.8.2

API reference#

14 members, generated from the std:semver source of OSL 9.3.0.

Functions

#semver.compare(string a, string b) result<int, error>

compare returns -1, 0 or 1 as a is lower than, equal to or higher than b in semantic version precedence; build metadata is ignored. Fails with ParseError when either is not a semantic version.

#semver.eq(string a, string b) result<boolean, error>

eq reports whether versions a and b have equal precedence (build metadata is ignored). Fails with ParseError when either is not a semantic version.

#semver.gt(string a, string b) result<boolean, error>

gt reports whether version a is higher than b. Fails with ParseError when either is not a semantic version.

#semver.gte(string a, string b) result<boolean, error>

gte reports whether version a is higher than or equal to b. Fails with ParseError when either is not a semantic version.

#semver.inc(string version, string part) result<string, error>

inc returns version with part ("major", "minor" or "patch") increased and the lower parts, prerelease and build cleared. Fails with ParseError when version is not a semantic version; throws ValueError for any other part.

#semver.isValid(string text) boolean

isValid reports whether text is a semantic version semver.parse accepts.

#semver.lt(string a, string b) result<boolean, error>

lt reports whether version a is lower than b. Fails with ParseError when either is not a semantic version.

#semver.lte(string a, string b) result<boolean, error>

lte reports whether version a is lower than or equal to b. Fails with ParseError when either is not a semantic version.

#semver.max(any versions) result<string, error>

max returns the highest of an array of version strings, as written. Fails with ParseError when any item is not a semantic version and ValueError when versions is empty.

#semver.min(any versions) result<string, error>

min returns the lowest of an array of version strings, as written. Fails with ParseError when any item is not a semantic version and ValueError when versions is empty.

#semver.neq(string a, string b) result<boolean, error>

neq reports whether versions a and b have different precedence. Fails with ParseError when either is not a semantic version.

#semver.parse(string text) result<{build: string?, major: int, minor: int, patch: int, prerelease: string?, valid: boolean (deprecated)}, error>

parse reads a semantic version such as "1.4.0-rc.1+build.5"; a leading "v" is allowed. Returns a record {major, minor, patch, prerelease, build} where prerelease and build are strings, or null when absent; the deprecated key valid is always true. Fails with ParseError when text is not a semantic version.

#semver.satisfies(string version, string constraint) result<boolean, error>

satisfies reports whether version is in the npm-style range: comparators (">=1.2.0 <2"), caret and tilde ranges ("^1.2", "~1.2.3"), x-ranges ("1.x"), hyphen ranges ("1.2 - 1.5") and alternatives joined by "||". Prereleases only match comparators on the same major.minor.patch. Fails with ParseError when version is not a semantic version or the range is malformed.

#semver.sort(any versions) result<string[], error>

sort returns an array of version strings in ascending precedence as a new array; equal versions keep their order. Throws TypeError when versions is not an array of strings. Fails with ParseError when any item is not a semantic version.