@forthtilliath/ts-kit 0.10.0 → 0.11.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 +72 -22
- package/dist/array/flattenDeep.d.ts +7 -5
- package/dist/array/flattenDeep.d.ts.map +1 -1
- package/dist/array/flattenDeep.js +8 -5
- package/dist/async/abortReason.d.ts +9 -0
- package/dist/async/abortReason.d.ts.map +1 -0
- package/dist/async/abortReason.js +11 -0
- package/dist/async/memoize.d.ts +2 -18
- package/dist/async/memoize.d.ts.map +1 -1
- package/dist/async/memoize.js +4 -25
- package/dist/async/retry.d.ts +15 -3
- package/dist/async/retry.d.ts.map +1 -1
- package/dist/async/retry.js +15 -11
- package/dist/async/sleep.d.ts +2 -1
- package/dist/async/sleep.d.ts.map +1 -1
- package/dist/async/sleep.js +23 -2
- package/dist/async/withTimeout.d.ts +11 -4
- package/dist/async/withTimeout.d.ts.map +1 -1
- package/dist/async/withTimeout.js +31 -9
- package/dist/csv/escapeCsvField.d.ts +21 -0
- package/dist/csv/escapeCsvField.d.ts.map +1 -0
- package/dist/csv/escapeCsvField.js +28 -0
- package/dist/csv/formatCsvNumber.d.ts +14 -0
- package/dist/csv/formatCsvNumber.d.ts.map +1 -0
- package/dist/csv/formatCsvNumber.js +15 -0
- package/dist/csv/index.d.ts +2 -0
- package/dist/csv/index.d.ts.map +1 -1
- package/dist/csv/index.js +2 -0
- package/dist/date/dateInTimeZone.d.ts +22 -0
- package/dist/date/dateInTimeZone.d.ts.map +1 -0
- package/dist/date/dateInTimeZone.js +35 -0
- package/dist/date/daysUntil.d.ts +15 -0
- package/dist/date/daysUntil.d.ts.map +1 -0
- package/dist/date/daysUntil.js +22 -0
- package/dist/date/index.d.ts +3 -0
- package/dist/date/index.d.ts.map +1 -1
- package/dist/date/index.js +3 -0
- package/dist/date/todayInTimeZone.d.ts +14 -0
- package/dist/date/todayInTimeZone.d.ts.map +1 -0
- package/dist/date/todayInTimeZone.js +15 -0
- package/dist/files/downloadText.d.ts.map +1 -1
- package/dist/files/downloadText.js +9 -1
- package/dist/function/index.d.ts +2 -0
- package/dist/function/index.d.ts.map +1 -0
- package/dist/function/index.js +1 -0
- package/dist/function/memoize.d.ts +34 -0
- package/dist/function/memoize.d.ts.map +1 -0
- package/dist/function/memoize.js +50 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/maths/average.d.ts +2 -7
- package/dist/maths/average.d.ts.map +1 -1
- package/dist/maths/average.js +4 -10
- package/dist/maths/sum.d.ts +2 -7
- package/dist/maths/sum.d.ts.map +1 -1
- package/dist/maths/sum.js +4 -9
- package/dist/number/average.d.ts +13 -0
- package/dist/number/average.d.ts.map +1 -0
- package/dist/number/average.js +15 -0
- package/dist/number/formatBytes.d.ts.map +1 -1
- package/dist/number/formatBytes.js +6 -2
- package/dist/number/formatCsvNumber.d.ts +2 -13
- package/dist/number/formatCsvNumber.d.ts.map +1 -1
- package/dist/number/formatCsvNumber.js +4 -15
- package/dist/number/index.d.ts +2 -0
- package/dist/number/index.d.ts.map +1 -1
- package/dist/number/index.js +2 -0
- package/dist/number/sum.d.ts +10 -0
- package/dist/number/sum.d.ts.map +1 -0
- package/dist/number/sum.js +11 -0
- package/dist/object/deepEqual.d.ts +6 -0
- package/dist/object/deepEqual.d.ts.map +1 -1
- package/dist/object/deepEqual.js +45 -11
- package/dist/string/escapeCsvField.d.ts +2 -13
- package/dist/string/escapeCsvField.d.ts.map +1 -1
- package/dist/string/escapeCsvField.js +4 -17
- package/dist/string/escapeHtml.d.ts +6 -4
- package/dist/string/escapeHtml.d.ts.map +1 -1
- package/dist/string/escapeHtml.js +8 -5
- package/dist/string/pluralize.d.ts +30 -7
- package/dist/string/pluralize.d.ts.map +1 -1
- package/dist/string/pluralize.js +48 -15
- package/dist/version/compareVersions.d.ts +8 -1
- package/dist/version/compareVersions.d.ts.map +1 -1
- package/dist/version/compareVersions.js +63 -9
- package/package.json +7 -6
package/dist/object/deepEqual.js
CHANGED
|
@@ -1,31 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Recursively compares two values for structural equality.
|
|
3
3
|
*
|
|
4
|
+
* Both values must share the same prototype (a plain object never equals an
|
|
5
|
+
* array or a class instance). `Date`s are compared by timestamp, `RegExp`s by
|
|
6
|
+
* source and flags, `Map`s by keys (identity) then values (deeply), and
|
|
7
|
+
* `Set`s by membership (identity, like `Set.prototype.has`).
|
|
8
|
+
*
|
|
4
9
|
* @param a - The first value.
|
|
5
10
|
* @param b - The second value.
|
|
6
11
|
* @returns `true` if `a` and `b` are deeply equal.
|
|
7
12
|
* @example
|
|
8
13
|
* deepEqual({ a: [1, 2] }, { a: [1, 2] }); // => true
|
|
9
14
|
* deepEqual({ a: 1 }, { a: 2 }); // => false
|
|
15
|
+
* deepEqual(new Date(0), new Date(0)); // => true
|
|
10
16
|
*/
|
|
11
17
|
export function deepEqual(a, b) {
|
|
12
18
|
if (Object.is(a, b))
|
|
13
19
|
return true;
|
|
14
|
-
if (typeof a !==
|
|
20
|
+
if (typeof a !== "object" ||
|
|
21
|
+
typeof b !== "object" ||
|
|
22
|
+
a === null ||
|
|
23
|
+
b === null ||
|
|
24
|
+
Object.getPrototypeOf(a) !== Object.getPrototypeOf(b)) {
|
|
15
25
|
return false;
|
|
16
|
-
|
|
17
|
-
|
|
26
|
+
}
|
|
27
|
+
if (a instanceof Date) {
|
|
28
|
+
return Object.is(a.getTime(), b.getTime());
|
|
29
|
+
}
|
|
30
|
+
if (a instanceof RegExp) {
|
|
31
|
+
const other = b;
|
|
32
|
+
return a.source === other.source && a.flags === other.flags;
|
|
33
|
+
}
|
|
34
|
+
if (a instanceof Map) {
|
|
35
|
+
const other = b;
|
|
36
|
+
if (a.size !== other.size)
|
|
18
37
|
return false;
|
|
19
|
-
|
|
38
|
+
for (const [key, value] of a) {
|
|
39
|
+
if (!other.has(key) || !deepEqual(value, other.get(key)))
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
if (a instanceof Set) {
|
|
45
|
+
const other = b;
|
|
46
|
+
if (a.size !== other.size)
|
|
20
47
|
return false;
|
|
21
|
-
|
|
48
|
+
for (const value of a) {
|
|
49
|
+
if (!other.has(value))
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
return true;
|
|
22
53
|
}
|
|
23
|
-
if (
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
if (aKeys.length !== bKeys.length)
|
|
54
|
+
if (Array.isArray(a)) {
|
|
55
|
+
const other = b;
|
|
56
|
+
if (a.length !== other.length)
|
|
27
57
|
return false;
|
|
28
|
-
return
|
|
58
|
+
return a.every((item, index) => deepEqual(item, other[index]));
|
|
29
59
|
}
|
|
30
|
-
|
|
60
|
+
const aKeys = Object.keys(a);
|
|
61
|
+
if (aKeys.length !== Object.keys(b).length)
|
|
62
|
+
return false;
|
|
63
|
+
return aKeys.every((key) => Object.prototype.hasOwnProperty.call(b, key) &&
|
|
64
|
+
deepEqual(a[key], b[key]));
|
|
31
65
|
}
|
|
@@ -1,14 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
|
|
3
|
-
* internal double quote if the value contains a double quote, a semicolon
|
|
4
|
-
* (the delimiter), or a newline — returned as-is otherwise.
|
|
5
|
-
*
|
|
6
|
-
* @param value - The raw field value.
|
|
7
|
-
* @returns The value, quoted and escaped only if needed.
|
|
8
|
-
* @example
|
|
9
|
-
* escapeCsvField("Apple"); // => "Apple"
|
|
10
|
-
* escapeCsvField("a;b"); // => '"a;b"'
|
|
11
|
-
* escapeCsvField('He said "hi"'); // => '"He said ""hi"""'
|
|
12
|
-
*/
|
|
13
|
-
export declare function escapeCsvField(value: string): string;
|
|
1
|
+
/** @deprecated Import from `@forthtilliath/ts-kit/csv/escapeCsvField` instead. */
|
|
2
|
+
export { escapeCsvField } from "../csv/escapeCsvField.js";
|
|
14
3
|
//# sourceMappingURL=escapeCsvField.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escapeCsvField.d.ts","sourceRoot":"","sources":["../../src/string/escapeCsvField.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"escapeCsvField.d.ts","sourceRoot":"","sources":["../../src/string/escapeCsvField.ts"],"names":[],"mappings":"AAGA,kFAAkF;AAClF,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC"}
|
|
@@ -1,17 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
* @param value - The raw field value.
|
|
7
|
-
* @returns The value, quoted and escaped only if needed.
|
|
8
|
-
* @example
|
|
9
|
-
* escapeCsvField("Apple"); // => "Apple"
|
|
10
|
-
* escapeCsvField("a;b"); // => '"a;b"'
|
|
11
|
-
* escapeCsvField('He said "hi"'); // => '"He said ""hi"""'
|
|
12
|
-
*/
|
|
13
|
-
export function escapeCsvField(value) {
|
|
14
|
-
if (!/[";\n]/.test(value))
|
|
15
|
-
return value;
|
|
16
|
-
return `"${value.replace(/"/g, '""')}"`;
|
|
17
|
-
}
|
|
1
|
+
// CSV helpers now live together in `csv/`. Re-exported so the
|
|
2
|
+
// `@forthtilliath/ts-kit/string/escapeCsvField` deep import keeps working.
|
|
3
|
+
/** @deprecated Import from `@forthtilliath/ts-kit/csv/escapeCsvField` instead. */
|
|
4
|
+
export { escapeCsvField } from "../csv/escapeCsvField.js";
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Escapes the HTML
|
|
3
|
-
*
|
|
4
|
-
* dependency.
|
|
2
|
+
* Escapes the HTML special characters (`&`, `<`, `>`, `"`, `'`) when
|
|
3
|
+
* inserting arbitrary text into an HTML template, without pulling in a
|
|
4
|
+
* templating dependency. Safe in text content and in attribute values,
|
|
5
|
+
* whether they're quoted with `"` or `'`.
|
|
5
6
|
*
|
|
6
7
|
* @param text - The text to escape.
|
|
7
|
-
* @returns `text` with `&`, `<`,
|
|
8
|
+
* @returns `text` with `&`, `<`, `>`, `"` and `'` replaced by HTML entities.
|
|
8
9
|
* @example
|
|
9
10
|
* escapeHtml("Tom & Jerry <script>"); // => "Tom & Jerry <script>"
|
|
11
|
+
* escapeHtml("it's"); // => "it's"
|
|
10
12
|
*/
|
|
11
13
|
export declare function escapeHtml(text: string): string;
|
|
12
14
|
//# sourceMappingURL=escapeHtml.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"escapeHtml.d.ts","sourceRoot":"","sources":["../../src/string/escapeHtml.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"escapeHtml.d.ts","sourceRoot":"","sources":["../../src/string/escapeHtml.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAO/C"}
|
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Escapes the HTML
|
|
3
|
-
*
|
|
4
|
-
* dependency.
|
|
2
|
+
* Escapes the HTML special characters (`&`, `<`, `>`, `"`, `'`) when
|
|
3
|
+
* inserting arbitrary text into an HTML template, without pulling in a
|
|
4
|
+
* templating dependency. Safe in text content and in attribute values,
|
|
5
|
+
* whether they're quoted with `"` or `'`.
|
|
5
6
|
*
|
|
6
7
|
* @param text - The text to escape.
|
|
7
|
-
* @returns `text` with `&`, `<`,
|
|
8
|
+
* @returns `text` with `&`, `<`, `>`, `"` and `'` replaced by HTML entities.
|
|
8
9
|
* @example
|
|
9
10
|
* escapeHtml("Tom & Jerry <script>"); // => "Tom & Jerry <script>"
|
|
11
|
+
* escapeHtml("it's"); // => "it's"
|
|
10
12
|
*/
|
|
11
13
|
export function escapeHtml(text) {
|
|
12
14
|
return text
|
|
13
15
|
.replace(/&/g, "&")
|
|
14
16
|
.replace(/</g, "<")
|
|
15
17
|
.replace(/>/g, ">")
|
|
16
|
-
.replace(/"/g, """)
|
|
18
|
+
.replace(/"/g, """)
|
|
19
|
+
.replace(/'/g, "'");
|
|
17
20
|
}
|
|
@@ -1,17 +1,40 @@
|
|
|
1
|
+
export interface PluralizeOptions {
|
|
2
|
+
/** An explicit plural form (overrides the default suffix rules). */
|
|
3
|
+
plural?: string;
|
|
4
|
+
/**
|
|
5
|
+
* The locale deciding which counts take the singular (defaults to `"en"`):
|
|
6
|
+
* only ±1 in English, but also 0 and 1.5 in French.
|
|
7
|
+
*/
|
|
8
|
+
locale?: string;
|
|
9
|
+
}
|
|
1
10
|
/**
|
|
2
|
-
* Pluralizes a word based on a count
|
|
3
|
-
*
|
|
4
|
-
*
|
|
11
|
+
* Pluralizes a word based on a count. Which counts take the singular follows
|
|
12
|
+
* the locale's plural rules (`Intl.PluralRules`). Without an explicit plural
|
|
13
|
+
* form, the default suffix is naive: in English `s`, `y` → `ies`, `es` after
|
|
14
|
+
* `s`/`x`/`z`/`sh`/`ch`; in French `s`, nothing after `s`/`x`/`z`; `s` in
|
|
15
|
+
* any other language.
|
|
5
16
|
*
|
|
6
17
|
* @param count - The count driving singular vs. plural.
|
|
7
18
|
* @param singular - The singular form.
|
|
8
|
-
* @param
|
|
9
|
-
* @returns
|
|
19
|
+
* @param options - An explicit plural form, or `{ plural?, locale? }`.
|
|
20
|
+
* @returns The singular or plural form.
|
|
10
21
|
* @example
|
|
11
22
|
* pluralize(1, "item"); // => "item"
|
|
12
|
-
* pluralize(3, "item"); // => "items"
|
|
13
23
|
* pluralize(3, "city"); // => "cities"
|
|
14
24
|
* pluralize(2, "child", "children"); // => "children"
|
|
25
|
+
* pluralize(0, "chant", { locale: "fr" }); // => "chant"
|
|
26
|
+
* pluralize(2, "journal", { plural: "journaux", locale: "fr" }); // => "journaux"
|
|
15
27
|
*/
|
|
16
|
-
export declare function pluralize(count: number, singular: string,
|
|
28
|
+
export declare function pluralize(count: number, singular: string, options?: string | PluralizeOptions): string;
|
|
29
|
+
/**
|
|
30
|
+
* Returns `pluralize` bound to a locale, to declare once per app.
|
|
31
|
+
*
|
|
32
|
+
* @param locale - The locale of every call.
|
|
33
|
+
* @returns `(count, singular, plural?) => string`.
|
|
34
|
+
* @example
|
|
35
|
+
* const plural = createPluralize("fr");
|
|
36
|
+
* plural(0, "chant"); // => "chant"
|
|
37
|
+
* plural(2, "est", "sont"); // => "sont"
|
|
38
|
+
*/
|
|
39
|
+
export declare function createPluralize(locale: string): (count: number, singular: string, plural?: string) => string;
|
|
17
40
|
//# sourceMappingURL=pluralize.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pluralize.d.ts","sourceRoot":"","sources":["../../src/string/pluralize.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"pluralize.d.ts","sourceRoot":"","sources":["../../src/string/pluralize.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,gBAAgB;IAC/B,oEAAoE;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAyBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GAAG,gBAAgB,GAClC,MAAM,CAKR;AAED;;;;;;;;;GASG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,IACpC,OAAO,MAAM,EAAE,UAAU,MAAM,EAAE,SAAS,MAAM,KAAG,MAAM,CAElE"}
|
package/dist/string/pluralize.js
CHANGED
|
@@ -1,26 +1,59 @@
|
|
|
1
|
+
const pluralRules = new Map();
|
|
2
|
+
function takesSingular(count, locale) {
|
|
3
|
+
let rules = pluralRules.get(locale);
|
|
4
|
+
if (!rules) {
|
|
5
|
+
rules = new Intl.PluralRules(locale);
|
|
6
|
+
pluralRules.set(locale, rules);
|
|
7
|
+
}
|
|
8
|
+
return rules.select(count) === "one";
|
|
9
|
+
}
|
|
10
|
+
function defaultPlural(singular, locale) {
|
|
11
|
+
const language = locale.split("-")[0]?.toLowerCase();
|
|
12
|
+
if (language === "fr") {
|
|
13
|
+
return /[sxz]$/i.test(singular) ? singular : `${singular}s`;
|
|
14
|
+
}
|
|
15
|
+
if (language === "en") {
|
|
16
|
+
if (/[sxz]$|[cs]h$/i.test(singular))
|
|
17
|
+
return `${singular}es`;
|
|
18
|
+
if (/[^aeiou]y$/i.test(singular))
|
|
19
|
+
return `${singular.slice(0, -1)}ies`;
|
|
20
|
+
}
|
|
21
|
+
return `${singular}s`;
|
|
22
|
+
}
|
|
1
23
|
/**
|
|
2
|
-
* Pluralizes a word based on a count
|
|
3
|
-
*
|
|
4
|
-
*
|
|
24
|
+
* Pluralizes a word based on a count. Which counts take the singular follows
|
|
25
|
+
* the locale's plural rules (`Intl.PluralRules`). Without an explicit plural
|
|
26
|
+
* form, the default suffix is naive: in English `s`, `y` → `ies`, `es` after
|
|
27
|
+
* `s`/`x`/`z`/`sh`/`ch`; in French `s`, nothing after `s`/`x`/`z`; `s` in
|
|
28
|
+
* any other language.
|
|
5
29
|
*
|
|
6
30
|
* @param count - The count driving singular vs. plural.
|
|
7
31
|
* @param singular - The singular form.
|
|
8
|
-
* @param
|
|
9
|
-
* @returns
|
|
32
|
+
* @param options - An explicit plural form, or `{ plural?, locale? }`.
|
|
33
|
+
* @returns The singular or plural form.
|
|
10
34
|
* @example
|
|
11
35
|
* pluralize(1, "item"); // => "item"
|
|
12
|
-
* pluralize(3, "item"); // => "items"
|
|
13
36
|
* pluralize(3, "city"); // => "cities"
|
|
14
37
|
* pluralize(2, "child", "children"); // => "children"
|
|
38
|
+
* pluralize(0, "chant", { locale: "fr" }); // => "chant"
|
|
39
|
+
* pluralize(2, "journal", { plural: "journaux", locale: "fr" }); // => "journaux"
|
|
15
40
|
*/
|
|
16
|
-
export function pluralize(count, singular,
|
|
17
|
-
|
|
41
|
+
export function pluralize(count, singular, options) {
|
|
42
|
+
const { plural, locale = "en" } = typeof options === "string" ? { plural: options } : (options ?? {});
|
|
43
|
+
if (takesSingular(count, locale))
|
|
18
44
|
return singular;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
45
|
+
return plural ?? defaultPlural(singular, locale);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Returns `pluralize` bound to a locale, to declare once per app.
|
|
49
|
+
*
|
|
50
|
+
* @param locale - The locale of every call.
|
|
51
|
+
* @returns `(count, singular, plural?) => string`.
|
|
52
|
+
* @example
|
|
53
|
+
* const plural = createPluralize("fr");
|
|
54
|
+
* plural(0, "chant"); // => "chant"
|
|
55
|
+
* plural(2, "est", "sont"); // => "sont"
|
|
56
|
+
*/
|
|
57
|
+
export function createPluralize(locale) {
|
|
58
|
+
return (count, singular, plural) => pluralize(count, singular, { plural, locale });
|
|
26
59
|
}
|
|
@@ -1,7 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Compares two "x.y.z"
|
|
2
|
+
* Compares two version strings following semver precedence: "x.y.z"
|
|
3
|
+
* segments first (missing ones count as `0`), then pre-release identifiers
|
|
4
|
+
* (`1.0.0-beta.2` < `1.0.0-beta.10` < `1.0.0`). A leading `v` and build
|
|
5
|
+
* metadata (`+build.1`) are ignored.
|
|
3
6
|
*
|
|
4
7
|
* @returns -1 if `a` < `b`, 0 if they're equal, 1 if `a` > `b`.
|
|
8
|
+
* @example
|
|
9
|
+
* compareVersions("1.10.0", "1.2.0"); // => 1
|
|
10
|
+
* compareVersions("1.0.0-beta", "1.0.0"); // => -1
|
|
11
|
+
* compareVersions("v2.0.0", "2.0.0"); // => 0
|
|
5
12
|
*/
|
|
6
13
|
export declare function compareVersions(a: string, b: string): number;
|
|
7
14
|
//# sourceMappingURL=compareVersions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compareVersions.d.ts","sourceRoot":"","sources":["../../src/version/compareVersions.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"compareVersions.d.ts","sourceRoot":"","sources":["../../src/version/compareVersions.ts"],"names":[],"mappings":"AA6CA;;;;;;;;;;;GAWG;AACH,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAe5D"}
|
|
@@ -1,17 +1,71 @@
|
|
|
1
|
+
function parseVersion(version) {
|
|
2
|
+
// Drop a leading "v" (git tags) and build metadata ("+build.1"), which
|
|
3
|
+
// semver ignores for precedence.
|
|
4
|
+
const [withoutBuild = ""] = version.trim().replace(/^v/i, "").split("+");
|
|
5
|
+
const dashIndex = withoutBuild.indexOf("-");
|
|
6
|
+
const core = dashIndex === -1 ? withoutBuild : withoutBuild.slice(0, dashIndex);
|
|
7
|
+
const prerelease = dashIndex === -1 ? "" : withoutBuild.slice(dashIndex + 1);
|
|
8
|
+
return {
|
|
9
|
+
core: core.split(".").map((part) => Number(part) || 0),
|
|
10
|
+
prerelease: prerelease === "" ? [] : prerelease.split("."),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function compareNumbers(a, b) {
|
|
14
|
+
if (a === b)
|
|
15
|
+
return 0;
|
|
16
|
+
return a > b ? 1 : -1;
|
|
17
|
+
}
|
|
18
|
+
// Semver rules: numeric identifiers compare numerically and rank below
|
|
19
|
+
// alphanumeric ones, which compare in ASCII order; with equal leading
|
|
20
|
+
// identifiers, the longer list ranks higher.
|
|
21
|
+
function comparePrerelease(a, b) {
|
|
22
|
+
const length = Math.max(a.length, b.length);
|
|
23
|
+
for (let i = 0; i < length; i++) {
|
|
24
|
+
const idA = a[i];
|
|
25
|
+
const idB = b[i];
|
|
26
|
+
if (idA === undefined)
|
|
27
|
+
return -1;
|
|
28
|
+
if (idB === undefined)
|
|
29
|
+
return 1;
|
|
30
|
+
if (idA === idB)
|
|
31
|
+
continue;
|
|
32
|
+
const numA = /^\d+$/.test(idA) ? Number(idA) : null;
|
|
33
|
+
const numB = /^\d+$/.test(idB) ? Number(idB) : null;
|
|
34
|
+
if (numA !== null && numB !== null)
|
|
35
|
+
return compareNumbers(numA, numB);
|
|
36
|
+
if (numA !== null)
|
|
37
|
+
return -1;
|
|
38
|
+
if (numB !== null)
|
|
39
|
+
return 1;
|
|
40
|
+
return idA > idB ? 1 : -1;
|
|
41
|
+
}
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
1
44
|
/**
|
|
2
|
-
* Compares two "x.y.z"
|
|
45
|
+
* Compares two version strings following semver precedence: "x.y.z"
|
|
46
|
+
* segments first (missing ones count as `0`), then pre-release identifiers
|
|
47
|
+
* (`1.0.0-beta.2` < `1.0.0-beta.10` < `1.0.0`). A leading `v` and build
|
|
48
|
+
* metadata (`+build.1`) are ignored.
|
|
3
49
|
*
|
|
4
50
|
* @returns -1 if `a` < `b`, 0 if they're equal, 1 if `a` > `b`.
|
|
51
|
+
* @example
|
|
52
|
+
* compareVersions("1.10.0", "1.2.0"); // => 1
|
|
53
|
+
* compareVersions("1.0.0-beta", "1.0.0"); // => -1
|
|
54
|
+
* compareVersions("v2.0.0", "2.0.0"); // => 0
|
|
5
55
|
*/
|
|
6
56
|
export function compareVersions(a, b) {
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const length = Math.max(
|
|
57
|
+
const parsedA = parseVersion(a);
|
|
58
|
+
const parsedB = parseVersion(b);
|
|
59
|
+
const length = Math.max(parsedA.core.length, parsedB.core.length);
|
|
10
60
|
for (let i = 0; i < length; i++) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
return numA > numB ? 1 : -1;
|
|
61
|
+
const result = compareNumbers(parsedA.core[i] ?? 0, parsedB.core[i] ?? 0);
|
|
62
|
+
if (result !== 0)
|
|
63
|
+
return result;
|
|
15
64
|
}
|
|
16
|
-
|
|
65
|
+
// A version without pre-release identifiers ranks above one that has some.
|
|
66
|
+
const isPrereleaseA = parsedA.prerelease.length > 0;
|
|
67
|
+
const isPrereleaseB = parsedB.prerelease.length > 0;
|
|
68
|
+
if (isPrereleaseA !== isPrereleaseB)
|
|
69
|
+
return isPrereleaseA ? -1 : 1;
|
|
70
|
+
return comparePrerelease(parsedA.prerelease, parsedB.prerelease);
|
|
17
71
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@forthtilliath/ts-kit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"type": "module",
|
|
5
|
+
"sideEffects": false,
|
|
5
6
|
"license": "MIT",
|
|
6
7
|
"publishConfig": {
|
|
7
8
|
"access": "public"
|
|
@@ -21,12 +22,12 @@
|
|
|
21
22
|
"./*": "./dist/*.js"
|
|
22
23
|
},
|
|
23
24
|
"devDependencies": {
|
|
24
|
-
"@vitest/coverage-v8": "
|
|
25
|
+
"@vitest/coverage-v8": "5.0.2",
|
|
25
26
|
"eslint": "10.7.0",
|
|
26
|
-
"jsdom": "^
|
|
27
|
+
"jsdom": "^30.1.1",
|
|
27
28
|
"typescript": "^5.9.3",
|
|
28
|
-
"vitest": "^
|
|
29
|
-
"@forthtilliath/eslint-config": "0.
|
|
29
|
+
"vitest": "^5.0.2",
|
|
30
|
+
"@forthtilliath/eslint-config": "0.8.0",
|
|
30
31
|
"@forthtilliath/typescript-config": "0.1.1"
|
|
31
32
|
},
|
|
32
33
|
"scripts": {
|
|
@@ -34,7 +35,7 @@
|
|
|
34
35
|
"build": "tsc -p tsconfig.build.json",
|
|
35
36
|
"check-types": "tsc --noEmit",
|
|
36
37
|
"lint": "eslint . --max-warnings 0",
|
|
37
|
-
"clean": "
|
|
38
|
+
"clean": "node -e \"require('node:fs').rmSync('dist', { recursive: true, force: true })\"",
|
|
38
39
|
"test": "vitest run",
|
|
39
40
|
"test:watch": "vitest",
|
|
40
41
|
"coverage": "vitest run --coverage"
|