Skip to content

Commit 9d4d150

Browse files
feat: show deprecated packages in dependency tree (#344)
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent e4181fc commit 9d4d150

28 files changed

Lines changed: 524 additions & 40 deletions

app/components/PackageDependencies.vue

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { useVulnerabilityTree } from '~/composables/useVulnerabilityTree'
2+
import { useDependencyAnalysis } from '~/composables/useDependencyAnalysis'
33
import { SEVERITY_TEXT_COLORS, getHighestSeverity } from '#shared/utils/severity'
44
55
const props = defineProps<{
@@ -15,7 +15,7 @@ const props = defineProps<{
1515
const outdatedDeps = useOutdatedDependencies(() => props.dependencies)
1616
1717
// Get vulnerability info from shared cache (already fetched by PackageVulnerabilityTree)
18-
const { data: vulnTree } = useVulnerabilityTree(
18+
const { data: vulnTree } = useDependencyAnalysis(
1919
() => props.packageName,
2020
() => props.version,
2121
)
@@ -26,6 +26,12 @@ function getVulnerableDepInfo(depName: string) {
2626
return vulnTree.value.vulnerablePackages.find(p => p.name === depName && p.depth === 'direct')
2727
}
2828
29+
// Check if a dependency is deprecated (only direct deps)
30+
function getDeprecatedDepInfo(depName: string) {
31+
if (!vulnTree.value) return null
32+
return vulnTree.value.deprecatedPackages.find(p => p.name === depName && p.depth === 'direct')
33+
}
34+
2935
// Expanded state for each section
3036
const depsExpanded = shallowRef(false)
3137
const peerDepsExpanded = shallowRef(false)
@@ -120,6 +126,18 @@ const sortedOptionalDependencies = computed(() => {
120126
<span class="i-carbon-security w-3 h-3 block" aria-hidden="true" />
121127
<span class="sr-only">{{ $t('package.dependencies.view_vulnerabilities') }}</span>
122128
</NuxtLink>
129+
<NuxtLink
130+
v-if="getDeprecatedDepInfo(dep)"
131+
:to="{
132+
name: 'package',
133+
params: { package: [...dep.split('/'), 'v', getDeprecatedDepInfo(dep)!.version] },
134+
}"
135+
class="shrink-0 text-purple-500"
136+
:title="getDeprecatedDepInfo(dep)!.message"
137+
>
138+
<span class="i-carbon-warning-hex w-3 h-3 block" aria-hidden="true" />
139+
<span class="sr-only">{{ $t('package.deprecated.label') }}</span>
140+
</NuxtLink>
123141
<NuxtLink
124142
:to="{ name: 'package', params: { package: [...dep.split('/'), 'v', version] } }"
125143
class="font-mono text-xs text-right truncate"
Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
<script setup lang="ts">
2+
import type { DependencyDepth } from '#shared/types'
3+
4+
const props = defineProps<{
5+
packageName: string
6+
version: string
7+
}>()
8+
9+
const { data: analysisData, status } = useDependencyAnalysis(
10+
() => props.packageName,
11+
() => props.version,
12+
)
13+
14+
const isExpanded = shallowRef(false)
15+
const showAll = shallowRef(false)
16+
17+
const hasDeprecated = computed(
18+
() => analysisData.value && analysisData.value.deprecatedPackages.length > 0,
19+
)
20+
21+
// Banner color - purple for deprecated
22+
const bannerColor = 'border-purple-600/40 bg-purple-500/10 text-purple-700 dark:text-purple-400'
23+
24+
// Styling for each depth level
25+
const depthStyles = {
26+
root: {
27+
bg: 'bg-purple-500/5 border-l-2 border-l-purple-600',
28+
text: 'text-fg',
29+
},
30+
direct: {
31+
bg: 'bg-purple-500/5 border-l-2 border-l-purple-500',
32+
text: 'text-fg-muted',
33+
},
34+
transitive: {
35+
bg: 'bg-purple-500/5 border-l-2 border-l-purple-400',
36+
text: 'text-fg-muted',
37+
},
38+
} as const
39+
40+
function getDepthStyle(depth: DependencyDepth) {
41+
return depthStyles[depth] || depthStyles.transitive
42+
}
43+
</script>
44+
45+
<template>
46+
<section
47+
v-if="status === 'success' && hasDeprecated"
48+
aria-labelledby="deprecated-tree-heading"
49+
class="relative"
50+
>
51+
<div class="rounded-lg border overflow-hidden" :class="bannerColor">
52+
<!-- Header -->
53+
<button
54+
type="button"
55+
class="w-full flex items-center justify-between gap-3 px-4 py-3 text-left transition-colors duration-200 hover:bg-white/5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-fg/50"
56+
:aria-expanded="isExpanded"
57+
aria-controls="deprecated-tree-details"
58+
@click="isExpanded = !isExpanded"
59+
>
60+
<div class="flex items-center gap-2 min-w-0">
61+
<span class="i-carbon-warning-hex w-4 h-4 shrink-0" aria-hidden="true" />
62+
<span class="font-mono text-sm font-medium truncate">
63+
{{ $t('package.deprecated.tree_found', analysisData!.deprecatedPackages.length) }}
64+
</span>
65+
</div>
66+
<span
67+
class="i-carbon-chevron-down w-4 h-4 transition-transform duration-200 shrink-0"
68+
:class="{ 'rotate-180': isExpanded }"
69+
aria-hidden="true"
70+
/>
71+
</button>
72+
73+
<!-- Expandable details -->
74+
<div
75+
v-show="isExpanded"
76+
id="deprecated-tree-details"
77+
class="border-t border-border bg-bg-subtle"
78+
>
79+
<ul class="divide-y divide-border list-none m-0 p-0">
80+
<li
81+
v-for="pkg in analysisData!.deprecatedPackages.slice(0, showAll ? undefined : 5)"
82+
:key="`${pkg.name}@${pkg.version}`"
83+
class="px-4 py-3"
84+
:class="getDepthStyle(pkg.depth).bg"
85+
>
86+
<div class="flex items-center gap-2 mb-1">
87+
<!-- Path badge -->
88+
<DependencyPathPopup v-if="pkg.path && pkg.path.length > 1" :path="pkg.path" />
89+
90+
<NuxtLink
91+
:to="{
92+
name: 'package',
93+
params: { package: [...pkg.name.split('/'), 'v', pkg.version] },
94+
}"
95+
class="font-mono text-sm font-medium hover:underline truncate"
96+
:class="getDepthStyle(pkg.depth).text"
97+
>
98+
{{ pkg.name }}@{{ pkg.version }}
99+
</NuxtLink>
100+
</div>
101+
<p class="text-xs text-fg-muted m-0 line-clamp-2">
102+
{{ pkg.message }}
103+
</p>
104+
</li>
105+
</ul>
106+
107+
<button
108+
v-if="analysisData!.deprecatedPackages.length > 5 && !showAll"
109+
type="button"
110+
class="w-full px-4 py-2 text-xs font-mono text-fg-muted hover:text-fg border-t border-border transition-colors duration-200"
111+
@click="showAll = true"
112+
>
113+
{{
114+
$t('package.deprecated.show_all', { count: analysisData!.deprecatedPackages.length })
115+
}}
116+
</button>
117+
</div>
118+
</div>
119+
</section>
120+
</template>

app/components/PackageVersions.vue

Lines changed: 36 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -363,7 +363,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
363363
<div class="flex items-center justify-between gap-2">
364364
<NuxtLink
365365
:to="versionRoute(row.primaryVersion.version)"
366-
class="font-mono text-sm transition-colors duration-200 truncate"
366+
class="font-mono text-sm transition-colors duration-200 truncate inline-flex items-center gap-1"
367367
:class="
368368
row.primaryVersion.deprecated
369369
? 'text-red-400 hover:text-red-300'
@@ -377,6 +377,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
377377
: row.primaryVersion.version
378378
"
379379
>
380+
<span
381+
v-if="row.primaryVersion.deprecated"
382+
class="i-carbon-warning-hex w-3.5 h-3.5 shrink-0"
383+
aria-hidden="true"
384+
/>
380385
{{ row.primaryVersion.version }}
381386
</NuxtLink>
382387
<div class="flex items-center gap-2 shrink-0">
@@ -418,7 +423,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
418423
<div class="flex items-center justify-between gap-2">
419424
<NuxtLink
420425
:to="versionRoute(v.version)"
421-
class="font-mono text-xs transition-colors duration-200 truncate"
426+
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
422427
:class="
423428
v.deprecated
424429
? 'text-red-400 hover:text-red-300'
@@ -430,6 +435,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
430435
: v.version
431436
"
432437
>
438+
<span
439+
v-if="v.deprecated"
440+
class="i-carbon-warning-hex w-3 h-3 shrink-0"
441+
aria-hidden="true"
442+
/>
433443
{{ v.version }}
434444
</NuxtLink>
435445
<div class="flex items-center gap-2 shrink-0">
@@ -510,7 +520,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
510520
<div class="flex items-center justify-between gap-2">
511521
<NuxtLink
512522
:to="versionRoute(row.primaryVersion.version)"
513-
class="font-mono text-xs transition-colors duration-200 truncate"
523+
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
514524
:class="
515525
row.primaryVersion.deprecated
516526
? 'text-red-400 hover:text-red-300'
@@ -524,6 +534,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
524534
: row.primaryVersion.version
525535
"
526536
>
537+
<span
538+
v-if="row.primaryVersion.deprecated"
539+
class="i-carbon-warning-hex w-3 h-3 shrink-0"
540+
aria-hidden="true"
541+
/>
527542
{{ row.primaryVersion.version }}
528543
</NuxtLink>
529544
<div class="flex items-center gap-2 shrink-0">
@@ -580,7 +595,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
580595
<NuxtLink
581596
v-if="group.versions[0]?.version"
582597
:to="versionRoute(group.versions[0]?.version)"
583-
class="font-mono text-xs transition-colors duration-200 truncate"
598+
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
584599
:class="
585600
group.versions[0]?.deprecated
586601
? 'text-red-400 hover:text-red-300'
@@ -594,6 +609,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
594609
: group.versions[0]?.version
595610
"
596611
>
612+
<span
613+
v-if="group.versions[0]?.deprecated"
614+
class="i-carbon-warning-hex w-3 h-3 shrink-0"
615+
aria-hidden="true"
616+
/>
597617
{{ group.versions[0]?.version }}
598618
</NuxtLink>
599619
</div>
@@ -636,7 +656,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
636656
<NuxtLink
637657
v-if="group.versions[0]?.version"
638658
:to="versionRoute(group.versions[0]?.version)"
639-
class="font-mono text-xs transition-colors duration-200 truncate"
659+
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
640660
:class="
641661
group.versions[0]?.deprecated
642662
? 'text-red-400 hover:text-red-300'
@@ -650,6 +670,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
650670
: group.versions[0]?.version
651671
"
652672
>
673+
<span
674+
v-if="group.versions[0]?.deprecated"
675+
class="i-carbon-warning-hex w-3 h-3 shrink-0"
676+
aria-hidden="true"
677+
/>
653678
{{ group.versions[0]?.version }}
654679
</NuxtLink>
655680
</div>
@@ -690,7 +715,7 @@ function getTagVersions(tag: string): VersionDisplay[] {
690715
<div class="flex items-center justify-between gap-2">
691716
<NuxtLink
692717
:to="versionRoute(v.version)"
693-
class="font-mono text-xs transition-colors duration-200 truncate"
718+
class="font-mono text-xs transition-colors duration-200 truncate inline-flex items-center gap-1"
694719
:class="
695720
v.deprecated
696721
? 'text-red-400 hover:text-red-300'
@@ -702,6 +727,11 @@ function getTagVersions(tag: string): VersionDisplay[] {
702727
: v.version
703728
"
704729
>
730+
<span
731+
v-if="v.deprecated"
732+
class="i-carbon-warning-hex w-3 h-3 shrink-0"
733+
aria-hidden="true"
734+
/>
705735
{{ v.version }}
706736
</NuxtLink>
707737
<div class="flex items-center gap-2 shrink-0">

app/components/PackageVulnerabilityTree.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const {
1111
data: vulnTree,
1212
status,
1313
fetch: fetchVulnTree,
14-
} = useVulnerabilityTree(
14+
} = useDependencyAnalysis(
1515
() => props.packageName,
1616
() => props.version,
1717
)
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,18 @@
1-
import type { VulnerabilityTreeResult } from '#shared/types/osv'
1+
import type { VulnerabilityTreeResult } from '#shared/types/dependency-analysis'
22

33
/**
4-
* Shared composable for vulnerability tree data.
4+
* Shared composable for dependency analysis data (vulnerabilities, deprecated packages).
55
* Fetches once and caches the result so multiple components can use it.
6+
* Before: useVulnerabilityTree - but now we use this for both vulnerabilities and deprecated packages.
67
*/
7-
export function useVulnerabilityTree(
8+
export function useDependencyAnalysis(
89
packageName: MaybeRefOrGetter<string>,
910
version: MaybeRefOrGetter<string>,
1011
) {
1112
// Build a stable key from the current values
1213
const name = toValue(packageName)
1314
const ver = toValue(version)
14-
const key = `vuln-tree:v1:${name}@${ver}`
15+
const key = `dep-analysis:v1:${name}@${ver}`
1516

1617
// Use useState for SSR-safe caching across components
1718
const data = useState<VulnerabilityTreeResult | null>(key, () => null)
@@ -37,7 +38,7 @@ export function useVulnerabilityTree(
3738
data.value = result
3839
status.value = 'success'
3940
} catch (e) {
40-
error.value = e instanceof Error ? e : new Error('Failed to fetch vulnerabilities')
41+
error.value = e instanceof Error ? e : new Error('Failed to fetch dependency analysis')
4142
status.value = 'error'
4243
}
4344
}

app/pages/[...package].vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -85,13 +85,13 @@ const displayVersion = computed(() => {
8585
return pkg.value.versions[latestTag] ?? null
8686
})
8787
88-
// Fetch vulnerability tree (lazy, client-side)
89-
// This is the same composable used by PackageVulnerabilityTree
88+
// Fetch dependency analysis (lazy, client-side)
89+
// This is the same composable used by PackageVulnerabilityTree and PackageDeprecatedTree
9090
const {
9191
data: vulnTree,
9292
status: vulnTreeStatus,
9393
fetch: fetchVulnTree,
94-
} = useVulnerabilityTree(packageName, () => displayVersion.value?.version ?? '')
94+
} = useDependencyAnalysis(packageName, () => displayVersion.value?.version ?? '')
9595
onMounted(() => {
9696
// Fetch vulnerability tree once displayVersion is available
9797
if (displayVersion.value) {
@@ -1191,6 +1191,12 @@ defineOgImageComponent('Package', {
11911191
:package-name="pkg.name"
11921192
:version="displayVersion.version"
11931193
/>
1194+
<PackageDeprecatedTree
1195+
v-if="displayVersion"
1196+
:package-name="pkg.name"
1197+
:version="displayVersion.version"
1198+
class="mt-3"
1199+
/>
11941200
</ClientOnly>
11951201
</div>
11961202

i18n/locales/de-DE.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@
271271
"transitive": "Transitive Abhängigkeit"
272272
}
273273
},
274+
"deprecated": {
275+
"label": "Veraltet",
276+
"tree_found": "{count} veraltete Abhängigkeit | {count} veraltete Abhängigkeiten",
277+
"show_all": "alle {count} veralteten Pakete anzeigen"
278+
},
274279
"access": {
275280
"title": "Team-Zugriff",
276281
"refresh": "Team-Zugriff aktualisieren",

i18n/locales/en.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,11 @@
276276
"low": "low"
277277
}
278278
},
279+
"deprecated": {
280+
"label": "Deprecated",
281+
"tree_found": "{count} deprecated dependency | {count} deprecated dependencies",
282+
"show_all": "show all {count} deprecated packages"
283+
},
279284
"access": {
280285
"title": "Team Access",
281286
"refresh": "Refresh team access",

0 commit comments

Comments
 (0)