@guanghechen/version 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,168 @@
1
+ <header>
2
+ <h1 align="center">
3
+ <a href="https://github.com/guanghechen/sora/tree/@guanghechen/version@1.0.0/packages/version#readme">@guanghechen/version</a>
4
+ </h1>
5
+ <div align="center">
6
+ <a href="https://www.npmjs.com/package/@guanghechen/version">
7
+ <img
8
+ alt="Npm Version"
9
+ src="https://img.shields.io/npm/v/@guanghechen/version.svg"
10
+ />
11
+ </a>
12
+ <a href="https://www.npmjs.com/package/@guanghechen/version">
13
+ <img
14
+ alt="Npm Download"
15
+ src="https://img.shields.io/npm/dm/@guanghechen/version.svg"
16
+ />
17
+ </a>
18
+ <a href="https://www.npmjs.com/package/@guanghechen/version">
19
+ <img
20
+ alt="Npm License"
21
+ src="https://img.shields.io/npm/l/@guanghechen/version.svg"
22
+ />
23
+ </a>
24
+ <a href="#install">
25
+ <img
26
+ alt="Module Formats: cjs, esm"
27
+ src="https://img.shields.io/badge/module_formats-cjs%2C%20esm-green.svg"
28
+ />
29
+ </a>
30
+ <a href="https://github.com/nodejs/node">
31
+ <img
32
+ alt="Node.js Version"
33
+ src="https://img.shields.io/node/v/@guanghechen/version"
34
+ />
35
+ </a>
36
+ <a href="https://github.com/facebook/jest">
37
+ <img
38
+ alt="Tested with Jest"
39
+ src="https://img.shields.io/badge/tested_with-jest-9c465e.svg"
40
+ />
41
+ </a>
42
+ <a href="https://github.com/prettier/prettier">
43
+ <img
44
+ alt="Code Style: prettier"
45
+ src="https://img.shields.io/badge/code_style-prettier-ff69b4.svg?style=flat-square"
46
+ />
47
+ </a>
48
+ </div>
49
+ </header>
50
+ <br/>
51
+
52
+ Lightweight semver version comparison utilities.
53
+
54
+ ## Install
55
+
56
+ - npm
57
+
58
+ ```bash
59
+ npm install --save @guanghechen/version
60
+ ```
61
+
62
+ - yarn
63
+
64
+ ```bash
65
+ yarn add @guanghechen/version
66
+ ```
67
+
68
+ ## Usage
69
+
70
+ ### Parse and Compare Versions
71
+
72
+ ```typescript
73
+ import { parseVersion, compareVersions, compareSemVer } from '@guanghechen/version'
74
+
75
+ // Parse a version string
76
+ const v = parseVersion('1.2.3-alpha.1')
77
+ // => { major: 1, minor: 2, patch: 3, prerelease: ['alpha', 1] }
78
+
79
+ // Compare version strings
80
+ compareVersions('1.2.3', '1.2.4') // => -1
81
+ compareVersions('2.0.0', '1.9.9') // => 1
82
+ compareVersions('1.0.0', '1.0.0') // => 0
83
+
84
+ // Compare parsed versions
85
+ compareSemVer(
86
+ { major: 1, minor: 0, patch: 0, prerelease: [] },
87
+ { major: 2, minor: 0, patch: 0, prerelease: [] }
88
+ ) // => -1
89
+ ```
90
+
91
+ ### Check Version Ranges
92
+
93
+ ```typescript
94
+ import { satisfies } from '@guanghechen/version'
95
+
96
+ // Exact version
97
+ satisfies('1.2.3', '1.2.3') // => true
98
+
99
+ // Caret ranges (^)
100
+ satisfies('1.2.3', '^1.0.0') // => true
101
+ satisfies('2.0.0', '^1.0.0') // => false
102
+
103
+ // Tilde ranges (~)
104
+ satisfies('1.2.5', '~1.2.0') // => true
105
+ satisfies('1.3.0', '~1.2.0') // => false
106
+
107
+ // Comparison operators
108
+ satisfies('1.5.0', '>=1.0.0') // => true
109
+ satisfies('1.5.0', '<2.0.0') // => true
110
+ satisfies('1.5.0', '>1.0.0 <2.0.0') // => true
111
+
112
+ // X-ranges
113
+ satisfies('1.2.3', '1.x') // => true
114
+ satisfies('1.2.3', '1.2.x') // => true
115
+
116
+ // Hyphen ranges
117
+ satisfies('1.5.0', '1.0.0 - 2.0.0') // => true
118
+
119
+ // OR ranges
120
+ satisfies('3.0.0', '^1.0.0 || ^2.0.0 || ^3.0.0') // => true
121
+
122
+ // Prerelease versions (includePrerelease defaults to true)
123
+ satisfies('1.0.0-alpha', '^1.0.0') // => true
124
+
125
+ // Strict mode (node-semver compatible)
126
+ satisfies('1.0.0-alpha', '^1.0.0', { includePrerelease: false }) // => false
127
+ ```
128
+
129
+ ## API
130
+
131
+ ### Types
132
+
133
+ ```typescript
134
+ interface ISemVer {
135
+ major: number
136
+ minor: number
137
+ patch: number
138
+ prerelease: ReadonlyArray<string | number>
139
+ }
140
+
141
+ interface IPartialSemVer {
142
+ major: number
143
+ minor: number | undefined
144
+ patch: number | undefined
145
+ prerelease: ReadonlyArray<string | number>
146
+ isWildcard?: boolean
147
+ }
148
+
149
+ interface ISatisfiesOptions {
150
+ includePrerelease?: boolean // default: true
151
+ }
152
+ ```
153
+
154
+ ### Functions
155
+
156
+ | Function | Description |
157
+ | ----------------------------------------------------- | ---------------------------------------------- |
158
+ | `parseVersion(version: string)` | Parse a full semver string to `ISemVer` |
159
+ | `parsePartialVersion(version: string)` | Parse a partial version to `IPartialSemVer` |
160
+ | `compareVersions(v1: string, v2: string)` | Compare two version strings (-1, 0, 1) |
161
+ | `compareSemVer(v1: ISemVer, v2: ISemVer)` | Compare two parsed versions (-1, 0, 1) |
162
+ | `satisfies(version: string, range: string, options?)` | Check if version satisfies the range |
163
+ | `formatVersion(v: ISemVer)` | Format a parsed version back to string |
164
+ | `isFullVersion(v: IPartialSemVer)` | Check if partial version is a full version |
165
+ | `toFullVersion(v: IPartialSemVer)` | Convert partial version to full (fill with 0s) |
166
+
167
+ [homepage]:
168
+ https://github.com/guanghechen/sora/tree/@guanghechen/version@1.0.0/packages/version#readme
@@ -0,0 +1,322 @@
1
+ 'use strict';
2
+
3
+ const SEMVER_REGEX = /^[v=]?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
4
+ const PARTIAL_SEMVER_REGEX = /^[v=]?(\d+)(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
5
+ function isWildcardChar(s) {
6
+ return s === '*' || s === 'x' || s === 'X';
7
+ }
8
+ function parsePrerelease(prereleaseStr) {
9
+ if (!prereleaseStr)
10
+ return [];
11
+ return prereleaseStr.split('.').map(part => {
12
+ const num = parseInt(part, 10);
13
+ return String(num) === part ? num : part;
14
+ });
15
+ }
16
+ function parseVersion(version) {
17
+ const match = SEMVER_REGEX.exec(version);
18
+ if (!match)
19
+ return undefined;
20
+ return {
21
+ major: parseInt(match[1], 10),
22
+ minor: parseInt(match[2], 10),
23
+ patch: parseInt(match[3], 10),
24
+ prerelease: parsePrerelease(match[4]),
25
+ };
26
+ }
27
+ function parsePartialVersion(version) {
28
+ const trimmed = version.trim();
29
+ if (trimmed === '' || isWildcardChar(trimmed)) {
30
+ return { major: 0, minor: undefined, patch: undefined, prerelease: [], isWildcard: true };
31
+ }
32
+ const match = PARTIAL_SEMVER_REGEX.exec(trimmed);
33
+ if (!match)
34
+ return undefined;
35
+ const minorStr = match[2];
36
+ const patchStr = match[3];
37
+ return {
38
+ major: parseInt(match[1], 10),
39
+ minor: minorStr === undefined || isWildcardChar(minorStr) ? undefined : parseInt(minorStr, 10),
40
+ patch: patchStr === undefined || isWildcardChar(patchStr) ? undefined : parseInt(patchStr, 10),
41
+ prerelease: parsePrerelease(match[4]),
42
+ };
43
+ }
44
+ function isFullVersion(v) {
45
+ return v.minor !== undefined && v.patch !== undefined;
46
+ }
47
+ function toFullVersion(v) {
48
+ return {
49
+ major: v.major,
50
+ minor: v.minor ?? 0,
51
+ patch: v.patch ?? 0,
52
+ prerelease: v.prerelease,
53
+ };
54
+ }
55
+ function formatVersion(v) {
56
+ const base = `${v.major}.${v.minor}.${v.patch}`;
57
+ return v.prerelease.length === 0 ? base : `${base}-${v.prerelease.join('.')}`;
58
+ }
59
+
60
+ function comparePrerelease(a, b) {
61
+ if (a.length === 0 && b.length === 0)
62
+ return 0;
63
+ if (a.length === 0)
64
+ return 1;
65
+ if (b.length === 0)
66
+ return -1;
67
+ const len = Math.max(a.length, b.length);
68
+ for (let i = 0; i < len; i++) {
69
+ if (i >= a.length)
70
+ return -1;
71
+ if (i >= b.length)
72
+ return 1;
73
+ const aVal = a[i];
74
+ const bVal = b[i];
75
+ if (aVal === bVal)
76
+ continue;
77
+ const aIsNum = typeof aVal === 'number';
78
+ const bIsNum = typeof bVal === 'number';
79
+ if (aIsNum !== bIsNum)
80
+ return aIsNum ? -1 : 1;
81
+ return aVal < bVal ? -1 : 1;
82
+ }
83
+ return 0;
84
+ }
85
+ function compareSemVer(v1, v2) {
86
+ if (v1.major !== v2.major)
87
+ return v1.major < v2.major ? -1 : 1;
88
+ if (v1.minor !== v2.minor)
89
+ return v1.minor < v2.minor ? -1 : 1;
90
+ if (v1.patch !== v2.patch)
91
+ return v1.patch < v2.patch ? -1 : 1;
92
+ return comparePrerelease(v1.prerelease, v2.prerelease);
93
+ }
94
+ function compareVersions(v1, v2) {
95
+ const parsed1 = parseVersion(v1);
96
+ const parsed2 = parseVersion(v2);
97
+ if (!parsed1 || !parsed2) {
98
+ throw new Error(`Invalid version: ${!parsed1 ? v1 : v2}`);
99
+ }
100
+ return compareSemVer(parsed1, parsed2);
101
+ }
102
+
103
+ const HYPHEN_RANGE_REGEX = /^\s*(\S+)\s+-\s+(\S+)\s*$/;
104
+ const COMPARATOR_REGEX = /^(>=|>|<=|<|=|\^|~)?(.*)$/;
105
+ function createComparator(target, op) {
106
+ return (v) => {
107
+ const cmp = compareSemVer(v, target);
108
+ if (op === 'gt')
109
+ return cmp > 0;
110
+ if (op === 'gte')
111
+ return cmp >= 0;
112
+ if (op === 'lt')
113
+ return cmp < 0;
114
+ if (op === 'lte')
115
+ return cmp <= 0;
116
+ return cmp === 0;
117
+ };
118
+ }
119
+ function createAndComparator(comparators) {
120
+ return (v) => comparators.every(c => c(v));
121
+ }
122
+ function createSemVer(major, minor, patch, prerelease = []) {
123
+ return { major, minor, patch, prerelease: [...prerelease] };
124
+ }
125
+ function withResult(comparator, prereleaseVersions) {
126
+ return { comparator, prereleaseVersions };
127
+ }
128
+ function hasSameBaseVersion(v1, v2) {
129
+ return v1.major === v2.major && v1.minor === v2.minor && v1.patch === v2.patch;
130
+ }
131
+ function parseHyphenRange(range) {
132
+ const match = HYPHEN_RANGE_REGEX.exec(range);
133
+ if (!match)
134
+ return undefined;
135
+ const start = parsePartialVersion(match[1]);
136
+ const end = parsePartialVersion(match[2]);
137
+ if (!start || !end)
138
+ return undefined;
139
+ const startFull = toFullVersion(start);
140
+ const comparators = [createComparator(startFull, 'gte')];
141
+ const prereleaseVersions = [];
142
+ if (start.prerelease.length > 0)
143
+ prereleaseVersions.push(startFull);
144
+ if (isFullVersion(end)) {
145
+ comparators.push(createComparator(end, 'lte'));
146
+ if (end.prerelease.length > 0)
147
+ prereleaseVersions.push(end);
148
+ }
149
+ else {
150
+ const endMax = end.minor === undefined
151
+ ? createSemVer(end.major + 1, 0, 0, [0])
152
+ : createSemVer(end.major, end.minor + 1, 0, [0]);
153
+ comparators.push(createComparator(endMax, 'lt'));
154
+ }
155
+ return [withResult(createAndComparator(comparators), prereleaseVersions)];
156
+ }
157
+ function parseXRange(partial) {
158
+ if (partial.minor === undefined) {
159
+ if (partial.isWildcard)
160
+ return withResult(() => true, []);
161
+ const min = createSemVer(partial.major, 0, 0, partial.prerelease);
162
+ const max = createSemVer(partial.major + 1, 0, 0, [0]);
163
+ return withResult(createAndComparator([createComparator(min, 'gte'), createComparator(max, 'lt')]), partial.prerelease.length > 0 ? [min] : []);
164
+ }
165
+ const min = createSemVer(partial.major, partial.minor, 0, partial.prerelease);
166
+ const max = createSemVer(partial.major, partial.minor + 1, 0, [0]);
167
+ return withResult(createAndComparator([createComparator(min, 'gte'), createComparator(max, 'lt')]), partial.prerelease.length > 0 ? [min] : []);
168
+ }
169
+ function parseCaretRange(partial) {
170
+ const min = toFullVersion(partial);
171
+ const hasPrerelease = partial.prerelease.length > 0;
172
+ let max;
173
+ if (partial.minor === undefined) {
174
+ max = createSemVer(min.major + 1, 0, 0, [0]);
175
+ }
176
+ else if (partial.patch === undefined) {
177
+ if (min.major === 0 && min.minor === 0) {
178
+ max = createSemVer(0, 1, 0, [0]);
179
+ }
180
+ else if (min.major === 0) {
181
+ max = createSemVer(0, min.minor + 1, 0, [0]);
182
+ }
183
+ else {
184
+ max = createSemVer(min.major + 1, 0, 0, [0]);
185
+ }
186
+ }
187
+ else if (min.major > 0) {
188
+ max = createSemVer(min.major + 1, 0, 0, [0]);
189
+ }
190
+ else if (min.minor > 0) {
191
+ max = createSemVer(0, min.minor + 1, 0, [0]);
192
+ }
193
+ else {
194
+ max = createSemVer(0, 0, min.patch + 1, [0]);
195
+ }
196
+ return withResult(createAndComparator([createComparator(min, 'gte'), createComparator(max, 'lt')]), hasPrerelease ? [min] : []);
197
+ }
198
+ function parseTildeRange(partial) {
199
+ const min = toFullVersion(partial);
200
+ const hasPrerelease = partial.prerelease.length > 0;
201
+ const max = partial.minor === undefined
202
+ ? createSemVer(min.major + 1, 0, 0, [0])
203
+ : createSemVer(min.major, min.minor + 1, 0, [0]);
204
+ return withResult(createAndComparator([createComparator(min, 'gte'), createComparator(max, 'lt')]), hasPrerelease ? [min] : []);
205
+ }
206
+ function parseComparatorWithOperator(operator, partial) {
207
+ const hasPrerelease = partial.prerelease.length > 0;
208
+ switch (operator) {
209
+ case '':
210
+ case '=': {
211
+ if (!isFullVersion(partial))
212
+ return parseXRange(partial);
213
+ return withResult(createComparator(partial, 'eq'), hasPrerelease ? [partial] : []);
214
+ }
215
+ case '>': {
216
+ if (!isFullVersion(partial)) {
217
+ const target = partial.minor === undefined
218
+ ? createSemVer(partial.major + 1, 0, 0)
219
+ : createSemVer(partial.major, partial.minor + 1, 0);
220
+ return withResult(createComparator(target, 'gte'), hasPrerelease ? [toFullVersion(partial)] : []);
221
+ }
222
+ return withResult(createComparator(partial, 'gt'), hasPrerelease ? [partial] : []);
223
+ }
224
+ case '>=': {
225
+ const full = toFullVersion(partial);
226
+ return withResult(createComparator(full, 'gte'), hasPrerelease ? [full] : []);
227
+ }
228
+ case '<': {
229
+ if (!isFullVersion(partial)) {
230
+ const target = toFullVersion(partial);
231
+ const targetWithPrerelease = createSemVer(target.major, target.minor, target.patch, [0]);
232
+ return withResult(createComparator(targetWithPrerelease, 'lt'), hasPrerelease ? [target] : []);
233
+ }
234
+ return withResult(createComparator(partial, 'lt'), hasPrerelease ? [partial] : []);
235
+ }
236
+ case '<=': {
237
+ if (!isFullVersion(partial)) {
238
+ const target = partial.minor === undefined
239
+ ? createSemVer(partial.major + 1, 0, 0, [0])
240
+ : createSemVer(partial.major, partial.minor + 1, 0, [0]);
241
+ return withResult(createComparator(target, 'lt'), hasPrerelease ? [toFullVersion(partial)] : []);
242
+ }
243
+ return withResult(createComparator(partial, 'lte'), hasPrerelease ? [partial] : []);
244
+ }
245
+ case '^':
246
+ return parseCaretRange(partial);
247
+ case '~':
248
+ return parseTildeRange(partial);
249
+ }
250
+ }
251
+ const OPERATORS = ['>=', '>', '<=', '<', '=', '^', '~', ''];
252
+ function isOperator(s) {
253
+ return OPERATORS.includes(s);
254
+ }
255
+ function parseSingleComparator(comp) {
256
+ const trimmed = comp.trim();
257
+ if (trimmed === '' || trimmed === '*' || trimmed === 'x' || trimmed === 'X') {
258
+ return withResult(() => true, []);
259
+ }
260
+ const match = COMPARATOR_REGEX.exec(trimmed);
261
+ if (!match)
262
+ return undefined;
263
+ const operator = match[1] || '';
264
+ const versionStr = match[2];
265
+ if (!versionStr) {
266
+ return operator === '' ? withResult(() => true, []) : undefined;
267
+ }
268
+ const partial = parsePartialVersion(versionStr);
269
+ if (!partial || !isOperator(operator))
270
+ return undefined;
271
+ return parseComparatorWithOperator(operator, partial);
272
+ }
273
+ function parseComparatorSet(set) {
274
+ const hyphenResult = parseHyphenRange(set);
275
+ if (hyphenResult)
276
+ return hyphenResult;
277
+ const parts = set.trim().split(/\s+/);
278
+ const results = [];
279
+ for (const part of parts) {
280
+ if (part === '')
281
+ continue;
282
+ const parsed = parseSingleComparator(part);
283
+ if (!parsed)
284
+ return undefined;
285
+ results.push(parsed);
286
+ }
287
+ return results.length === 0 ? [withResult(() => true, [])] : results;
288
+ }
289
+ function checkPrereleaseAllowed(version, prereleaseVersions) {
290
+ if (version.prerelease.length === 0)
291
+ return true;
292
+ return prereleaseVersions.some(pv => hasSameBaseVersion(version, pv));
293
+ }
294
+ function satisfies(version, range, options) {
295
+ const parsed = parseVersion(version);
296
+ if (!parsed)
297
+ return false;
298
+ const includePrerelease = options?.includePrerelease ?? true;
299
+ for (const orPart of range.split('||')) {
300
+ const comparatorSet = parseComparatorSet(orPart);
301
+ if (!comparatorSet)
302
+ continue;
303
+ const allMatch = comparatorSet.every(c => c.comparator(parsed));
304
+ if (!allMatch)
305
+ continue;
306
+ if (includePrerelease)
307
+ return true;
308
+ const prereleaseVersions = comparatorSet.flatMap(c => c.prereleaseVersions);
309
+ if (checkPrereleaseAllowed(parsed, prereleaseVersions))
310
+ return true;
311
+ }
312
+ return false;
313
+ }
314
+
315
+ exports.compareSemVer = compareSemVer;
316
+ exports.compareVersions = compareVersions;
317
+ exports.formatVersion = formatVersion;
318
+ exports.isFullVersion = isFullVersion;
319
+ exports.parsePartialVersion = parsePartialVersion;
320
+ exports.parseVersion = parseVersion;
321
+ exports.satisfies = satisfies;
322
+ exports.toFullVersion = toFullVersion;
@@ -0,0 +1,313 @@
1
+ const SEMVER_REGEX = /^[v=]?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
2
+ const PARTIAL_SEMVER_REGEX = /^[v=]?(\d+)(?:\.(\d+|[xX*]))?(?:\.(\d+|[xX*]))?(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
3
+ function isWildcardChar(s) {
4
+ return s === '*' || s === 'x' || s === 'X';
5
+ }
6
+ function parsePrerelease(prereleaseStr) {
7
+ if (!prereleaseStr)
8
+ return [];
9
+ return prereleaseStr.split('.').map(part => {
10
+ const num = parseInt(part, 10);
11
+ return String(num) === part ? num : part;
12
+ });
13
+ }
14
+ function parseVersion(version) {
15
+ const match = SEMVER_REGEX.exec(version);
16
+ if (!match)
17
+ return undefined;
18
+ return {
19
+ major: parseInt(match[1], 10),
20
+ minor: parseInt(match[2], 10),
21
+ patch: parseInt(match[3], 10),
22
+ prerelease: parsePrerelease(match[4]),
23
+ };
24
+ }
25
+ function parsePartialVersion(version) {
26
+ const trimmed = version.trim();
27
+ if (trimmed === '' || isWildcardChar(trimmed)) {
28
+ return { major: 0, minor: undefined, patch: undefined, prerelease: [], isWildcard: true };
29
+ }
30
+ const match = PARTIAL_SEMVER_REGEX.exec(trimmed);
31
+ if (!match)
32
+ return undefined;
33
+ const minorStr = match[2];
34
+ const patchStr = match[3];
35
+ return {
36
+ major: parseInt(match[1], 10),
37
+ minor: minorStr === undefined || isWildcardChar(minorStr) ? undefined : parseInt(minorStr, 10),
38
+ patch: patchStr === undefined || isWildcardChar(patchStr) ? undefined : parseInt(patchStr, 10),
39
+ prerelease: parsePrerelease(match[4]),
40
+ };
41
+ }
42
+ function isFullVersion(v) {
43
+ return v.minor !== undefined && v.patch !== undefined;
44
+ }
45
+ function toFullVersion(v) {
46
+ return {
47
+ major: v.major,
48
+ minor: v.minor ?? 0,
49
+ patch: v.patch ?? 0,
50
+ prerelease: v.prerelease,
51
+ };
52
+ }
53
+ function formatVersion(v) {
54
+ const base = `${v.major}.${v.minor}.${v.patch}`;
55
+ return v.prerelease.length === 0 ? base : `${base}-${v.prerelease.join('.')}`;
56
+ }
57
+
58
+ function comparePrerelease(a, b) {
59
+ if (a.length === 0 && b.length === 0)
60
+ return 0;
61
+ if (a.length === 0)
62
+ return 1;
63
+ if (b.length === 0)
64
+ return -1;
65
+ const len = Math.max(a.length, b.length);
66
+ for (let i = 0; i < len; i++) {
67
+ if (i >= a.length)
68
+ return -1;
69
+ if (i >= b.length)
70
+ return 1;
71
+ const aVal = a[i];
72
+ const bVal = b[i];
73
+ if (aVal === bVal)
74
+ continue;
75
+ const aIsNum = typeof aVal === 'number';
76
+ const bIsNum = typeof bVal === 'number';
77
+ if (aIsNum !== bIsNum)
78
+ return aIsNum ? -1 : 1;
79
+ return aVal < bVal ? -1 : 1;
80
+ }
81
+ return 0;
82
+ }
83
+ function compareSemVer(v1, v2) {
84
+ if (v1.major !== v2.major)
85
+ return v1.major < v2.major ? -1 : 1;
86
+ if (v1.minor !== v2.minor)
87
+ return v1.minor < v2.minor ? -1 : 1;
88
+ if (v1.patch !== v2.patch)
89
+ return v1.patch < v2.patch ? -1 : 1;
90
+ return comparePrerelease(v1.prerelease, v2.prerelease);
91
+ }
92
+ function compareVersions(v1, v2) {
93
+ const parsed1 = parseVersion(v1);
94
+ const parsed2 = parseVersion(v2);
95
+ if (!parsed1 || !parsed2) {
96
+ throw new Error(`Invalid version: ${!parsed1 ? v1 : v2}`);
97
+ }
98
+ return compareSemVer(parsed1, parsed2);
99
+ }
100
+
101
+ const HYPHEN_RANGE_REGEX = /^\s*(\S+)\s+-\s+(\S+)\s*$/;
102
+ const COMPARATOR_REGEX = /^(>=|>|<=|<|=|\^|~)?(.*)$/;
103
+ function createComparator(target, op) {
104
+ return (v) => {
105
+ const cmp = compareSemVer(v, target);
106
+ if (op === 'gt')
107
+ return cmp > 0;
108
+ if (op === 'gte')
109
+ return cmp >= 0;
110
+ if (op === 'lt')
111
+ return cmp < 0;
112
+ if (op === 'lte')
113
+ return cmp <= 0;
114
+ return cmp === 0;
115
+ };
116
+ }
117
+ function createAndComparator(comparators) {
118
+ return (v) => comparators.every(c => c(v));
119
+ }
120
+ function createSemVer(major, minor, patch, prerelease = []) {
121
+ return { major, minor, patch, prerelease: [...prerelease] };
122
+ }
123
+ function withResult(comparator, prereleaseVersions) {
124
+ return { comparator, prereleaseVersions };
125
+ }
126
+ function hasSameBaseVersion(v1, v2) {
127
+ return v1.major === v2.major && v1.minor === v2.minor && v1.patch === v2.patch;
128
+ }
129
+ function parseHyphenRange(range) {
130
+ const match = HYPHEN_RANGE_REGEX.exec(range);
131
+ if (!match)
132
+ return undefined;
133
+ const start = parsePartialVersion(match[1]);
134
+ const end = parsePartialVersion(match[2]);
135
+ if (!start || !end)
136
+ return undefined;
137
+ const startFull = toFullVersion(start);
138
+ const comparators = [createComparator(startFull, 'gte')];
139
+ const prereleaseVersions = [];
140
+ if (start.prerelease.length > 0)
141
+ prereleaseVersions.push(startFull);
142
+ if (isFullVersion(end)) {
143
+ comparators.push(createComparator(end, 'lte'));
144
+ if (end.prerelease.length > 0)
145
+ prereleaseVersions.push(end);
146
+ }
147
+ else {
148
+ const endMax = end.minor === undefined
149
+ ? createSemVer(end.major + 1, 0, 0, [0])
150
+ : createSemVer(end.major, end.minor + 1, 0, [0]);
151
+ comparators.push(createComparator(endMax, 'lt'));
152
+ }
153
+ return [withResult(createAndComparator(comparators), prereleaseVersions)];
154
+ }
155
+ function parseXRange(partial) {
156
+ if (partial.minor === undefined) {
157
+ if (partial.isWildcard)
158
+ return withResult(() => true, []);
159
+ const min = createSemVer(partial.major, 0, 0, partial.prerelease);
160
+ const max = createSemVer(partial.major + 1, 0, 0, [0]);
161
+ return withResult(createAndComparator([createComparator(min, 'gte'), createComparator(max, 'lt')]), partial.prerelease.length > 0 ? [min] : []);
162
+ }
163
+ const min = createSemVer(partial.major, partial.minor, 0, partial.prerelease);
164
+ const max = createSemVer(partial.major, partial.minor + 1, 0, [0]);
165
+ return withResult(createAndComparator([createComparator(min, 'gte'), createComparator(max, 'lt')]), partial.prerelease.length > 0 ? [min] : []);
166
+ }
167
+ function parseCaretRange(partial) {
168
+ const min = toFullVersion(partial);
169
+ const hasPrerelease = partial.prerelease.length > 0;
170
+ let max;
171
+ if (partial.minor === undefined) {
172
+ max = createSemVer(min.major + 1, 0, 0, [0]);
173
+ }
174
+ else if (partial.patch === undefined) {
175
+ if (min.major === 0 && min.minor === 0) {
176
+ max = createSemVer(0, 1, 0, [0]);
177
+ }
178
+ else if (min.major === 0) {
179
+ max = createSemVer(0, min.minor + 1, 0, [0]);
180
+ }
181
+ else {
182
+ max = createSemVer(min.major + 1, 0, 0, [0]);
183
+ }
184
+ }
185
+ else if (min.major > 0) {
186
+ max = createSemVer(min.major + 1, 0, 0, [0]);
187
+ }
188
+ else if (min.minor > 0) {
189
+ max = createSemVer(0, min.minor + 1, 0, [0]);
190
+ }
191
+ else {
192
+ max = createSemVer(0, 0, min.patch + 1, [0]);
193
+ }
194
+ return withResult(createAndComparator([createComparator(min, 'gte'), createComparator(max, 'lt')]), hasPrerelease ? [min] : []);
195
+ }
196
+ function parseTildeRange(partial) {
197
+ const min = toFullVersion(partial);
198
+ const hasPrerelease = partial.prerelease.length > 0;
199
+ const max = partial.minor === undefined
200
+ ? createSemVer(min.major + 1, 0, 0, [0])
201
+ : createSemVer(min.major, min.minor + 1, 0, [0]);
202
+ return withResult(createAndComparator([createComparator(min, 'gte'), createComparator(max, 'lt')]), hasPrerelease ? [min] : []);
203
+ }
204
+ function parseComparatorWithOperator(operator, partial) {
205
+ const hasPrerelease = partial.prerelease.length > 0;
206
+ switch (operator) {
207
+ case '':
208
+ case '=': {
209
+ if (!isFullVersion(partial))
210
+ return parseXRange(partial);
211
+ return withResult(createComparator(partial, 'eq'), hasPrerelease ? [partial] : []);
212
+ }
213
+ case '>': {
214
+ if (!isFullVersion(partial)) {
215
+ const target = partial.minor === undefined
216
+ ? createSemVer(partial.major + 1, 0, 0)
217
+ : createSemVer(partial.major, partial.minor + 1, 0);
218
+ return withResult(createComparator(target, 'gte'), hasPrerelease ? [toFullVersion(partial)] : []);
219
+ }
220
+ return withResult(createComparator(partial, 'gt'), hasPrerelease ? [partial] : []);
221
+ }
222
+ case '>=': {
223
+ const full = toFullVersion(partial);
224
+ return withResult(createComparator(full, 'gte'), hasPrerelease ? [full] : []);
225
+ }
226
+ case '<': {
227
+ if (!isFullVersion(partial)) {
228
+ const target = toFullVersion(partial);
229
+ const targetWithPrerelease = createSemVer(target.major, target.minor, target.patch, [0]);
230
+ return withResult(createComparator(targetWithPrerelease, 'lt'), hasPrerelease ? [target] : []);
231
+ }
232
+ return withResult(createComparator(partial, 'lt'), hasPrerelease ? [partial] : []);
233
+ }
234
+ case '<=': {
235
+ if (!isFullVersion(partial)) {
236
+ const target = partial.minor === undefined
237
+ ? createSemVer(partial.major + 1, 0, 0, [0])
238
+ : createSemVer(partial.major, partial.minor + 1, 0, [0]);
239
+ return withResult(createComparator(target, 'lt'), hasPrerelease ? [toFullVersion(partial)] : []);
240
+ }
241
+ return withResult(createComparator(partial, 'lte'), hasPrerelease ? [partial] : []);
242
+ }
243
+ case '^':
244
+ return parseCaretRange(partial);
245
+ case '~':
246
+ return parseTildeRange(partial);
247
+ }
248
+ }
249
+ const OPERATORS = ['>=', '>', '<=', '<', '=', '^', '~', ''];
250
+ function isOperator(s) {
251
+ return OPERATORS.includes(s);
252
+ }
253
+ function parseSingleComparator(comp) {
254
+ const trimmed = comp.trim();
255
+ if (trimmed === '' || trimmed === '*' || trimmed === 'x' || trimmed === 'X') {
256
+ return withResult(() => true, []);
257
+ }
258
+ const match = COMPARATOR_REGEX.exec(trimmed);
259
+ if (!match)
260
+ return undefined;
261
+ const operator = match[1] || '';
262
+ const versionStr = match[2];
263
+ if (!versionStr) {
264
+ return operator === '' ? withResult(() => true, []) : undefined;
265
+ }
266
+ const partial = parsePartialVersion(versionStr);
267
+ if (!partial || !isOperator(operator))
268
+ return undefined;
269
+ return parseComparatorWithOperator(operator, partial);
270
+ }
271
+ function parseComparatorSet(set) {
272
+ const hyphenResult = parseHyphenRange(set);
273
+ if (hyphenResult)
274
+ return hyphenResult;
275
+ const parts = set.trim().split(/\s+/);
276
+ const results = [];
277
+ for (const part of parts) {
278
+ if (part === '')
279
+ continue;
280
+ const parsed = parseSingleComparator(part);
281
+ if (!parsed)
282
+ return undefined;
283
+ results.push(parsed);
284
+ }
285
+ return results.length === 0 ? [withResult(() => true, [])] : results;
286
+ }
287
+ function checkPrereleaseAllowed(version, prereleaseVersions) {
288
+ if (version.prerelease.length === 0)
289
+ return true;
290
+ return prereleaseVersions.some(pv => hasSameBaseVersion(version, pv));
291
+ }
292
+ function satisfies(version, range, options) {
293
+ const parsed = parseVersion(version);
294
+ if (!parsed)
295
+ return false;
296
+ const includePrerelease = options?.includePrerelease ?? true;
297
+ for (const orPart of range.split('||')) {
298
+ const comparatorSet = parseComparatorSet(orPart);
299
+ if (!comparatorSet)
300
+ continue;
301
+ const allMatch = comparatorSet.every(c => c.comparator(parsed));
302
+ if (!allMatch)
303
+ continue;
304
+ if (includePrerelease)
305
+ return true;
306
+ const prereleaseVersions = comparatorSet.flatMap(c => c.prereleaseVersions);
307
+ if (checkPrereleaseAllowed(parsed, prereleaseVersions))
308
+ return true;
309
+ }
310
+ return false;
311
+ }
312
+
313
+ export { compareSemVer, compareVersions, formatVersion, isFullVersion, parsePartialVersion, parseVersion, satisfies, toFullVersion };
@@ -0,0 +1,41 @@
1
+ interface ISemVer {
2
+ major: number;
3
+ minor: number;
4
+ patch: number;
5
+ prerelease: ReadonlyArray<string | number>;
6
+ }
7
+ interface IPartialSemVer {
8
+ major: number;
9
+ minor: number | undefined;
10
+ patch: number | undefined;
11
+ prerelease: ReadonlyArray<string | number>;
12
+ isWildcard?: boolean;
13
+ }
14
+ type IComparator = (version: ISemVer) => boolean;
15
+ interface IComparatorWithPrerelease {
16
+ comparator: IComparator;
17
+ prereleaseVersions: ISemVer[];
18
+ }
19
+ interface ISatisfiesOptions {
20
+ /**
21
+ * Include prerelease versions in the comparison.
22
+ * When true, prerelease versions will match ranges even if the range
23
+ * doesn't include a prerelease tag.
24
+ * @default true (for compatibility with compare-versions)
25
+ */
26
+ includePrerelease?: boolean;
27
+ }
28
+
29
+ declare function compareSemVer(v1: ISemVer, v2: ISemVer): -1 | 0 | 1;
30
+ declare function compareVersions(v1: string, v2: string): -1 | 0 | 1;
31
+
32
+ declare function parseVersion(version: string): ISemVer | undefined;
33
+ declare function parsePartialVersion(version: string): IPartialSemVer | undefined;
34
+ declare function isFullVersion(v: IPartialSemVer): v is ISemVer;
35
+ declare function toFullVersion(v: IPartialSemVer): ISemVer;
36
+ declare function formatVersion(v: ISemVer): string;
37
+
38
+ declare function satisfies(version: string, range: string, options?: ISatisfiesOptions): boolean;
39
+
40
+ export { compareSemVer, compareVersions, formatVersion, isFullVersion, parsePartialVersion, parseVersion, satisfies, toFullVersion };
41
+ export type { IComparator, IComparatorWithPrerelease, IPartialSemVer, ISatisfiesOptions, ISemVer };
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@guanghechen/version",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight semver version comparison utilities.",
5
+ "author": {
6
+ "name": "guanghechen",
7
+ "url": "https://github.com/guanghechen/"
8
+ },
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "https://github.com/guanghechen/sora/tree/@guanghechen/version@0.0.1",
12
+ "directory": "packages/version"
13
+ },
14
+ "homepage": "https://github.com/guanghechen/sora/tree/@guanghechen/version@0.0.1/packages/version#readme",
15
+ "keywords": [
16
+ "semver",
17
+ "version",
18
+ "compare"
19
+ ],
20
+ "type": "module",
21
+ "exports": {
22
+ ".": {
23
+ "source": "./src/index.ts",
24
+ "import": "./lib/esm/index.mjs",
25
+ "require": "./lib/cjs/index.cjs",
26
+ "types": "./lib/types/index.d.ts"
27
+ }
28
+ },
29
+ "source": "./src/index.ts",
30
+ "main": "./lib/cjs/index.cjs",
31
+ "module": "./lib/esm/index.mjs",
32
+ "types": "./lib/types/index.d.ts",
33
+ "license": "MIT",
34
+ "scripts": {
35
+ "build": "rollup -c ../../rollup.config.mjs",
36
+ "clean": "rimraf lib",
37
+ "test": "vitest run --config ../../vitest.config.ts",
38
+ "test:coverage": "vitest run --config ../../vitest.config.ts --coverage",
39
+ "test:update": "vitest run --config ../../vitest.config.ts -u"
40
+ },
41
+ "files": [
42
+ "lib/",
43
+ "!lib/**/*.map",
44
+ "package.json",
45
+ "CHANGELOG.md",
46
+ "LICENSE",
47
+ "README.md"
48
+ ],
49
+ "gitHead": "12990a720b31d50d217e2e17a6191256dc94eda6"
50
+ }