@fedify/fedify
Version:
An ActivityPub server framework
149 lines (146 loc) • 5.02 kB
JavaScript
import { Temporal } from "@js-temporal/polyfill";
import { URLPattern } from "urlpattern-polyfill";
globalThis.addEventListener = () => {};
//#region nodeinfo/semver.ts
const MAX_LENGTH = 256;
/**
* A single `0`, or a non-zero digit followed by zero or more digits.
*/
const NUMERIC_IDENTIFIER = "0|[1-9]\\d*";
/**
* Zero or more digits, followed by a letter or hyphen, and then zero or more letters, digits, or hyphens.
*/
const NON_NUMERIC_IDENTIFIER = "\\d*[a-zA-Z-][a-zA-Z0-9-]*";
/**
* Three dot-separated numeric identifiers.
*/
const VERSION_CORE = `(?<major>${NUMERIC_IDENTIFIER})\\.(?<minor>${NUMERIC_IDENTIFIER})\\.(?<patch>${NUMERIC_IDENTIFIER})`;
/**
* A numeric identifier, or a non-numeric identifier.
*/
const PRERELEASE_IDENTIFIER = `(?:${NUMERIC_IDENTIFIER}|${NON_NUMERIC_IDENTIFIER})`;
/**
* A hyphen, followed by one or more dot-separated pre-release version identifiers.
* @example "-pre.release"
*/
const PRERELEASE = `(?:-(?<prerelease>${PRERELEASE_IDENTIFIER}(?:\\.${PRERELEASE_IDENTIFIER})*))`;
/**
* Any combination of digits, letters, or hyphens.
*/
const BUILD_IDENTIFIER = "[0-9A-Za-z-]+";
/**
* A plus sign, followed by one or more period-separated build metadata identifiers.
* @example "+build.meta"
*/
const BUILD = `(?:\\+(?<buildmetadata>${BUILD_IDENTIFIER}(?:\\.${BUILD_IDENTIFIER})*))`;
/**
* A version, followed optionally by a pre-release version and build metadata.
*/
const FULL_VERSION = `v?${VERSION_CORE}${PRERELEASE}?${BUILD}?`;
const FULL_REGEXP = /* @__PURE__ */ new RegExp(`^${FULL_VERSION}$`);
/**
* Attempt to parse a string as a semantic version, returning a SemVer object.
*
* @example Usage
* ```ts
* import { parseSemVer } from "@fedify/fedify/nodeinfo";
* import { assertEquals } from "@std/assert";
*
* const version = parseSemVer("1.2.3");
* assertEquals(version, {
* major: 1,
* minor: 2,
* patch: 3,
* prerelease: [],
* build: [],
* });
* ```
*
* @throws {TypeError} If the input string is invalid.
* @param value The version string to parse
* @returns A valid SemVer
* @since 1.2.0
*/
function parseSemVer(value) {
if (typeof value !== "string") throw new TypeError(`Cannot parse version as version must be a string: received ${typeof value}`);
if (value.length > MAX_LENGTH) throw new TypeError(`Cannot parse version as version length is too long: length is ${value.length}, max length is ${MAX_LENGTH}`);
value = value.trim();
const groups = value.match(FULL_REGEXP)?.groups;
if (!groups) throw new TypeError(`Cannot parse version: ${value}`);
const major = parseNumber(groups.major, `Cannot parse version ${value}: invalid major version`);
const minor = parseNumber(groups.minor, `Cannot parse version ${value}: invalid minor version`);
const patch = parseNumber(groups.patch, `Cannot parse version ${value}: invalid patch version`);
const prerelease = groups.prerelease ? parsePrerelease(groups.prerelease) : [];
const build = groups.buildmetadata ? parseBuild(groups.buildmetadata) : [];
return {
major,
minor,
patch,
prerelease,
build
};
}
/**
* Returns true if the value is a valid SemVer number.
*
* Must be a number. Must not be NaN. Can be positive or negative infinity.
* Can be between 0 and MAX_SAFE_INTEGER.
* @param value The value to check
* @returns True if its a valid semver number
*/
function isValidNumber(value) {
return typeof value === "number" && !Number.isNaN(value) && (!Number.isFinite(value) || 0 <= value && value <= Number.MAX_SAFE_INTEGER);
}
const NUMERIC_IDENTIFIER_REGEXP = /* @__PURE__ */ new RegExp(`^${NUMERIC_IDENTIFIER}$`);
function parsePrerelease(prerelease) {
return prerelease.split(".").filter(Boolean).map((id) => {
if (NUMERIC_IDENTIFIER_REGEXP.test(id)) {
const number = Number(id);
if (isValidNumber(number)) return number;
}
return id;
});
}
function parseBuild(buildmetadata) {
return buildmetadata.split(".").filter(Boolean);
}
function parseNumber(input, errorMessage) {
const number = Number(input);
if (!isValidNumber(number)) throw new TypeError(errorMessage);
return number;
}
function formatNumber(value) {
return value.toFixed(0);
}
/**
* Format a SemVer object into a string.
*
* @example Usage
* ```ts
* import { formatSemVer } from "@fedify/fedify/nodeinfo";
* import { assertEquals } from "@std/assert";
*
* const semver = {
* major: 1,
* minor: 2,
* patch: 3,
* };
* assertEquals(formatSemVer(semver), "1.2.3");
* ```
*
* @param version The SemVer to format
* @returns The string representation of a semantic version.
* @since 1.2.0
*/
function formatSemVer(version) {
const major = formatNumber(version.major);
const minor = formatNumber(version.minor);
const patch = formatNumber(version.patch);
const pre = version.prerelease?.join(".") ?? "";
const build = version.build?.join(".") ?? "";
const primary = `${major}.${minor}.${patch}`;
const release = [primary, pre].filter((v) => v).join("-");
return [release, build].filter((v) => v).join("+");
}
//#endregion
export { formatSemVer, parseSemVer };