CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-gluegun

A delightful toolkit for building Node-powered command-line interfaces (CLIs) with extensive tooling and plugin architecture.

Pending
Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Pending

The risk profile of this skill

Overview
Eval results
Files

semver-tools.mddocs/

Semantic Versioning

Semantic versioning utilities for version comparison, validation, range checking, and version manipulation following semver.org standards.

Capabilities

Version Validation

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}`);
}

Version Comparison

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"

Range Validation

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 }

Version Range Patterns

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 constraints

Complete Semver Interface

interface 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)}`);
  }
}

docs

cli-builder.md

command-system.md

filesystem-tools.md

http-tools.md

index.md

package-manager-tools.md

patching-tools.md

print-tools.md

prompt-tools.md

semver-tools.md

string-tools.md

system-tools.md

template-tools.md

tile.json