Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .bumpy/replace-semver-with-verkit.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@varlock/bumpy': patch
---

Replaced the `semver` dependency with [verkit](https://github.com/sxzz/verkit) — a smaller, tree-shakeable, zero-dependency SemVer library. No behavior changes, except invalid snapshot versions are now also refused (previously only stable versions were).
9 changes: 4 additions & 5 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 3 additions & 4 deletions packages/bumpy/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,21 +47,20 @@
"@types/bun": "latest",
"@types/js-yaml": "^4.0.9",
"@types/picomatch": "^4.0.3",
"@types/semver": "^7.7.0",
"js-yaml": "^4.1.0",
"picocolors": "^1.1.1",
"picomatch": "^4.0.4",
"semver": "^7.7.2",
"tsdown": "catalog:",
"typescript": "catalog:"
"typescript": "catalog:",
"verkit": "^0.4.0"
},
"bumpy": {
"releaseTriggeringDevDeps": [
"@clack/prompts",
"js-yaml",
"picocolors",
"picomatch",
"semver"
"verkit"
]
}
}
6 changes: 3 additions & 3 deletions packages/bumpy/src/commands/publish.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import semver from 'semver';
import { isPrerelease } from 'verkit';
import { log, colorize } from '../utils/logger.ts';
import { loadConfig } from '../core/config.ts';
import { discoverWorkspace } from '../core/workspace.ts';
Expand Down Expand Up @@ -107,7 +107,7 @@ export async function publishCommand(
// happen — committed versions are always stable — so a suffixed version here
// means something went wrong. Refuse loudly rather than publish it.
if (Object.keys(config.channels || {}).length > 0) {
const prereleases = toPublish.filter((r) => semver.prerelease(r.newVersion) !== null);
const prereleases = toPublish.filter((r) => isPrerelease(r.newVersion) === true);
if (prereleases.length > 0) {
log.error('Refusing to publish prerelease versions outside a channel:');
for (const r of prereleases) log.error(` • ${r.name}@${r.newVersion}`);
Expand Down Expand Up @@ -490,7 +490,7 @@ async function runPublishFlow(

try {
await createDraftRelease(tag, title, body, rootDir, headSha || undefined, {
prerelease: semver.prerelease(release.newVersion) !== null,
prerelease: isPrerelease(release.newVersion) === true,
});
log.dim(` Created draft release: ${title}`);
releaseMetadataByPkg.set(release.name, { tag, metadata, existingBody: body });
Expand Down
11 changes: 6 additions & 5 deletions packages/bumpy/src/core/prerelease.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { resolve } from 'node:path';
import semver from 'semver';
import { tryParse } from 'verkit';
import { readText, writeText, updateJsonFields, updateJsonNestedField } from '../utils/fs.ts';
import { runArgsAsync, tryRunArgs } from '../utils/shell.ts';
import { listTags } from './git.ts';
Expand Down Expand Up @@ -29,12 +29,13 @@ export interface PublishedPrereleaseState {
export function extractPrereleaseCounters(versions: string[], target: string, preid: string): number[] {
const counters: number[] = [];
for (const v of versions) {
const parsed = semver.parse(v);
const parsed = tryParse(v);
if (!parsed) continue;
if (`${parsed.major}.${parsed.minor}.${parsed.patch}` !== target) continue;
if (parsed.prerelease.length !== 2) continue;
if (parsed.prerelease[0] !== preid) continue;
const n = parsed.prerelease[1];
const prerelease = parsed.prerelease ?? [];
if (prerelease.length !== 2) continue;
if (prerelease[0] !== preid) continue;
const n = prerelease[1];
if (typeof n === 'number') counters.push(n);
}
return counters;
Expand Down
14 changes: 7 additions & 7 deletions packages/bumpy/src/core/semver.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import semver from 'semver';
import { compare, increment, isValid, satisfies as verkitSatisfies } from 'verkit';
import type { BumpType } from '../types.ts';

export function bumpVersion(version: string, type: BumpType): string {
const result = semver.inc(version, type);
const result = increment(version, type);
if (!result) throw new Error(`Failed to bump ${version} by ${type}`);
return result;
}
Expand All @@ -21,16 +21,16 @@ export function satisfies(version: string, range: string, currentVersion?: strin
if (cleanRange === '^' || cleanRange === '~') {
if (!currentVersion) return true; // can't resolve without current version
const resolved = `${cleanRange}${currentVersion}`;
return semver.satisfies(version, resolved);
return verkitSatisfies(version, resolved);
}
return semver.satisfies(version, cleanRange);
return verkitSatisfies(version, cleanRange);
}
// catalog: references can't be range-checked without catalog data,
// so treat them as always satisfied (don't trigger out-of-range bumps)
if (range.startsWith('catalog:')) return true;

if (!range || range === '*') return true;
return semver.satisfies(version, range);
return verkitSatisfies(version, range);
}

/** Strip workspace: protocol from version ranges */
Expand All @@ -40,9 +40,9 @@ export function stripProtocol(range: string): string {

/** Compare two versions: -1 if a < b, 0 if equal, 1 if a > b */
export function compareVersions(a: string, b: string): number {
return semver.compare(a, b);
return compare(a, b);
}

export function isValidVersion(version: string): boolean {
return semver.valid(version) !== null;
return isValid(version);
}
4 changes: 2 additions & 2 deletions packages/bumpy/src/core/snapshot.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import semver from 'semver';
import { isPrerelease } from 'verkit';
import { tryRunArgs } from '../utils/shell.ts';
import { fetchPublishedVersions, usesNpmRegistry } from './prerelease.ts';
import type { BumpyConfig, ReleasePlan, PlannedRelease, WorkspacePackage } from '../types.ts';
Expand Down Expand Up @@ -161,7 +161,7 @@ export function formatSnapshotVersionSummary(releases: PlannedRelease[]): string

/** Guard: a snapshot version must be a valid prerelease (never collides with a stable release) */
export function assertSnapshotPrerelease(version: string): void {
if (semver.prerelease(version) === null) {
if (isPrerelease(version) !== true) {
throw new Error(`Snapshot version "${version}" is not a prerelease — refusing to publish (would land on @latest).`);
}
}
12 changes: 6 additions & 6 deletions packages/bumpy/test/core/snapshot.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, test, expect, afterEach } from 'bun:test';
import semver from 'semver';
import { isLess, isPrerelease, isValid, normalize } from 'verkit';
import {
sanitizeSnapshotName,
resolveSnapshot,
Expand Down Expand Up @@ -28,7 +28,7 @@ describe('sanitizeSnapshotName', () => {

test('produces a valid semver prerelease identifier', () => {
const name = sanitizeSnapshotName('feature/Foo_Bar');
expect(semver.valid(`1.2.3-${name}.0`)).not.toBeNull();
expect(isValid(`1.2.3-${name}.0`)).toBe(true);
});

test('throws when nothing alphanumeric remains', () => {
Expand Down Expand Up @@ -94,19 +94,19 @@ describe('snapshotVersion', () => {
test('sha → <target>-<name>-<sha>', () => {
const v = snapshotVersion('1.4.0', base({ strategy: 'sha', suffix: 'a1b2c3d' }));
expect(v).toBe('1.4.0-pr-123-a1b2c3d');
expect(semver.valid(v)).toBe(v);
expect(semver.prerelease(v)).not.toBeNull();
expect(normalize(v)).toBe(v);
expect(isPrerelease(v)).toBe(true);
});

test('timestamp → <target>-<name>-<timestamp>', () => {
const v = snapshotVersion('2.0.0', base({ strategy: 'timestamp', suffix: '20260623123456' }));
expect(v).toBe('2.0.0-pr-123-20260623123456');
expect(semver.valid(v)).toBe(v);
expect(normalize(v)).toBe(v);
});

test('snapshot versions sort below their stable target', () => {
const v = snapshotVersion('1.4.0', base({}));
expect(semver.lt(v, '1.4.0')).toBe(true);
expect(isLess(v, '1.4.0')).toBe(true);
});
});

Expand Down
Loading