Skip to content

Commit b708b27

Browse files
committed
fix: complex versions url handler
1 parent 365bd9f commit b708b27

File tree

2 files changed

+69
-17
lines changed

2 files changed

+69
-17
lines changed

app/components/Package/Dependencies.vue

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<script setup lang="ts">
22
import { SEVERITY_TEXT_COLORS, getHighestSeverity } from '#shared/utils/severity'
33
import { getOutdatedTooltip, getVersionClass } from '~/utils/npm/outdated-dependencies'
4+
import { buildVersionLink } from '~/utils/versions'
45
56
const { t } = useI18n()
67
@@ -45,7 +46,12 @@ const optionalDepsExpanded = shallowRef(false)
4546
// Sort dependencies alphabetically
4647
const sortedDependencies = computed(() => {
4748
if (!props.dependencies) return []
48-
return Object.entries(props.dependencies).sort(([a], [b]) => a.localeCompare(b))
49+
return Object.entries(props.dependencies)
50+
.map(([name, version]) => ({
51+
name,
52+
version: buildVersionLink(version),
53+
}))
54+
.sort((a, b) => a.name.localeCompare(b.name))
4955
})
5056
5157
// Sort peer dependencies alphabetically, with required first then optional
@@ -55,7 +61,7 @@ const sortedPeerDependencies = computed(() => {
5561
return Object.entries(props.peerDependencies)
5662
.map(([name, version]) => ({
5763
name,
58-
version,
64+
version: buildVersionLink(version),
5965
optional: props.peerDependenciesMeta?.[name]?.optional ?? false,
6066
}))
6167
.sort((a, b) => {
@@ -68,7 +74,12 @@ const sortedPeerDependencies = computed(() => {
6874
// Sort optional dependencies alphabetically
6975
const sortedOptionalDependencies = computed(() => {
7076
if (!props.optionalDependencies) return []
71-
return Object.entries(props.optionalDependencies).sort(([a], [b]) => a.localeCompare(b))
77+
return Object.entries(props.optionalDependencies)
78+
.map(([name, version]) => ({
79+
name,
80+
version: buildVersionLink(version),
81+
}))
82+
.sort((a, b) => a.name.localeCompare(b.name))
7283
})
7384
7485
// Get version tooltip
@@ -110,7 +121,10 @@ const numberFormatter = useNumberFormatter()
110121
>
111122
<ul class="px-1 space-y-1 list-none m-0" :aria-label="$t('package.dependencies.list_label')">
112123
<li
113-
v-for="[dep, version] in sortedDependencies.slice(0, depsExpanded ? undefined : 10)"
124+
v-for="{ name: dep, version } in sortedDependencies.slice(
125+
0,
126+
depsExpanded ? undefined : 10,
127+
)"
114128
:key="dep"
115129
class="flex items-center justify-between py-1 text-sm gap-2"
116130
>
@@ -165,12 +179,12 @@ const numberFormatter = useNumberFormatter()
165179
<span class="sr-only">{{ $t('package.deprecated.label') }}</span>
166180
</LinkBase>
167181
<LinkBase
168-
:to="packageRoute(dep, version)"
182+
:to="packageRoute(dep, version.href)"
169183
class="block truncate"
170184
:class="getDepVersionClass(dep)"
171-
:title="getDepVersionTooltip(dep, version)"
185+
:title="getDepVersionTooltip(dep, version.title)"
172186
>
173-
{{ version }}
187+
{{ version.title }}
174188
</LinkBase>
175189
<span v-if="outdatedDeps[dep]" class="sr-only">
176190
({{ getOutdatedTooltip(outdatedDeps[dep], $t) }})
@@ -227,12 +241,12 @@ const numberFormatter = useNumberFormatter()
227241
</TagStatic>
228242
</div>
229243
<LinkBase
230-
:to="packageRoute(peer.name, peer.version)"
244+
:to="packageRoute(peer.name, peer.version.href)"
231245
class="block truncate max-w-[40%]"
232-
:title="peer.version"
246+
:title="peer.version.title"
233247
dir="ltr"
234248
>
235-
{{ peer.version }}
249+
{{ peer.version.title }}
236250
</LinkBase>
237251
</li>
238252
</ul>
@@ -273,23 +287,23 @@ const numberFormatter = useNumberFormatter()
273287
:aria-label="$t('package.optional_dependencies.list_label')"
274288
>
275289
<li
276-
v-for="[dep, version] in sortedOptionalDependencies.slice(
290+
v-for="{ name, version } in sortedOptionalDependencies.slice(
277291
0,
278292
optionalDepsExpanded ? undefined : 10,
279293
)"
280-
:key="dep"
294+
:key="name"
281295
class="flex items-center justify-between py-1 text-sm gap-2"
282296
>
283-
<LinkBase :to="packageRoute(dep)" class="block truncate" dir="ltr">
284-
{{ dep }}
297+
<LinkBase :to="packageRoute(name)" class="block truncate" dir="ltr">
298+
{{ name }}
285299
</LinkBase>
286300
<LinkBase
287-
:to="packageRoute(dep, version)"
301+
:to="packageRoute(name, version.href)"
288302
class="block truncate"
289-
:title="version"
303+
:title="version.title"
290304
dir="ltr"
291305
>
292-
{{ version }}
306+
{{ version.title }}
293307
</LinkBase>
294308
</li>
295309
</ul>

app/utils/versions.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,14 @@ export interface ParsedVersion {
2323
prerelease: string
2424
}
2525

26+
export interface VersionLink {
27+
href: string
28+
title: string
29+
}
30+
31+
const LAST_VERSION_IN_RANGE_REGEXP =
32+
/([~^<>=]*\s*\d+\.\d+\.\d+(?:-[0-9A-Z-]+(?:\.[0-9A-Z-]+)*)?(?:\+[0-9A-Z-]+(?:\.[0-9A-Z-]+)*)?)\s*$/i
33+
2634
/**
2735
* Parse a semver version string into its components
2836
* @param version - The version string (e.g., "1.2.3" or "1.0.0-beta.1")
@@ -207,3 +215,33 @@ export function filterVersions(versions: string[], range: string): Set<string> {
207215
}
208216
return matched
209217
}
218+
219+
/**
220+
* Convert a semver complex version (Comparator Set, Range, Union) to
221+
* a version object with href and title. It's needed to get a single version
222+
* for valid URL if there is a range version, union version or combination of both
223+
* e.g. (href), "^1.0.0" -> "^1.0.0",
224+
* ">=1.0.0 <= 2.0.0" -> "<=2.0.0"
225+
* "1.0.0 || 2.0.0" -> "2.0.0"
226+
*
227+
* @param version - A semver version, might be a range, union, etc
228+
* @returns Version object with href and title, where href is a valid single version for URL usage
229+
*/
230+
export function buildVersionLink(version: string): VersionLink {
231+
let href = version
232+
// Check for union version, which means only logical OR ("||") present and no range ("-")
233+
if (version.includes('||') && !version.includes(' - ')) {
234+
const versions: string[] = version.split('||').map(item => item.trim())
235+
236+
href = versions.at(-1) || version
237+
// Check for range version and complex conditions: could be ">=" + "<=", "||", "&&", "-"
238+
} else if (/>=|<=|[<>]|\s-\s|&&/.test(version)) {
239+
// Remove whitespaces to convert to valid href: "< 1.0.0" -> "<1.0.0"
240+
href = version.match(LAST_VERSION_IN_RANGE_REGEXP)?.[1]?.replace(/\s+/g, '') || version
241+
}
242+
243+
return {
244+
href,
245+
title: version,
246+
}
247+
}

0 commit comments

Comments
 (0)