Skip to content

Commit 36d6a31

Browse files
fix: show and link aliased deps correctly (#2192)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent fff9a6c commit 36d6a31

9 files changed

Lines changed: 260 additions & 65 deletions

File tree

app/components/Package/Dependencies.vue

Lines changed: 112 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
<script setup lang="ts">
2+
import { parseDependencyVersion } from '#shared/utils/npm'
23
import { SEVERITY_TEXT_COLORS, getHighestSeverity } from '#shared/utils/severity'
34
import { getOutdatedTooltip, getVersionClass } from '~/utils/npm/outdated-dependencies'
45
@@ -66,24 +67,41 @@ const sortedOptionalDependencies = computed(() => {
6667
return Object.entries(props.optionalDependencies).sort(([a], [b]) => a.localeCompare(b))
6768
})
6869
69-
// Get version tooltip
70-
function getDepVersionTooltip(dep: string, version: string) {
71-
const outdated = outdatedDeps.value[dep]
70+
// Get version tooltip (key for outdated lookup, realName for vuln/replacement lookup)
71+
function getDepVersionTooltip(key: string, realName: string, version: string) {
72+
const outdated = outdatedDeps.value[key]
7273
if (outdated) return getOutdatedTooltip(outdated, t)
73-
if (getVulnerableDepInfo(dep) || getDeprecatedDepInfo(dep)) return version
74-
if (replacementDeps.value[dep]) return t('package.dependencies.has_replacement')
74+
if (getVulnerableDepInfo(realName) || getDeprecatedDepInfo(realName)) return version
75+
if (replacementDeps.value[realName]) return t('package.dependencies.has_replacement')
7576
return version
7677
}
7778
78-
// Get version class
79-
function getDepVersionClass(dep: string) {
80-
const outdated = outdatedDeps.value[dep]
79+
// Get version class (key for outdated lookup, realName for vuln/replacement lookup)
80+
function getDepVersionClass(key: string, realName: string) {
81+
const outdated = outdatedDeps.value[key]
8182
if (outdated) return getVersionClass(outdated)
82-
if (replacementDeps.value[dep]) return 'text-amber-700 dark:text-amber-500'
83-
if (getVulnerableDepInfo(dep) || getDeprecatedDepInfo(dep)) return getVersionClass(undefined)
83+
if (getVulnerableDepInfo(realName) || getDeprecatedDepInfo(realName))
84+
return getVersionClass(undefined)
85+
if (replacementDeps.value[realName]) return 'text-amber-700 dark:text-amber-500'
8486
return getVersionClass(undefined)
8587
}
8688
89+
// Resolve npm: aliases — returns the real package name for links
90+
function resolveDepName(key: string, value: string): string {
91+
return parseDependencyVersion(value).name ?? key
92+
}
93+
94+
// Resolve npm: aliases — returns the version range for display
95+
function depRange(value: string): string {
96+
return parseDependencyVersion(value).range ?? value
97+
}
98+
99+
// Whether a dependency uses an npm: alias (listed name differs from the real package)
100+
function isAliased(key: string, value: string): boolean {
101+
const realName = parseDependencyVersion(value).name
102+
return realName != null && realName !== key
103+
}
104+
87105
const {
88106
visibleItems: visibleDeps,
89107
hasMore: hasMoreDeps,
@@ -127,10 +145,29 @@ const numberFormatter = useNumberFormatter()
127145
:key="dep"
128146
class="flex items-center justify-between py-1 text-sm gap-2"
129147
>
130-
<LinkBase :to="packageRoute(dep)" class="block truncate" dir="ltr">
148+
<LinkBase
149+
:to="packageRoute(resolveDepName(dep, version))"
150+
class="block truncate"
151+
dir="ltr"
152+
>
131153
{{ dep }}
132154
</LinkBase>
133155
<span class="flex items-center gap-1 max-w-[40%]" dir="ltr">
156+
<TooltipApp
157+
v-if="isAliased(dep, version)"
158+
class="shrink-0 text-fg-muted"
159+
:text="$t('package.dependencies.aliased_to', { name: resolveDepName(dep, version) })"
160+
>
161+
<button
162+
type="button"
163+
class="inline-flex items-center justify-center p-2 -m-2"
164+
:aria-label="
165+
$t('package.dependencies.aliased_to', { name: resolveDepName(dep, version) })
166+
"
167+
>
168+
<span class="i-lucide:arrow-right-left w-3 h-3" aria-hidden="true" />
169+
</button>
170+
</TooltipApp>
134171
<TooltipApp
135172
v-if="outdatedDeps[dep]"
136173
class="shrink-0"
@@ -146,7 +183,7 @@ const numberFormatter = useNumberFormatter()
146183
</button>
147184
</TooltipApp>
148185
<TooltipApp
149-
v-if="replacementDeps[dep]"
186+
v-if="replacementDeps[resolveDepName(dep, version)]"
150187
class="shrink-0 text-amber-700 dark:text-amber-500"
151188
:text="$t('package.dependencies.has_replacement')"
152189
>
@@ -159,41 +196,55 @@ const numberFormatter = useNumberFormatter()
159196
</button>
160197
</TooltipApp>
161198
<LinkBase
162-
v-if="getVulnerableDepInfo(dep)"
163-
:to="packageRoute(dep, getVulnerableDepInfo(dep)!.version)"
199+
v-if="getVulnerableDepInfo(resolveDepName(dep, version))"
200+
:to="
201+
packageRoute(
202+
resolveDepName(dep, version),
203+
getVulnerableDepInfo(resolveDepName(dep, version))!.version,
204+
)
205+
"
164206
class="shrink-0"
165-
:class="SEVERITY_TEXT_COLORS[getHighestSeverity(getVulnerableDepInfo(dep)!.counts)]"
207+
:class="
208+
SEVERITY_TEXT_COLORS[
209+
getHighestSeverity(getVulnerableDepInfo(resolveDepName(dep, version))!.counts)
210+
]
211+
"
166212
:aria-label="$t('package.dependencies.view_vulnerabilities')"
167213
:title="
168214
$t('package.dependencies.vulnerabilities_count', {
169-
count: getVulnerableDepInfo(dep)!.counts.total,
215+
count: getVulnerableDepInfo(resolveDepName(dep, version))!.counts.total,
170216
})
171217
"
172218
classicon="i-lucide:shield-check"
173219
/>
174220
<LinkBase
175-
v-if="getDeprecatedDepInfo(dep)"
176-
:to="packageRoute(dep, getDeprecatedDepInfo(dep)!.version)"
221+
v-if="getDeprecatedDepInfo(resolveDepName(dep, version))"
222+
:to="
223+
packageRoute(
224+
resolveDepName(dep, version),
225+
getDeprecatedDepInfo(resolveDepName(dep, version))!.version,
226+
)
227+
"
177228
class="shrink-0 text-purple-700 dark:text-purple-500"
178229
:aria-label="$t('package.deprecated.label')"
179-
:title="getDeprecatedDepInfo(dep)!.message"
230+
:title="getDeprecatedDepInfo(resolveDepName(dep, version))!.message"
180231
classicon="i-lucide:octagon-alert"
181232
/>
182233
<LinkBase
183-
:to="packageRoute(dep, version)"
234+
:to="packageRoute(resolveDepName(dep, version), depRange(version))"
184235
class="block truncate"
185-
:class="getDepVersionClass(dep)"
186-
:title="getDepVersionTooltip(dep, version)"
236+
:class="getDepVersionClass(dep, resolveDepName(dep, version))"
237+
:title="getDepVersionTooltip(dep, resolveDepName(dep, version), depRange(version))"
187238
>
188-
{{ version }}
239+
{{ depRange(version) }}
189240
</LinkBase>
190241
<span v-if="outdatedDeps[dep]" class="sr-only">
191242
({{ getOutdatedTooltip(outdatedDeps[dep], $t) }})
192243
</span>
193-
<span v-if="getVulnerableDepInfo(dep)" class="sr-only">
244+
<span v-if="getVulnerableDepInfo(resolveDepName(dep, version))" class="sr-only">
194245
({{
195246
$t('package.dependencies.vulnerabilities_count', {
196-
count: getVulnerableDepInfo(dep)!.counts.total,
247+
count: getVulnerableDepInfo(resolveDepName(dep, version))!.counts.total,
197248
})
198249
}})
199250
</span>
@@ -238,7 +289,11 @@ const numberFormatter = useNumberFormatter()
238289
class="flex items-center justify-between py-1 text-sm gap-1 min-w-0"
239290
>
240291
<div class="flex items-center gap-2 min-w-0 flex-1">
241-
<LinkBase :to="packageRoute(peer.name)" class="block min-w-0 break-all" dir="ltr">
292+
<LinkBase
293+
:to="packageRoute(resolveDepName(peer.name, peer.version))"
294+
class="block min-w-0 break-all"
295+
dir="ltr"
296+
>
242297
{{ peer.name }}
243298
</LinkBase>
244299
<TagStatic
@@ -250,12 +305,12 @@ const numberFormatter = useNumberFormatter()
250305
</TagStatic>
251306
</div>
252307
<LinkBase
253-
:to="packageRoute(peer.name, peer.version)"
308+
:to="packageRoute(resolveDepName(peer.name, peer.version), depRange(peer.version))"
254309
class="block truncate shrink-0 max-w-20"
255-
:title="peer.version"
310+
:title="depRange(peer.version)"
256311
dir="ltr"
257312
>
258-
{{ peer.version }}
313+
{{ depRange(peer.version) }}
259314
</LinkBase>
260315
</li>
261316
</ul>
@@ -300,17 +355,38 @@ const numberFormatter = useNumberFormatter()
300355
:key="dep"
301356
class="flex items-baseline justify-between py-1 text-sm gap-2"
302357
>
303-
<LinkBase :to="packageRoute(dep)" class="block max-w-[80%] break-words" dir="ltr">
304-
{{ dep }}
305-
</LinkBase>
306358
<LinkBase
307-
:to="packageRoute(dep, version)"
308-
class="block truncate"
309-
:title="version"
359+
:to="packageRoute(resolveDepName(dep, version))"
360+
class="block max-w-[80%] break-words"
310361
dir="ltr"
311362
>
312-
{{ version }}
363+
{{ dep }}
313364
</LinkBase>
365+
<span class="flex items-center gap-1 min-w-0" dir="ltr">
366+
<TooltipApp
367+
v-if="isAliased(dep, version)"
368+
class="shrink-0 text-fg-muted"
369+
:text="$t('package.dependencies.aliased_to', { name: resolveDepName(dep, version) })"
370+
>
371+
<button
372+
type="button"
373+
class="inline-flex items-center justify-center p-2 -m-2"
374+
:aria-label="
375+
$t('package.dependencies.aliased_to', { name: resolveDepName(dep, version) })
376+
"
377+
>
378+
<span class="i-lucide:arrow-right-left w-3 h-3" aria-hidden="true" />
379+
</button>
380+
</TooltipApp>
381+
<LinkBase
382+
:to="packageRoute(resolveDepName(dep, version), depRange(version))"
383+
class="block truncate"
384+
:title="depRange(version)"
385+
dir="ltr"
386+
>
387+
{{ depRange(version) }}
388+
</LinkBase>
389+
</span>
314390
</li>
315391
</ul>
316392
<button

app/composables/npm/useOutdatedDependencies.ts

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
import type { PackageVersionsInfo } from 'fast-npm-meta'
22
import { getVersionsBatch } from 'fast-npm-meta'
33
import { difference, findMaxSatisfying, getMajor, getMinor, isGreater, isStable } from 'verkit'
4+
import { parseDependencyVersion } from '#shared/utils/npm'
45
import {
56
type OutdatedDependencyInfo,
6-
isNonSemverConstraint,
77
constraintIncludesPrerelease,
88
} from '~/utils/npm/outdated-dependencies'
99

@@ -67,16 +67,20 @@ export function useOutdatedDependencies(
6767
return
6868
}
6969

70-
const semverEntries = Object.entries(deps).filter(
71-
([, constraint]) => !isNonSemverConstraint(constraint),
72-
)
70+
// Resolve npm: aliases and filter out non-semver constraints
71+
const resolvedEntries = Object.entries(deps)
72+
.map(([key, value]) => {
73+
const parsed = parseDependencyVersion(value)
74+
return { key, realName: parsed.name ?? key, range: parsed.range }
75+
})
76+
.filter((e): e is typeof e & { range: string } => e.range !== null)
7377

74-
if (semverEntries.length === 0) {
78+
if (resolvedEntries.length === 0) {
7579
outdated.value = {}
7680
return
7781
}
7882

79-
const packageNames = semverEntries.map(([name]) => name)
83+
const packageNames = [...new Set(resolvedEntries.map(e => e.realName))]
8084

8185
const chunks: string[][] = []
8286
for (let i = 0; i < packageNames.length; i += BATCH_SIZE) {
@@ -95,16 +99,16 @@ export function useOutdatedDependencies(
9599
}
96100

97101
const results: Record<string, OutdatedDependencyInfo> = {}
98-
for (const [name, constraint] of semverEntries) {
99-
const data = versionMap.get(name)
102+
for (const { key, realName, range } of resolvedEntries) {
103+
const data = versionMap.get(realName)
100104
if (!data) continue
101105

102106
const latestTag = data.distTags.latest
103107
if (!latestTag) continue
104108

105-
const info = resolveOutdated(data.versions, latestTag, constraint)
109+
const info = resolveOutdated(data.versions, latestTag, range)
106110
if (info) {
107-
results[name] = info
111+
results[key] = info
108112
}
109113
}
110114

app/composables/npm/useReplacementDependencies.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
import type { ModuleReplacement, ModuleReplacementMapping } from 'module-replacements'
2+
import { parseDependencyVersion } from '#shared/utils/npm'
23

34
async function fetchReplacements(
45
deps: Record<string, string>,
56
): Promise<Record<string, ModuleReplacement>> {
6-
const names = Object.keys(deps)
7+
const names = [
8+
...new Set(
9+
Object.entries(deps).map(([key, value]) => parseDependencyVersion(value).name ?? key),
10+
),
11+
]
712

813
const results = await Promise.all(
914
names.map(async name => {

app/utils/npm/outdated-dependencies.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,6 @@ export function constraintIncludesPrerelease(constraint: string): boolean {
2525
)
2626
}
2727

28-
/**
29-
* Check if a constraint is a non-semver value (git URL, file path, etc.)
30-
*/
31-
export function isNonSemverConstraint(constraint: string): boolean {
32-
return (
33-
constraint.startsWith('git') ||
34-
constraint.startsWith('http') ||
35-
constraint.startsWith('file:') ||
36-
constraint.startsWith('npm:') ||
37-
constraint.startsWith('link:') ||
38-
constraint.startsWith('workspace:') ||
39-
constraint.includes('/')
40-
)
41-
}
42-
4328
/**
4429
* Get tooltip text for an outdated dependency
4530
*/

i18n/locales/de.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -618,7 +618,8 @@
618618
"outdated_minor": "{count} Nebenversion zurück (neueste: {latest}) | {count} Nebenversionen zurück (neueste: {latest})",
619619
"outdated_patch": "Patch-Update verfügbar (aktuellste: {latest})",
620620
"has_replacement": "Für diese Abhängigkeit gibt es empfohlene Alternativen",
621-
"vulnerabilities_count": "{count} Sicherheitslücke | {count} Sicherheitslücken"
621+
"vulnerabilities_count": "{count} Sicherheitslücke | {count} Sicherheitslücken",
622+
"aliased_to": "Alias für {name}"
622623
},
623624
"peer_dependencies": {
624625
"title": "Peer-Abhängigkeit ({count}) | Peer-Abhängigkeiten ({count})",

i18n/locales/en.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -689,7 +689,8 @@
689689
"outdated_minor": "{count} minor version behind (latest: {latest}) | {count} minor versions behind (latest: {latest})",
690690
"outdated_patch": "Patch update available (latest: {latest})",
691691
"has_replacement": "This dependency has suggested replacements",
692-
"vulnerabilities_count": "{count} vulnerability | {count} vulnerabilities"
692+
"vulnerabilities_count": "{count} vulnerability | {count} vulnerabilities",
693+
"aliased_to": "Aliased to {name}"
693694
},
694695
"peer_dependencies": {
695696
"title": "Peer Dependency ({count}) | Peer Dependencies ({count})",

i18n/schema.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2073,6 +2073,9 @@
20732073
},
20742074
"vulnerabilities_count": {
20752075
"type": "string"
2076+
},
2077+
"aliased_to": {
2078+
"type": "string"
20762079
}
20772080
},
20782081
"additionalProperties": false

0 commit comments

Comments
 (0)