ProvenPin Install

semver range checks and upgrade planning: working versions (tested 2026-10-15)

Use node-semver to check engine ranges, pick the highest satisfying release, classify patch, minor and major upgrades, coerce loose versions and handle prereleases.

passed5 passed · 0 failed2026-10-15 17:11 UTC
Status
passed
Last passed
2026-10-15 17:11 UTC
Tests
5 / 5
Language
TypeScript
Runtime
node v22.23.3
pinned versions4
semver@7.8.5@types/node@26.6.5tsx@4.23.15@types/semver@7.8.0
run it
npm install --no-audit --no-fund --ignore-scripts=false
npm test
test environment
node v22.23.3
linux 6.17.0-1022-azure
x64
runner: github-actions 38775053802

The code

Exactly the files that passed. Your agent can pull them with get_recipe("semver-range-checks") (install), or see them on GitHub.

package.json
{
  "name": "semver-range-checks",
  "private": true,
  "type": "module",
  "scripts": {
    "test": "node --import tsx --test test/*.test.ts"
  },
  "dependencies": {
    "semver": "7.8.5"
  },
  "devDependencies": {
    "@types/node": "26.6.5",
    "tsx": "4.23.15",
    "@types/semver": "7.8.0"
  }
}
src/versions.ts
import semver from "semver";

export function satisfiesEngine(version: string, range: string): boolean {
  const v = semver.coerce(version);
  return v !== null && semver.satisfies(v, range);
}

export type UpgradePlan = { current: string; patch: string | null; minor: string | null; major: string | null };

/** Latest stable release within patch, minor and major reach of the current version. */
export function planUpgrades(current: string, available: string[]): UpgradePlan {
  const stable = available.filter((v) => semver.valid(v) && !semver.prerelease(v));
  const newer = (range: string) => {
    const best = semver.maxSatisfying(stable, range);
    return best && semver.gt(best, current) ? best : null;
  };
  const major = semver.maxSatisfying(stable, `>=${current}`);
  return {
    current,
    patch: newer(`~${current}`),
    minor: newer(`^${current}`),
    major: major && semver.major(major) > semver.major(current) ? major : null,
  };
}

export function sortReleases(versions: string[]): string[] {
  return semver.sort(versions.filter((v) => semver.valid(v)));
}

export function bump(version: string, kind: semver.ReleaseType, preid?: string): string {
  const next = semver.inc(version, kind, preid);
  if (!next) throw new Error(`cannot bump ${version}`);
  return next;
}

export function describeRange(range: string): { valid: boolean; min: string | null } {
  const valid = semver.validRange(range) !== null;
  return { valid, min: valid ? semver.minVersion(range)?.version ?? null : null };
}
test/versions.test.ts
import { test } from "node:test";
import assert from "node:assert/strict";
import semver from "semver";
import { satisfiesEngine, planUpgrades, sortReleases, bump, describeRange } from "../src/versions.ts";

const releases = ["1.2.3", "1.2.9", "1.4.0", "1.5.0-beta.1", "2.0.0", "2.1.0", "3.0.0-rc.1"];

test("engine checks coerce loose versions like v22.11", () => {
  assert.equal(satisfiesEngine("v22.11", ">=20 <23"), true);
  assert.equal(satisfiesEngine("18.19.0", ">=20"), false);
  assert.equal(satisfiesEngine("garbage", ">=1"), false);
});

test("plans patch, minor and major upgrades ignoring prereleases", () => {
  assert.deepEqual(planUpgrades("1.2.3", releases), { current: "1.2.3", patch: "1.2.9", minor: "1.4.0", major: "2.1.0" });
  assert.deepEqual(planUpgrades("2.1.0", releases), { current: "2.1.0", patch: null, minor: null, major: null });
});

test("sorts and drops invalid versions", () => {
  assert.deepEqual(sortReleases(["2.0.0", "nope", "1.10.0", "1.2.0"]), ["1.2.0", "1.10.0", "2.0.0"]);
});

test("bumps including prerelease ids", () => {
  assert.equal(bump("1.2.3", "minor"), "1.3.0");
  assert.equal(bump("1.2.3", "prerelease", "rc"), "1.2.4-rc.0");
  assert.equal(bump("1.2.4-rc.0", "prerelease", "rc"), "1.2.4-rc.1");
});

test("ranges: validity, minimum version and prerelease opt-in", () => {
  assert.deepEqual(describeRange("^1.4.0 || ^2.0.0"), { valid: true, min: "1.4.0" });
  assert.equal(describeRange("not a range").valid, false);
  assert.equal(semver.satisfies("3.0.0-rc.1", ">=2"), false);
  assert.equal(semver.satisfies("3.0.0-rc.1", ">=2", { includePrerelease: true }), true);
});

Connect to ProvenPin

Add the free MCP server so your agent can pull this recipe and others like it. Same setup as the docs.

terminal
claude mcp add --transport http provenpin https://mcp.provenpin.com/mcp

Any other MCP client: Streamable HTTP at https://mcp.provenpin.com/mcp. No API key, no signup.