aboutsummaryrefslogtreecommitdiffhomepage
path: root/shared/core-utils/common
diff options
context:
space:
mode:
authorChocobozzz <me@florianbigard.com>2023-07-31 14:34:36 +0200
committerChocobozzz <me@florianbigard.com>2023-08-11 15:02:33 +0200
commit3a4992633ee62d5edfbb484d9c6bcb3cf158489d (patch)
treee4510b39bdac9c318fdb4b47018d08f15368b8f0 /shared/core-utils/common
parent04d1da5621d25d59bd5fa1543b725c497bf5d9a8 (diff)
downloadPeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.gz
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.tar.zst
PeerTube-3a4992633ee62d5edfbb484d9c6bcb3cf158489d.zip
Migrate server to ESM
Sorry for the very big commit that may lead to git log issues and merge conflicts, but it's a major step forward: * Server can be faster at startup because imports() are async and we can easily lazy import big modules * Angular doesn't seem to support ES import (with .js extension), so we had to correctly organize peertube into a monorepo: * Use yarn workspace feature * Use typescript reference projects for dependencies * Shared projects have been moved into "packages", each one is now a node module (with a dedicated package.json/tsconfig.json) * server/tools have been moved into apps/ and is now a dedicated app bundled and published on NPM so users don't have to build peertube cli tools manually * server/tests have been moved into packages/ so we don't compile them every time we want to run the server * Use isolatedModule option: * Had to move from const enum to const (https://www.typescriptlang.org/docs/handbook/enums.html#objects-vs-enums) * Had to explictely specify "type" imports when used in decorators * Prefer tsx (that uses esbuild under the hood) instead of ts-node to load typescript files (tests with mocha or scripts): * To reduce test complexity as esbuild doesn't support decorator metadata, we only test server files that do not import server models * We still build tests files into js files for a faster CI * Remove unmaintained peertube CLI import script * Removed some barrels to speed up execution (less imports)
Diffstat (limited to 'shared/core-utils/common')
-rw-r--r--shared/core-utils/common/array.ts41
-rw-r--r--shared/core-utils/common/date.ts114
-rw-r--r--shared/core-utils/common/env.ts46
-rw-r--r--shared/core-utils/common/index.ts12
-rw-r--r--shared/core-utils/common/number.ts13
-rw-r--r--shared/core-utils/common/object.ts86
-rw-r--r--shared/core-utils/common/path.ts48
-rw-r--r--shared/core-utils/common/promises.ts58
-rw-r--r--shared/core-utils/common/random.ts8
-rw-r--r--shared/core-utils/common/regexp.ts5
-rw-r--r--shared/core-utils/common/time.ts7
-rw-r--r--shared/core-utils/common/url.ts150
-rw-r--r--shared/core-utils/common/version.ts11
13 files changed, 0 insertions, 599 deletions
diff --git a/shared/core-utils/common/array.ts b/shared/core-utils/common/array.ts
deleted file mode 100644
index 878ed1ffe..000000000
--- a/shared/core-utils/common/array.ts
+++ /dev/null
@@ -1,41 +0,0 @@
1function findCommonElement <T> (array1: T[], array2: T[]) {
2 for (const a of array1) {
3 for (const b of array2) {
4 if (a === b) return a
5 }
6 }
7
8 return null
9}
10
11// Avoid conflict with other toArray() functions
12function arrayify <T> (element: T | T[]) {
13 if (Array.isArray(element)) return element
14
15 return [ element ]
16}
17
18// Avoid conflict with other uniq() functions
19function uniqify <T> (elements: T[]) {
20 return Array.from(new Set(elements))
21}
22
23// Thanks: https://stackoverflow.com/a/12646864
24function shuffle <T> (elements: T[]) {
25 const shuffled = [ ...elements ]
26
27 for (let i = shuffled.length - 1; i > 0; i--) {
28 const j = Math.floor(Math.random() * (i + 1));
29
30 [ shuffled[i], shuffled[j] ] = [ shuffled[j], shuffled[i] ]
31 }
32
33 return shuffled
34}
35
36export {
37 uniqify,
38 findCommonElement,
39 shuffle,
40 arrayify
41}
diff --git a/shared/core-utils/common/date.ts b/shared/core-utils/common/date.ts
deleted file mode 100644
index f0684ff86..000000000
--- a/shared/core-utils/common/date.ts
+++ /dev/null
@@ -1,114 +0,0 @@
1function isToday (d: Date) {
2 const today = new Date()
3
4 return areDatesEqual(d, today)
5}
6
7function isYesterday (d: Date) {
8 const yesterday = new Date()
9 yesterday.setDate(yesterday.getDate() - 1)
10
11 return areDatesEqual(d, yesterday)
12}
13
14function isThisWeek (d: Date) {
15 const minDateOfThisWeek = new Date()
16 minDateOfThisWeek.setHours(0, 0, 0)
17
18 // getDay() -> Sunday - Saturday : 0 - 6
19 // We want to start our week on Monday
20 let dayOfWeek = minDateOfThisWeek.getDay() - 1
21 if (dayOfWeek < 0) dayOfWeek = 6 // Sunday
22
23 minDateOfThisWeek.setDate(minDateOfThisWeek.getDate() - dayOfWeek)
24
25 return d >= minDateOfThisWeek
26}
27
28function isThisMonth (d: Date) {
29 const thisMonth = new Date().getMonth()
30
31 return d.getMonth() === thisMonth
32}
33
34function isLastMonth (d: Date) {
35 const now = new Date()
36
37 return getDaysDifferences(now, d) <= 30
38}
39
40function isLastWeek (d: Date) {
41 const now = new Date()
42
43 return getDaysDifferences(now, d) <= 7
44}
45
46// ---------------------------------------------------------------------------
47
48function timeToInt (time: number | string) {
49 if (!time) return 0
50 if (typeof time === 'number') return time
51
52 const reg = /^((\d+)[h:])?((\d+)[m:])?((\d+)s?)?$/
53 const matches = time.match(reg)
54
55 if (!matches) return 0
56
57 const hours = parseInt(matches[2] || '0', 10)
58 const minutes = parseInt(matches[4] || '0', 10)
59 const seconds = parseInt(matches[6] || '0', 10)
60
61 return hours * 3600 + minutes * 60 + seconds
62}
63
64function secondsToTime (seconds: number, full = false, symbol?: string) {
65 let time = ''
66
67 if (seconds === 0 && !full) return '0s'
68
69 const hourSymbol = (symbol || 'h')
70 const minuteSymbol = (symbol || 'm')
71 const secondsSymbol = full ? '' : 's'
72
73 const hours = Math.floor(seconds / 3600)
74 if (hours >= 1) time = hours + hourSymbol
75 else if (full) time = '0' + hourSymbol
76
77 seconds %= 3600
78 const minutes = Math.floor(seconds / 60)
79 if (minutes >= 1 && minutes < 10 && full) time += '0' + minutes + minuteSymbol
80 else if (minutes >= 1) time += minutes + minuteSymbol
81 else if (full) time += '00' + minuteSymbol
82
83 seconds %= 60
84 if (seconds >= 1 && seconds < 10 && full) time += '0' + seconds + secondsSymbol
85 else if (seconds >= 1) time += seconds + secondsSymbol
86 else if (full) time += '00'
87
88 return time
89}
90
91// ---------------------------------------------------------------------------
92
93export {
94 isYesterday,
95 isThisWeek,
96 isThisMonth,
97 isToday,
98 isLastMonth,
99 isLastWeek,
100 timeToInt,
101 secondsToTime
102}
103
104// ---------------------------------------------------------------------------
105
106function areDatesEqual (d1: Date, d2: Date) {
107 return d1.getFullYear() === d2.getFullYear() &&
108 d1.getMonth() === d2.getMonth() &&
109 d1.getDate() === d2.getDate()
110}
111
112function getDaysDifferences (d1: Date, d2: Date) {
113 return (d1.getTime() - d2.getTime()) / (86400000)
114}
diff --git a/shared/core-utils/common/env.ts b/shared/core-utils/common/env.ts
deleted file mode 100644
index 973f895d4..000000000
--- a/shared/core-utils/common/env.ts
+++ /dev/null
@@ -1,46 +0,0 @@
1function parallelTests () {
2 return process.env.MOCHA_PARALLEL === 'true'
3}
4
5function isGithubCI () {
6 return !!process.env.GITHUB_WORKSPACE
7}
8
9function areHttpImportTestsDisabled () {
10 const disabled = process.env.DISABLE_HTTP_IMPORT_TESTS === 'true'
11
12 if (disabled) console.log('DISABLE_HTTP_IMPORT_TESTS env set to "true" so import tests are disabled')
13
14 return disabled
15}
16
17function areMockObjectStorageTestsDisabled () {
18 const disabled = process.env.ENABLE_OBJECT_STORAGE_TESTS !== 'true'
19
20 if (disabled) console.log('ENABLE_OBJECT_STORAGE_TESTS env is not set to "true" so object storage tests are disabled')
21
22 return disabled
23}
24
25function areScalewayObjectStorageTestsDisabled () {
26 if (areMockObjectStorageTestsDisabled()) return true
27
28 const enabled = process.env.OBJECT_STORAGE_SCALEWAY_KEY_ID && process.env.OBJECT_STORAGE_SCALEWAY_ACCESS_KEY
29 if (!enabled) {
30 console.log(
31 'OBJECT_STORAGE_SCALEWAY_KEY_ID and/or OBJECT_STORAGE_SCALEWAY_ACCESS_KEY are not set, so scaleway object storage tests are disabled'
32 )
33
34 return true
35 }
36
37 return false
38}
39
40export {
41 parallelTests,
42 isGithubCI,
43 areHttpImportTestsDisabled,
44 areMockObjectStorageTestsDisabled,
45 areScalewayObjectStorageTestsDisabled
46}
diff --git a/shared/core-utils/common/index.ts b/shared/core-utils/common/index.ts
deleted file mode 100644
index 8d63ee1b2..000000000
--- a/shared/core-utils/common/index.ts
+++ /dev/null
@@ -1,12 +0,0 @@
1export * from './array'
2export * from './random'
3export * from './date'
4export * from './env'
5export * from './number'
6export * from './object'
7export * from './path'
8export * from './regexp'
9export * from './time'
10export * from './promises'
11export * from './url'
12export * from './version'
diff --git a/shared/core-utils/common/number.ts b/shared/core-utils/common/number.ts
deleted file mode 100644
index ce5a6041a..000000000
--- a/shared/core-utils/common/number.ts
+++ /dev/null
@@ -1,13 +0,0 @@
1export function forceNumber (value: any) {
2 return parseInt(value + '')
3}
4
5export function isOdd (num: number) {
6 return (num % 2) !== 0
7}
8
9export function toEven (num: number) {
10 if (isOdd(num)) return num + 1
11
12 return num
13}
diff --git a/shared/core-utils/common/object.ts b/shared/core-utils/common/object.ts
deleted file mode 100644
index 1276bfcc7..000000000
--- a/shared/core-utils/common/object.ts
+++ /dev/null
@@ -1,86 +0,0 @@
1function pick <O extends object, K extends keyof O> (object: O, keys: K[]): Pick<O, K> {
2 const result: any = {}
3
4 for (const key of keys) {
5 if (Object.prototype.hasOwnProperty.call(object, key)) {
6 result[key] = object[key]
7 }
8 }
9
10 return result
11}
12
13function omit <O extends object, K extends keyof O> (object: O, keys: K[]): Exclude<O, K> {
14 const result: any = {}
15 const keysSet = new Set(keys) as Set<string>
16
17 for (const [ key, value ] of Object.entries(object)) {
18 if (keysSet.has(key)) continue
19
20 result[key] = value
21 }
22
23 return result
24}
25
26function objectKeysTyped <O extends object, K extends keyof O> (object: O): K[] {
27 return (Object.keys(object) as K[])
28}
29
30function getKeys <O extends object, K extends keyof O> (object: O, keys: K[]): K[] {
31 return (Object.keys(object) as K[]).filter(k => keys.includes(k))
32}
33
34function hasKey <T extends object> (obj: T, k: keyof any): k is keyof T {
35 return k in obj
36}
37
38function sortObjectComparator (key: string, order: 'asc' | 'desc') {
39 return (a: any, b: any) => {
40 if (a[key] < b[key]) {
41 return order === 'asc' ? -1 : 1
42 }
43
44 if (a[key] > b[key]) {
45 return order === 'asc' ? 1 : -1
46 }
47
48 return 0
49 }
50}
51
52function shallowCopy <T> (o: T): T {
53 return Object.assign(Object.create(Object.getPrototypeOf(o)), o)
54}
55
56function simpleObjectsDeepEqual (a: any, b: any) {
57 if (a === b) return true
58
59 if (typeof a !== 'object' || typeof b !== 'object' || a === null || b === null) {
60 return false
61 }
62
63 const keysA = Object.keys(a)
64 const keysB = Object.keys(b)
65
66 if (keysA.length !== keysB.length) return false
67
68 for (const key of keysA) {
69 if (!keysB.includes(key)) return false
70
71 if (!simpleObjectsDeepEqual(a[key], b[key])) return false
72 }
73
74 return true
75}
76
77export {
78 pick,
79 omit,
80 objectKeysTyped,
81 getKeys,
82 hasKey,
83 shallowCopy,
84 sortObjectComparator,
85 simpleObjectsDeepEqual
86}
diff --git a/shared/core-utils/common/path.ts b/shared/core-utils/common/path.ts
deleted file mode 100644
index 006505316..000000000
--- a/shared/core-utils/common/path.ts
+++ /dev/null
@@ -1,48 +0,0 @@
1import { basename, extname, isAbsolute, join, resolve } from 'path'
2
3let rootPath: string
4
5function root () {
6 if (rootPath) return rootPath
7
8 rootPath = __dirname
9
10 if (basename(rootPath) === 'tools') rootPath = resolve(rootPath, '..')
11 if (basename(rootPath) === 'scripts') rootPath = resolve(rootPath, '..')
12 if (basename(rootPath) === 'common') rootPath = resolve(rootPath, '..')
13 if (basename(rootPath) === 'core-utils') rootPath = resolve(rootPath, '..')
14 if (basename(rootPath) === 'shared') rootPath = resolve(rootPath, '..')
15 if (basename(rootPath) === 'server') rootPath = resolve(rootPath, '..')
16 if (basename(rootPath) === 'dist') rootPath = resolve(rootPath, '..')
17
18 return rootPath
19}
20
21function buildPath (path: string) {
22 if (isAbsolute(path)) return path
23
24 return join(root(), path)
25}
26
27function getLowercaseExtension (filename: string) {
28 const ext = extname(filename) || ''
29
30 return ext.toLowerCase()
31}
32
33function buildAbsoluteFixturePath (path: string, customCIPath = false) {
34 if (isAbsolute(path)) return path
35
36 if (customCIPath && process.env.GITHUB_WORKSPACE) {
37 return join(process.env.GITHUB_WORKSPACE, 'fixtures', path)
38 }
39
40 return join(root(), 'server', 'tests', 'fixtures', path)
41}
42
43export {
44 root,
45 buildPath,
46 buildAbsoluteFixturePath,
47 getLowercaseExtension
48}
diff --git a/shared/core-utils/common/promises.ts b/shared/core-utils/common/promises.ts
deleted file mode 100644
index e3792d12e..000000000
--- a/shared/core-utils/common/promises.ts
+++ /dev/null
@@ -1,58 +0,0 @@
1export function isPromise <T = unknown> (value: T | Promise<T>): value is Promise<T> {
2 return value && typeof (value as Promise<T>).then === 'function'
3}
4
5export function isCatchable (value: any) {
6 return value && typeof value.catch === 'function'
7}
8
9export function timeoutPromise <T> (promise: Promise<T>, timeoutMs: number) {
10 let timer: ReturnType<typeof setTimeout>
11
12 return Promise.race([
13 promise,
14
15 new Promise((_res, rej) => {
16 timer = setTimeout(() => rej(new Error('Timeout')), timeoutMs)
17 })
18 ]).finally(() => clearTimeout(timer))
19}
20
21export function promisify0<A> (func: (cb: (err: any, result: A) => void) => void): () => Promise<A> {
22 return function promisified (): Promise<A> {
23 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
24 // eslint-disable-next-line no-useless-call
25 func.apply(null, [ (err: any, res: A) => err ? reject(err) : resolve(res) ])
26 })
27 }
28}
29
30// Thanks to https://gist.github.com/kumasento/617daa7e46f13ecdd9b2
31export function promisify1<T, A> (func: (arg: T, cb: (err: any, result: A) => void) => void): (arg: T) => Promise<A> {
32 return function promisified (arg: T): Promise<A> {
33 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
34 // eslint-disable-next-line no-useless-call
35 func.apply(null, [ arg, (err: any, res: A) => err ? reject(err) : resolve(res) ])
36 })
37 }
38}
39
40// eslint-disable-next-line max-len
41export function promisify2<T, U, A> (func: (arg1: T, arg2: U, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U) => Promise<A> {
42 return function promisified (arg1: T, arg2: U): Promise<A> {
43 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
44 // eslint-disable-next-line no-useless-call
45 func.apply(null, [ arg1, arg2, (err: any, res: A) => err ? reject(err) : resolve(res) ])
46 })
47 }
48}
49
50// eslint-disable-next-line max-len
51export function promisify3<T, U, V, A> (func: (arg1: T, arg2: U, arg3: V, cb: (err: any, result: A) => void) => void): (arg1: T, arg2: U, arg3: V) => Promise<A> {
52 return function promisified (arg1: T, arg2: U, arg3: V): Promise<A> {
53 return new Promise<A>((resolve: (arg: A) => void, reject: (err: any) => void) => {
54 // eslint-disable-next-line no-useless-call
55 func.apply(null, [ arg1, arg2, arg3, (err: any, res: A) => err ? reject(err) : resolve(res) ])
56 })
57 }
58}
diff --git a/shared/core-utils/common/random.ts b/shared/core-utils/common/random.ts
deleted file mode 100644
index 705735d09..000000000
--- a/shared/core-utils/common/random.ts
+++ /dev/null
@@ -1,8 +0,0 @@
1// high excluded
2function randomInt (low: number, high: number) {
3 return Math.floor(Math.random() * (high - low) + low)
4}
5
6export {
7 randomInt
8}
diff --git a/shared/core-utils/common/regexp.ts b/shared/core-utils/common/regexp.ts
deleted file mode 100644
index 59eb87eb6..000000000
--- a/shared/core-utils/common/regexp.ts
+++ /dev/null
@@ -1,5 +0,0 @@
1export const uuidRegex = '[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}'
2
3export function removeFragmentedMP4Ext (path: string) {
4 return path.replace(/-fragmented.mp4$/i, '')
5}
diff --git a/shared/core-utils/common/time.ts b/shared/core-utils/common/time.ts
deleted file mode 100644
index 2992609ca..000000000
--- a/shared/core-utils/common/time.ts
+++ /dev/null
@@ -1,7 +0,0 @@
1function wait (milliseconds: number) {
2 return new Promise(resolve => setTimeout(resolve, milliseconds))
3}
4
5export {
6 wait
7}
diff --git a/shared/core-utils/common/url.ts b/shared/core-utils/common/url.ts
deleted file mode 100644
index 33fc5ee3a..000000000
--- a/shared/core-utils/common/url.ts
+++ /dev/null
@@ -1,150 +0,0 @@
1import { Video, VideoPlaylist } from '../../models'
2import { secondsToTime } from './date'
3
4function addQueryParams (url: string, params: { [ id: string ]: string }) {
5 const objUrl = new URL(url)
6
7 for (const key of Object.keys(params)) {
8 objUrl.searchParams.append(key, params[key])
9 }
10
11 return objUrl.toString()
12}
13
14function removeQueryParams (url: string) {
15 const objUrl = new URL(url)
16
17 objUrl.searchParams.forEach((_v, k) => objUrl.searchParams.delete(k))
18
19 return objUrl.toString()
20}
21
22function buildPlaylistLink (playlist: Pick<VideoPlaylist, 'shortUUID'>, base?: string) {
23 return (base ?? window.location.origin) + buildPlaylistWatchPath(playlist)
24}
25
26function buildPlaylistWatchPath (playlist: Pick<VideoPlaylist, 'shortUUID'>) {
27 return '/w/p/' + playlist.shortUUID
28}
29
30function buildVideoWatchPath (video: Pick<Video, 'shortUUID'>) {
31 return '/w/' + video.shortUUID
32}
33
34function buildVideoLink (video: Pick<Video, 'shortUUID'>, base?: string) {
35 return (base ?? window.location.origin) + buildVideoWatchPath(video)
36}
37
38function buildPlaylistEmbedPath (playlist: Pick<VideoPlaylist, 'uuid'>) {
39 return '/video-playlists/embed/' + playlist.uuid
40}
41
42function buildPlaylistEmbedLink (playlist: Pick<VideoPlaylist, 'uuid'>, base?: string) {
43 return (base ?? window.location.origin) + buildPlaylistEmbedPath(playlist)
44}
45
46function buildVideoEmbedPath (video: Pick<Video, 'uuid'>) {
47 return '/videos/embed/' + video.uuid
48}
49
50function buildVideoEmbedLink (video: Pick<Video, 'uuid'>, base?: string) {
51 return (base ?? window.location.origin) + buildVideoEmbedPath(video)
52}
53
54function decorateVideoLink (options: {
55 url: string
56
57 startTime?: number
58 stopTime?: number
59
60 subtitle?: string
61
62 loop?: boolean
63 autoplay?: boolean
64 muted?: boolean
65
66 // Embed options
67 title?: boolean
68 warningTitle?: boolean
69
70 controls?: boolean
71 controlBar?: boolean
72
73 peertubeLink?: boolean
74 p2p?: boolean
75}) {
76 const { url } = options
77
78 const params = new URLSearchParams()
79
80 if (options.startTime !== undefined && options.startTime !== null) {
81 const startTimeInt = Math.floor(options.startTime)
82 params.set('start', secondsToTime(startTimeInt))
83 }
84
85 if (options.stopTime) {
86 const stopTimeInt = Math.floor(options.stopTime)
87 params.set('stop', secondsToTime(stopTimeInt))
88 }
89
90 if (options.subtitle) params.set('subtitle', options.subtitle)
91
92 if (options.loop === true) params.set('loop', '1')
93 if (options.autoplay === true) params.set('autoplay', '1')
94 if (options.muted === true) params.set('muted', '1')
95 if (options.title === false) params.set('title', '0')
96 if (options.warningTitle === false) params.set('warningTitle', '0')
97
98 if (options.controls === false) params.set('controls', '0')
99 if (options.controlBar === false) params.set('controlBar', '0')
100
101 if (options.peertubeLink === false) params.set('peertubeLink', '0')
102 if (options.p2p !== undefined) params.set('p2p', options.p2p ? '1' : '0')
103
104 return buildUrl(url, params)
105}
106
107function decoratePlaylistLink (options: {
108 url: string
109
110 playlistPosition?: number
111}) {
112 const { url } = options
113
114 const params = new URLSearchParams()
115
116 if (options.playlistPosition) params.set('playlistPosition', '' + options.playlistPosition)
117
118 return buildUrl(url, params)
119}
120
121// ---------------------------------------------------------------------------
122
123export {
124 addQueryParams,
125 removeQueryParams,
126
127 buildPlaylistLink,
128 buildVideoLink,
129
130 buildVideoWatchPath,
131 buildPlaylistWatchPath,
132
133 buildPlaylistEmbedPath,
134 buildVideoEmbedPath,
135
136 buildPlaylistEmbedLink,
137 buildVideoEmbedLink,
138
139 decorateVideoLink,
140 decoratePlaylistLink
141}
142
143function buildUrl (url: string, params: URLSearchParams) {
144 let hasParams = false
145 params.forEach(() => { hasParams = true })
146
147 if (hasParams) return url + '?' + params.toString()
148
149 return url
150}
diff --git a/shared/core-utils/common/version.ts b/shared/core-utils/common/version.ts
deleted file mode 100644
index 305287233..000000000
--- a/shared/core-utils/common/version.ts
+++ /dev/null
@@ -1,11 +0,0 @@
1// Thanks https://gist.github.com/iwill/a83038623ba4fef6abb9efca87ae9ccb
2function compareSemVer (a: string, b: string) {
3 if (a.startsWith(b + '-')) return -1
4 if (b.startsWith(a + '-')) return 1
5
6 return a.localeCompare(b, undefined, { numeric: true, sensitivity: 'case', caseFirst: 'upper' })
7}
8
9export {
10 compareSemVer
11}