semver.ts135 lines · main
1/**
2 * Semantic versioning utility for comparing version strings.
3 * Accepts 1-3 parts (e.g., "1", "1.5", "1.5.0"). Missing parts default to 0.
4 */
5
6export interface SemverVersion {
7 major: number
8 minor: number
9 patch: number
10}
11
12/**
13 * Parses a semver string into its components.
14 * Accepts 1-3 parts (e.g., "1", "1.5", "1.5.0"). Missing parts default to 0.
15 * @param version - The version string to parse (e.g., "1.2.3")
16 * @returns The parsed version components or null if invalid
17 */
18export function parseSemver(version: string): SemverVersion | null {
19 if (!version || typeof version !== 'string') {
20 return null
21 }
22
23 const parts = version.trim().split('.')
24
25 if (parts.length === 0 || parts.length > 3) {
26 return null
27 }
28
29 const numbers = parts.map((p) => parseInt(p, 10))
30
31 if (numbers.some(isNaN)) {
32 return null
33 }
34
35 if (numbers.some((n) => n < 0)) {
36 return null
37 }
38
39 return {
40 major: numbers[0],
41 minor: numbers[1] ?? 0,
42 patch: numbers[2] ?? 0,
43 }
44}
45
46/**
47 * Compares two semver version strings.
48 * Missing parts are treated as 0 (e.g., "1.5" equals "1.5.0").
49 * @param a - First version string
50 * @param b - Second version string
51 * @returns -1 if a < b, 0 if a === b, 1 if a > b, or null if either version is invalid
52 */
53export function compareSemver(a: string, b: string): -1 | 0 | 1 | null {
54 const versionA = parseSemver(a)
55 const versionB = parseSemver(b)
56
57 if (!versionA || !versionB) {
58 return null
59 }
60
61 if (versionA.major !== versionB.major) {
62 return versionA.major > versionB.major ? 1 : -1
63 }
64
65 if (versionA.minor !== versionB.minor) {
66 return versionA.minor > versionB.minor ? 1 : -1
67 }
68
69 if (versionA.patch !== versionB.patch) {
70 return versionA.patch > versionB.patch ? 1 : -1
71 }
72
73 return 0
74}
75
76/**
77 * Checks if version a is greater than version b
78 * @param a - First version string
79 * @param b - Second version string
80 * @returns true if a > b, false otherwise
81 */
82export function isGreaterThan(a: string, b: string): boolean {
83 return compareSemver(a, b) === 1
84}
85
86/**
87 * Checks if version a is less than version b
88 * @param a - First version string
89 * @param b - Second version string
90 * @returns true if a < b, false otherwise
91 */
92export function isLessThan(a: string, b: string): boolean {
93 return compareSemver(a, b) === -1
94}
95
96/**
97 * Checks if version a is equal to version b
98 * @param a - First version string
99 * @param b - Second version string
100 * @returns true if a === b, false otherwise
101 */
102export function isEqual(a: string, b: string): boolean {
103 return compareSemver(a, b) === 0
104}
105
106/**
107 * Checks if version a is greater than or equal to version b
108 * @param a - First version string
109 * @param b - Second version string
110 * @returns true if a >= b, false otherwise
111 */
112export function isGreaterThanOrEqual(a: string, b: string): boolean {
113 const result = compareSemver(a, b)
114 return result === 1 || result === 0
115}
116
117/**
118 * Checks if version a is less than or equal to version b
119 * @param a - First version string
120 * @param b - Second version string
121 * @returns true if a <= b, false otherwise
122 */
123export function isLessThanOrEqual(a: string, b: string): boolean {
124 const result = compareSemver(a, b)
125 return result === -1 || result === 0
126}
127
128/**
129 * Checks if a version string is valid
130 * @param version - The version string to validate
131 * @returns true if the version is valid, false otherwise
132 */
133export function isValidSemver(version: string): boolean {
134 return parseSemver(version) !== null
135}