From 7b4d3240783384da6c313d58bc3d5f892b55fa0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?X=C3=B9d=C5=8Dng=20Y=C3=A1ng?= Date: Tue, 23 Jul 2024 21:00:14 -0700 Subject: [PATCH 1/4] Implement proper version sorting Got bored and wanted to explore Typescript. Tested on https://typescriptlang.org/play: > console.log(sortVersions(['1.2.3', '1.2.3-d', '1.2.3.bcr.1', '1.2.3rc3', '1', 'k'])) ["k", "1.2.3rc3", "1.2.3.bcr.1", "1.2.3", "1.2.3-d", "1"] Addresses #54. Fixes #142. --- data/moduleStaticProps.ts | 85 +++++++++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 17 deletions(-) diff --git a/data/moduleStaticProps.ts b/data/moduleStaticProps.ts index b40604ddd68c..3c1502db7a5d 100644 --- a/data/moduleStaticProps.ts +++ b/data/moduleStaticProps.ts @@ -1,4 +1,3 @@ -import { compareVersions, validate as validateVersion } from 'compare-versions' import { extractModuleInfo, getModuleMetadata, @@ -53,23 +52,75 @@ export const getStaticPropsModulePage = async ( } /** - * Sort versions, by splitting them into sortable and unsortable ones. + * Parse and sort versions according to https://bazel.build/external/module#version_format. * - * The sortable versions will form the start of the list and are sorted, while the unsortable ones will - * form the end of it. - * - * This is mostly a placeholder until we have proper version parsing and comparison - * (see discussion in https://github.com/bazel-contrib/bcr-ui/issues/54). + * This is a placeholder until we switch to a common "Bzlmod semver" library + * (see https://github.com/bazel-contrib/bcr-ui/issues/54#issuecomment-1521844089). */ -const sortVersions = (versions: string[]): string[] => { - const sortableVersions = versions.filter((version) => - validateVersion(version) - ) - const unsortableVersions = versions.filter( - (version) => !validateVersion(version) - ) - sortableVersions.sort(compareVersions) - sortableVersions.reverse() - return [...sortableVersions, ...unsortableVersions] +type Version = { + release: string[], + prerelease: string[], + original: string, +} + +const parseVersion = (v: string): Version => { + const firstDash = v.indexOf('-') + if (firstDash === -1) { + return { release: v.split('.'), prerelease: [], original: v } + } + return { + release: v.slice(0, firstDash).split('.'), + prerelease: v.slice(firstDash + 1).split('.'), + original: v, + } +} + +type Cmp = (a: T, b: T) => number + +function natural(a: T, b: T): number { + return a < b ? -1 : a > b ? 1 : 0 +} + +function comparing(f: (t: T) => U, innerCmp: Cmp = natural): Cmp { + return (a: T, b: T): number => innerCmp(f(a), f(b)) +} + +function lexicographically(elementCmp: Cmp = natural): Cmp { + return (as: T[], bs: T[]): number => { + for (let i = 0; i < as.length && i < bs.length; i++) { + const result = elementCmp(as[i], bs[i]) + if (result !== 0) return result + } + return as.length - bs.length + } +} + +function composeCmps(...cmps: Cmp[]): Cmp { + return (a: T, b: T): number => { + for (const cmp of cmps) { + const result = cmp(a, b) + if (result !== 0) return result + } + return 0 + } +} + +const compareIdentifiers: Cmp = composeCmps( + comparing((id) => !/^\d+$/.test(id)), // pure numbers compare less than non-numbers + comparing((id) => /^\d+$/.test(id) ? parseInt(id) : 0), + natural, +) + +const compareVersions: Cmp = composeCmps( + comparing((v) => v.release, lexicographically(compareIdentifiers)), + comparing((v) => v.prerelease.length === 0), // nonempty prerelease compares less than empty prerelease + comparing((v) => v.prerelease, lexicographically(compareIdentifiers)), +) + +const sortVersions = (versions: string[]): string[] => { + const parsed = versions.map(parseVersion) + parsed.sort(compareVersions) + parsed.reverse() + return parsed.map((v) => v.original) } From 32aad6f1f84166e2c6e606e7d4f775c02b811b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?X=C3=B9d=C5=8Dng=20Y=C3=A1ng?= Date: Wed, 7 Aug 2024 20:16:53 +0200 Subject: [PATCH 2/4] fix formatting --- data/moduleStaticProps.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/data/moduleStaticProps.ts b/data/moduleStaticProps.ts index 3c1502db7a5d..14c76da74d78 100644 --- a/data/moduleStaticProps.ts +++ b/data/moduleStaticProps.ts @@ -59,9 +59,9 @@ export const getStaticPropsModulePage = async ( */ type Version = { - release: string[], - prerelease: string[], - original: string, + release: string[] + prerelease: string[] + original: string } const parseVersion = (v: string): Version => { @@ -108,7 +108,7 @@ function composeCmps(...cmps: Cmp[]): Cmp { const compareIdentifiers: Cmp = composeCmps( comparing((id) => !/^\d+$/.test(id)), // pure numbers compare less than non-numbers - comparing((id) => /^\d+$/.test(id) ? parseInt(id) : 0), + comparing((id) => (/^\d+$/.test(id) ? parseInt(id) : 0)), natural, ) From 7ab6b9f812c7dc81d7a0807c6ea026cf4a4cc6db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?X=C3=B9d=C5=8Dng=20Y=C3=A1ng?= Date: Wed, 7 Aug 2024 20:26:11 +0200 Subject: [PATCH 3/4] more formatting --- data/moduleStaticProps.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/data/moduleStaticProps.ts b/data/moduleStaticProps.ts index 14c76da74d78..99e93d9a3a7a 100644 --- a/data/moduleStaticProps.ts +++ b/data/moduleStaticProps.ts @@ -109,13 +109,13 @@ function composeCmps(...cmps: Cmp[]): Cmp { const compareIdentifiers: Cmp = composeCmps( comparing((id) => !/^\d+$/.test(id)), // pure numbers compare less than non-numbers comparing((id) => (/^\d+$/.test(id) ? parseInt(id) : 0)), - natural, + natural ) const compareVersions: Cmp = composeCmps( comparing((v) => v.release, lexicographically(compareIdentifiers)), comparing((v) => v.prerelease.length === 0), // nonempty prerelease compares less than empty prerelease - comparing((v) => v.prerelease, lexicographically(compareIdentifiers)), + comparing((v) => v.prerelease, lexicographically(compareIdentifiers)) ) const sortVersions = (versions: string[]): string[] => { From 37ae72823ee3e3e1127ba1a9e51bfd762524f200 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?X=C3=B9d=C5=8Dng=20Y=C3=A1ng?= Date: Wed, 7 Aug 2024 15:04:12 -0400 Subject: [PATCH 4/4] fiiiiiinnnne i'll install pnpm --- data/moduleStaticProps.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/moduleStaticProps.ts b/data/moduleStaticProps.ts index 99e93d9a3a7a..3410228650ca 100644 --- a/data/moduleStaticProps.ts +++ b/data/moduleStaticProps.ts @@ -107,17 +107,17 @@ function composeCmps(...cmps: Cmp[]): Cmp { } const compareIdentifiers: Cmp = composeCmps( - comparing((id) => !/^\d+$/.test(id)), // pure numbers compare less than non-numbers + comparing((id) => !/^\d+$/.test(id)), // pure numbers compare less than non-numbers comparing((id) => (/^\d+$/.test(id) ? parseInt(id) : 0)), natural ) const compareVersions: Cmp = composeCmps( comparing((v) => v.release, lexicographically(compareIdentifiers)), - comparing((v) => v.prerelease.length === 0), // nonempty prerelease compares less than empty prerelease + comparing((v) => v.prerelease.length === 0), // nonempty prerelease compares less than empty prerelease comparing((v) => v.prerelease, lexicographically(compareIdentifiers)) ) - + const sortVersions = (versions: string[]): string[] => { const parsed = versions.map(parseVersion) parsed.sort(compareVersions)