authorgravatar for 132888838+arhxam@users.noreply.github.comArham Amin <132888838+arhxam@users.noreply.github.com> 2026-07-31 03:43:46+05:30
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-07-30 22:13:46+00:00
log063ed7120a17de65fcd5bd835b5c7c4e19536037
tree62b8b8245df82e548d4a27d209ca135b7e8b2a69
parent34b15a9acec5519425c81bdd2a10f11e9ba3d23d
signature Signed by PGP key B5690EEEBB952194

feat(shared): support shorthand (major-only) versions in the semver helpers (#5027)

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

2 files changed, 31 insertions(+), 2 deletions(-)

packages/shared/src/semver.test.ts+25-1
......@@ -1,6 +1,11 @@
11import { describe, expect, it } from "vite-plus/test";
22
3import { compareSemverVersions, normalizeSemverVersion, satisfiesSemverRange } from "./semver.ts";
3import {
4 compareSemverVersions,
5 normalizeSemverVersion,
6 parseSemver,
7 satisfiesSemverRange,
8} from "./semver.ts";
49
510describe("semver helpers", () => {
611 it("matches supported range groups", () => {
......@@ -18,6 +23,25 @@ describe("semver helpers", () => {
1823 expect(normalizeSemverVersion("2.1")).toBe("2.1.0");
1924 });
2025
26 it("normalizes and parses shorthand major-only versions", () => {
27 expect(normalizeSemverVersion("20")).toBe("20.0.0");
28 expect(normalizeSemverVersion("v18")).toBe("v18.0.0");
29 expect(normalizeSemverVersion("20-rc.1")).toBe("20.0.0-rc.1");
30 expect(parseSemver("20")).toEqual({ major: 20, minor: 0, patch: 0, prerelease: [] });
31 });
32
33 it("compares shorthand versions numerically instead of lexically", () => {
34 // Regression: "20" vs "9" previously fell back to string comparison, which
35 // ordered "20" before "9" ("2" < "9").
36 expect(compareSemverVersions("20", "9")).toBeGreaterThan(0);
37 expect(compareSemverVersions("18", "18.0.0")).toBe(0);
38 });
39
40 it("still rejects non-numeric shorthand and keeps empty input empty", () => {
41 expect(parseSemver("abc")).toBeNull();
42 expect(normalizeSemverVersion("")).toBe("");
43 });
44
2145 it("compares prerelease versions before stable versions", () => {
2246 expect(compareSemverVersions("2.1.111-beta.1", "2.1.111")).toBeLessThan(0);
2347 });
packages/shared/src/semver.ts+6-1
......@@ -17,7 +17,12 @@ export function normalizeSemverVersion(version: string): string {
1717 }
1818 }
1919
20 if (segments.length === 2) {
20 // Pad shorthand versions ("20" or "20.1") up to three segments so major-only
21 // and minor-only inputs parse and compare numerically. This matches
22 // satisfiesSemverRange, which already treats a missing minor/patch as 0. The
23 // length > 0 guard keeps empty/garbage input empty (parseSemver still
24 // rejects it), and inputs with more than three segments are left untouched.
25 while (segments.length > 0 && segments.length < 3) {
2126 segments.push("0");
2227 }
2328