std/semver/increment.ts

199 lines
6.3 KiB
TypeScript

// Copyright 2018-2024 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.
import { parseBuild } from "./_shared.ts";
import type { ReleaseType, SemVer } from "./types.ts";
function bumpPrereleaseNumber(prerelease: ReadonlyArray<string | number> = []) {
const values = [...prerelease];
let index = values.length;
while (index >= 0) {
const value = values[index];
if (typeof value === "number") {
values[index] = value + 1;
break;
}
index -= 1;
}
// if no number was bumped
if (index === -1) values.push(0);
return values;
}
function bumpPrerelease(
prerelease: ReadonlyArray<string | number> = [],
identifier: string | undefined,
) {
let values = bumpPrereleaseNumber(prerelease);
if (!identifier) return values;
// 1.2.0-beta.1 bumps to 1.2.0-beta.2,
// 1.2.0-beta.foobar or 1.2.0-beta bumps to 1.2.0-beta.0
if (values[0] !== identifier || isNaN(values[1] as number)) {
values = [identifier, 0];
}
return values;
}
/** Options for {@linkcode increment}. */
export interface IncrementOptions {
/** The pre-release metadata of the new version. */
prerelease?: string;
/** The build metadata of the new version. */
build?: string;
}
/**
* Returns the new SemVer resulting from an increment by release type.
*
* `premajor`, `preminor` and `prepatch` will bump the version up to the next version,
* based on the type, and will also add prerelease metadata.
*
* If called from a non-prerelease version, the `prerelease` will work the same as
* `prepatch`. The patch version is incremented and then is made into a prerelease. If
* the input version is already a prerelease it will simply increment the prerelease
* metadata.
*
* If a prerelease identifier is specified without a number then a number will be added.
* For example `pre` will result in `pre.0`. If the existing version already has a
* prerelease with a number and its the same prerelease identifier then the number
* will be incremented. If the identifier differs from the new identifier then the new
* identifier is applied and the number is reset to `0`.
*
* If the input version has build metadata it will be preserved on the resulting version
* unless a new build parameter is specified. Specifying `""` will unset existing build
* metadata.
*
* @example Usage
* ```ts
* import { increment, parse } from "@std/semver";
* import { assertEquals } from "@std/assert";
*
* const version = parse("1.2.3");
* assertEquals(increment(version, "major"), parse("2.0.0"));
* assertEquals(increment(version, "minor"), parse("1.3.0"));
* assertEquals(increment(version, "patch"), parse("1.2.4"));
* assertEquals(increment(version, "prerelease"), parse("1.2.4-0"));
*
* const prerelease = parse("1.2.3-beta.0");
* assertEquals(increment(prerelease, "prerelease"), parse("1.2.3-beta.1"));
* ```
*
* @param version The version to increment
* @param release The type of increment to perform
* @param options Additional options
* @returns The new version
*/
export function increment(
version: SemVer,
release: ReleaseType,
options: IncrementOptions = {},
): SemVer {
const build = options.build !== undefined
? parseBuild(options.build)
: version.build;
switch (release) {
case "premajor":
return {
major: version.major + 1,
minor: 0,
patch: 0,
prerelease: bumpPrerelease(version.prerelease, options.prerelease),
build,
};
case "preminor":
return {
major: version.major,
minor: version.minor + 1,
patch: 0,
prerelease: bumpPrerelease(version.prerelease, options.prerelease),
build,
};
case "prepatch":
return {
major: version.major,
minor: version.minor,
patch: version.patch + 1,
prerelease: bumpPrerelease(version.prerelease, options.prerelease),
build,
};
case "prerelease": {
// If the input is a non-prerelease version, this acts the same as prepatch.
const isPrerelease = (version.prerelease ?? []).length === 0;
const patch = isPrerelease ? version.patch + 1 : version.patch;
return {
major: version.major,
minor: version.minor,
patch,
prerelease: bumpPrerelease(version.prerelease, options.prerelease),
build,
};
}
case "major": {
// If this is a pre-major version, bump up to the same major version. Otherwise increment major.
// 1.0.0-5 bumps to 1.0.0
// 1.1.0 bumps to 2.0.0
const isPrerelease = (version.prerelease ?? []).length === 0;
const major = isPrerelease || version.minor !== 0 || version.patch !== 0
? version.major + 1
: version.major;
return {
major,
minor: 0,
patch: 0,
prerelease: [],
build,
};
}
case "minor": {
// If this is a pre-minor version, bump up to the same minor version. Otherwise increment minor.
// 1.2.0-5 bumps to 1.2.0
// 1.2.1 bumps to 1.3.0
const isPrerelease = (version.prerelease ?? []).length === 0;
const minor = isPrerelease || version.patch !== 0
? version.minor + 1
: version.minor;
return {
major: version.major,
minor,
patch: 0,
prerelease: [],
build,
};
}
case "patch": {
// If this is not a pre-release version, it will increment the patch.
// If it is a pre-release it will bump up to the same patch version.
// 1.2.0-5 patches to 1.2.0
// 1.2.0 patches to 1.2.1
const isPrerelease = (version.prerelease ?? []).length === 0;
const patch = isPrerelease ? version.patch + 1 : version.patch;
return {
major: version.major,
minor: version.minor,
patch,
prerelease: [],
build,
};
}
case "pre": {
// 1.0.0 "pre" would become 1.0.0-0
// 1.0.0-0 would become 1.0.0-1
// 1.0.0-beta.0 would be come 1.0.0-beta.1
// switching the pre identifier resets the number to 0
return {
major: version.major,
minor: version.minor,
patch: version.patch,
prerelease: bumpPrerelease(version.prerelease, options.prerelease),
build,
};
}
default:
throw new TypeError(
`Cannot increment version: invalid argument ${release}`,
);
}
}