Semantic versioning utilities for version comparison, validation, range checking, and version manipulation following semver.org standards.
Functions for validating and cleaning semantic version strings.
/**
* Validate if string is a valid semantic version
* @param version - Version string to validate
* @returns Valid semver string or null if invalid
*/
valid(version: string): string | null;
/**
* Clean and normalize version string to valid semver
* @param version - Version string to clean
* @returns Cleaned semver string or null if invalid
*/
clean(version: string): string | null;Version Validation Examples:
import { semver } from "gluegun";
// Validate version strings
semver.valid("1.2.3"); // "1.2.3"
semver.valid("v1.2.3"); // "1.2.3"
semver.valid("1.2"); // null (invalid)
semver.valid("1.2.3-alpha"); // "1.2.3-alpha"
semver.valid("invalid"); // null
// Clean version strings
semver.clean(" v1.2.3 "); // "1.2.3"
semver.clean("=1.2.3"); // "1.2.3"
semver.clean("1.2.3-beta+build"); // "1.2.3-beta+build"
semver.clean("1.2"); // null (cannot be cleaned to valid semver)
// Usage in CLI
const userVersion = "v2.1.0-rc.1";
const cleanVersion = semver.clean(userVersion);
if (cleanVersion) {
console.log(`Using version: ${cleanVersion}`);
} else {
console.error(`Invalid version format: ${userVersion}`);
}Functions for comparing semantic versions to determine ordering and relationships.
/**
* Check if first version is greater than second version
* @param version - First version to compare
* @param other - Second version to compare
* @returns True if first version is greater
*/
gt(version: string, other: string): boolean;
/**
* Check if first version is less than second version
* @param version - First version to compare
* @param other - Second version to compare
* @returns True if first version is less
*/
lt(version: string, other: string): boolean;Version Comparison Examples:
import { semver } from "gluegun";
// Greater than comparison
semver.gt("2.0.0", "1.9.9"); // true
semver.gt("1.0.0", "1.0.0"); // false
semver.gt("1.0.1", "1.0.0"); // true
semver.gt("2.0.0-alpha", "1.9.9"); // true
semver.gt("2.0.0-alpha", "2.0.0-beta"); // false (alpha < beta)
// Less than comparison
semver.lt("1.0.0", "2.0.0"); // true
semver.lt("1.0.0", "1.0.0"); // false
semver.lt("1.0.0-alpha", "1.0.0"); // true
semver.lt("1.0.0-rc.1", "1.0.0-rc.2"); // true
// Version sorting
const versions = ["2.1.0", "1.0.0", "2.0.0", "1.1.0"];
const sorted = versions.sort((a, b) => {
if (semver.gt(a, b)) return -1;
if (semver.lt(a, b)) return 1;
return 0;
});
console.log(sorted); // ["2.1.0", "2.0.0", "1.1.0", "1.0.0"]
// Find latest version
function findLatestVersion(versions) {
return versions.reduce((latest, current) => {
return semver.gt(current, latest) ? current : latest;
});
}
const latest = findLatestVersion(["1.0.0", "1.2.0", "1.1.5"]);
console.log(latest); // "1.2.0"Functions for working with semantic version ranges and checking satisfaction.
/**
* Check if version satisfies a version range
* @param version - Version to check
* @param range - Version range pattern
* @returns True if version satisfies the range
*/
satisfies(version: string, range: string): boolean;
/**
* Validate if string is a valid version range
* @param range - Range string to validate
* @returns True if valid range, false otherwise
*/
validRange(range: string): boolean | null;Range Validation Examples:
import { semver } from "gluegun";
// Exact version matching
semver.satisfies("1.2.3", "1.2.3"); // true
semver.satisfies("1.2.4", "1.2.3"); // false
// Caret ranges (compatible within major version)
semver.satisfies("1.2.3", "^1.0.0"); // true
semver.satisfies("1.9.9", "^1.0.0"); // true
semver.satisfies("2.0.0", "^1.0.0"); // false
// Tilde ranges (compatible within minor version)
semver.satisfies("1.2.3", "~1.2.0"); // true
semver.satisfies("1.2.9", "~1.2.0"); // true
semver.satisfies("1.3.0", "~1.2.0"); // false
// Greater than/less than ranges
semver.satisfies("2.0.0", ">1.0.0"); // true
semver.satisfies("2.0.0", ">=2.0.0"); // true
semver.satisfies("1.9.9", "<2.0.0"); // true
semver.satisfies("2.0.0", "<=2.0.0"); // true
// Complex ranges
semver.satisfies("1.5.0", ">=1.0.0 <2.0.0"); // true
semver.satisfies("1.5.0", "^1.0.0 || ^2.0.0"); // true
// Range validation
semver.validRange("^1.0.0"); // true
semver.validRange(">=1.0.0 <2.0.0"); // true
semver.validRange("invalid-range"); // false
// Dependency checking
function checkDependencyVersion(installedVersion, requiredRange) {
if (!semver.valid(installedVersion)) {
return { valid: false, error: "Invalid installed version" };
}
if (!semver.validRange(requiredRange)) {
return { valid: false, error: "Invalid version range" };
}
if (semver.satisfies(installedVersion, requiredRange)) {
return { valid: true, compatible: true };
} else {
return { valid: true, compatible: false, needsUpdate: true };
}
}
const result = checkDependencyVersion("1.2.3", "^1.0.0");
console.log(result); // { valid: true, compatible: true }Understanding different semver range patterns and their meanings.
Common Range Patterns:
import { semver } from "gluegun";
// Exact version
semver.satisfies("1.2.3", "1.2.3"); // Only 1.2.3
// Caret ranges (^) - compatible within major version
semver.satisfies("1.2.3", "^1.0.0"); // >=1.0.0 <2.0.0
semver.satisfies("1.9.9", "^1.0.0"); // true
semver.satisfies("2.0.0", "^1.0.0"); // false
// Tilde ranges (~) - compatible within minor version
semver.satisfies("1.2.3", "~1.2.0"); // >=1.2.0 <1.3.0
semver.satisfies("1.2.9", "~1.2.0"); // true
semver.satisfies("1.3.0", "~1.2.0"); // false
// X-ranges - wildcard matching
semver.satisfies("1.2.3", "1.2.x"); // 1.2.0, 1.2.1, 1.2.999...
semver.satisfies("1.2.3", "1.x"); // 1.0.0, 1.999.999...
// Comparison operators
semver.satisfies("2.0.0", ">1.0.0"); // Greater than
semver.satisfies("2.0.0", ">=1.0.0"); // Greater than or equal
semver.satisfies("1.0.0", "<2.0.0"); // Less than
semver.satisfies("1.0.0", "<=1.0.0"); // Less than or equal
// Hyphen ranges
semver.satisfies("1.5.0", "1.0.0 - 2.0.0"); // >=1.0.0 <=2.0.0
// Multiple ranges (OR)
semver.satisfies("1.5.0", "^1.0.0 || ^2.0.0"); // Either range
// Multiple constraints (AND)
semver.satisfies("1.5.0", ">=1.0.0 <2.0.0"); // Both constraintsinterface GluegunSemver {
/**
* Validate semantic version string
* @param version - Version to validate
* @returns Valid version or null
*/
valid(version: string): string | null;
/**
* Clean and normalize version string
* @param version - Version to clean
* @returns Cleaned version or null
*/
clean(version: string): string | null;
/**
* Check if version satisfies range
* @param version - Version to check
* @param range - Range pattern
* @returns True if version satisfies range
*/
satisfies(version: string, range: string): boolean;
/**
* Check if first version is greater than second
* @param version - First version
* @param other - Second version
* @returns True if first > second
*/
gt(version: string, other: string): boolean;
/**
* Check if first version is less than second
* @param version - First version
* @param other - Second version
* @returns True if first < second
*/
lt(version: string, other: string): boolean;
/**
* Validate version range string
* @param range - Range to validate
* @returns True if valid range
*/
validRange(range: string): boolean | null;
}Comprehensive Usage Example:
// CLI command for version management
export = {
name: "version",
description: "Manage project versions and dependencies",
run: async (toolbox) => {
const { semver, print, prompt, filesystem, parameters } = toolbox;
const action = parameters.first;
switch (action) {
case 'check':
await checkVersion(toolbox);
break;
case 'bump':
await bumpVersion(toolbox);
break;
case 'deps':
await checkDependencies(toolbox);
break;
case 'compare':
await compareVersions(toolbox);
break;
default:
await showCurrentVersion(toolbox);
}
}
};
async function showCurrentVersion(toolbox) {
const { semver, print, filesystem } = toolbox;
const pkg = filesystem.read('package.json', 'json');
if (!pkg) {
print.error('No package.json found');
return;
}
const currentVersion = pkg.version;
if (semver.valid(currentVersion)) {
print.success(`Current version: ${currentVersion}`);
} else {
print.error(`Invalid version in package.json: ${currentVersion}`);
}
}
async function bumpVersion(toolbox) {
const { semver, print, prompt, filesystem } = toolbox;
const pkg = filesystem.read('package.json', 'json');
if (!pkg) {
print.error('No package.json found');
return;
}
const currentVersion = pkg.version;
if (!semver.valid(currentVersion)) {
print.error(`Invalid current version: ${currentVersion}`);
return;
}
const bumpType = await prompt.ask({
type: 'select',
name: 'type',
message: 'Version bump type:',
choices: [
{ name: 'patch', message: 'Patch (1.0.0 → 1.0.1)' },
{ name: 'minor', message: 'Minor (1.0.0 → 1.1.0)' },
{ name: 'major', message: 'Major (1.0.0 → 2.0.0)' },
{ name: 'custom', message: 'Custom version' }
]
});
let newVersion;
if (bumpType.type === 'custom') {
const input = await prompt.ask({
type: 'input',
name: 'version',
message: 'Enter new version:',
validate: (v) => semver.valid(v) ? true : 'Invalid semantic version'
});
newVersion = input.version;
} else {
// Simulate version bumping (normally you'd use semver.inc)
const parts = currentVersion.split('.');
if (bumpType.type === 'patch') {
parts[2] = String(parseInt(parts[2]) + 1);
} else if (bumpType.type === 'minor') {
parts[1] = String(parseInt(parts[1]) + 1);
parts[2] = '0';
} else if (bumpType.type === 'major') {
parts[0] = String(parseInt(parts[0]) + 1);
parts[1] = '0';
parts[2] = '0';
}
newVersion = parts.join('.');
}
if (semver.gt(newVersion, currentVersion)) {
pkg.version = newVersion;
filesystem.write('package.json', pkg);
print.success(`Version bumped: ${currentVersion} → ${newVersion}`);
} else {
print.error('New version must be greater than current version');
}
}
async function checkDependencies(toolbox) {
const { semver, print, filesystem } = toolbox;
const pkg = filesystem.read('package.json', 'json');
if (!pkg) {
print.error('No package.json found');
return;
}
const dependencies = { ...pkg.dependencies, ...pkg.devDependencies };
const issues = [];
for (const [name, range] of Object.entries(dependencies)) {
if (!semver.validRange(range)) {
issues.push({
package: name,
range,
issue: 'Invalid version range'
});
}
}
if (issues.length > 0) {
print.warning('Dependency issues found:');
print.table([
['Package', 'Range', 'Issue'],
...issues.map(i => [i.package, i.range, i.issue])
]);
} else {
print.success('All dependency ranges are valid');
}
}
async function compareVersions(toolbox) {
const { semver, print, prompt } = toolbox;
const versions = await prompt.ask([
{
type: 'input',
name: 'version1',
message: 'First version:',
validate: (v) => semver.valid(v) ? true : 'Invalid version'
},
{
type: 'input',
name: 'version2',
message: 'Second version:',
validate: (v) => semver.valid(v) ? true : 'Invalid version'
}
]);
const v1 = versions.version1;
const v2 = versions.version2;
print.info(`Comparing ${v1} and ${v2}:`);
if (semver.gt(v1, v2)) {
print.success(`${v1} > ${v2}`);
} else if (semver.lt(v1, v2)) {
print.success(`${v1} < ${v2}`);
} else {
print.success(`${v1} = ${v2}`);
}
// Test range satisfaction
const range = await prompt.ask({
type: 'input',
name: 'range',
message: 'Test against range (optional):',
validate: (v) => !v || semver.validRange(v) ? true : 'Invalid range'
});
if (range.range) {
print.info(`Range satisfaction test: ${range.range}`);
print.info(`${v1} satisfies range: ${semver.satisfies(v1, range.range)}`);
print.info(`${v2} satisfies range: ${semver.satisfies(v2, range.range)}`);
}
}