import { memoizeWith } from '@shlinkio/data-manipulation'; import { compare } from 'compare-versions'; export type Empty = null | undefined | '' | never[]; const isEmpty = (value: Exclude): boolean => ( (Array.isArray(value) && value.length === 0) || (typeof value === 'string' && value === '') || (typeof value === 'object' && Object.keys(value).length === 0) ); export const hasValue = (value: T | Empty): value is T => value !== undefined && value !== null && !isEmpty(value); type SemVerPatternFragment = `${bigint | '*'}`; type SemVerPattern = SemVerPatternFragment | `${SemVerPatternFragment}.${SemVerPatternFragment}` | `${SemVerPatternFragment}.${SemVerPatternFragment}.${SemVerPatternFragment}`; export type Versions = { maxVersion?: SemVerPattern; minVersion?: SemVerPattern; }; export type SemVer = `${bigint}.${bigint}.${bigint}` | 'latest'; export const versionMatch = (versionToMatch: SemVer | Empty, { maxVersion, minVersion }: Versions): boolean => { if (!hasValue(versionToMatch)) { return false; } const matchesMinVersion = !minVersion || compare(versionToMatch, minVersion, '>='); const matchesMaxVersion = !maxVersion || compare(versionToMatch, maxVersion, '<='); return matchesMaxVersion && matchesMinVersion; }; const versionIsValidSemVer = memoizeWith((v) => v, (version: string): version is SemVer => { try { return compare(version, version, '='); } catch (e) { return false; } }); export const versionToPrintable = (version: string) => (!versionIsValidSemVer(version) ? version : `v${version}`); export const versionToSemVer = (version: string, fallback: SemVer = 'latest'): SemVer => ( versionIsValidSemVer(version) ? version : fallback );