11<script setup lang="ts">
2+ import { SEVERITY_LEVELS } from ' ~~/shared/types'
3+
24const props = defineProps <{
35 packageName: string
46 dependencies? : Record <string , string >
@@ -10,6 +12,9 @@ const props = defineProps<{
1012// Fetch outdated info for dependencies
1113const outdatedDeps = useOutdatedDependencies (() => props .dependencies )
1214
15+ // Fetch vulnerability info for dependencies
16+ const vulnerableDeps = useVulnerableDependencies (() => props .dependencies )
17+
1318// Expanded state for each section
1419const depsExpanded = shallowRef (false )
1520const peerDepsExpanded = shallowRef (false )
@@ -43,10 +48,65 @@ const sortedOptionalDependencies = computed(() => {
4348 if (! props .optionalDependencies ) return []
4449 return Object .entries (props .optionalDependencies ).sort (([a ], [b ]) => a .localeCompare (b ))
4550})
51+
52+ // Vulnerability summary for banner
53+ const vulnerabilitySummary = computed (() => {
54+ const deps = Object .values (vulnerableDeps .value )
55+ if (deps .length === 0 ) return null
56+
57+ const counts = { critical: 0 , high: 0 , moderate: 0 , low: 0 }
58+ let total = 0
59+
60+ for (const info of deps ) {
61+ if (! info ?.counts ) continue
62+ total += info .counts .total || 0
63+ for (const s of SEVERITY_LEVELS ) counts [s ] += info .counts [s ] || 0
64+ }
65+
66+ const severity = SEVERITY_LEVELS .find (s => counts [s ] > 0 ) || ' low'
67+
68+ return { affectedDeps: deps .length , totalVulns: total , severity , counts }
69+ })
70+
71+ const vulnBreakdownText = computed (() => {
72+ if (! vulnerabilitySummary .value ) return ' '
73+ const { counts } = vulnerabilitySummary .value
74+ return SEVERITY_LEVELS .filter (s => counts [s ])
75+ .map (s => ` ${counts [s ]} ${s } ` )
76+ .join (' , ' )
77+ })
4678 </script >
4779
4880<template >
4981 <div class =" space-y-8" >
82+ <!-- Vulnerability warning banner -->
83+ <div
84+ v-if =" vulnerabilitySummary"
85+ role =" alert"
86+ class =" rounded-lg border px-4 py-3 cursor-help"
87+ :class =" {
88+ 'border-red-500/30 bg-red-500/10 text-red-400':
89+ vulnerabilitySummary.severity === 'critical',
90+ 'border-orange-500/30 bg-orange-500/10 text-orange-400':
91+ vulnerabilitySummary.severity === 'high',
92+ 'border-yellow-500/30 bg-yellow-500/10 text-yellow-400':
93+ vulnerabilitySummary.severity === 'moderate',
94+ 'border-blue-500/30 bg-blue-500/10 text-blue-400': vulnerabilitySummary.severity === 'low',
95+ }"
96+ :title =" `${vulnerabilitySummary.affectedDeps} ${vulnerabilitySummary.affectedDeps === 1 ? 'dependency' : 'dependencies'} affected`"
97+ >
98+ <div class =" flex items-center gap-2" >
99+ <span class =" i-carbon-security w-4 h-4 shrink-0" aria-hidden =" true" />
100+ <div >
101+ <div class =" font-mono text-sm" >
102+ {{ vulnerabilitySummary.totalVulns }}
103+ {{ vulnerabilitySummary.totalVulns === 1 ? 'vulnerability' : 'vulnerabilities' }}
104+ </div >
105+ <div class =" font-mono text-xs opacity-70" >{{ vulnBreakdownText }}</div >
106+ </div >
107+ </div >
108+ </div >
109+
50110 <!-- Dependencies -->
51111 <section v-if =" sortedDependencies.length > 0" aria-labelledby =" dependencies-heading" >
52112 <h2 id =" dependencies-heading" class =" text-xs text-fg-subtle uppercase tracking-wider mb-3" >
@@ -74,6 +134,19 @@ const sortedOptionalDependencies = computed(() => {
74134 >
75135 <span class =" i-carbon-warning-alt w-3 h-3 block" />
76136 </span >
137+ <NuxtLink
138+ v-if =" vulnerableDeps[dep]?.version"
139+ :to =" {
140+ name: 'package',
141+ params: { package: [...dep.split('/'), 'v', vulnerableDeps[dep].version] },
142+ }"
143+ class =" shrink-0"
144+ :class =" getVulnerabilitySeverityClass(vulnerableDeps[dep])"
145+ :title =" getVulnerabilityTooltip(vulnerableDeps[dep])"
146+ >
147+ <span class =" i-carbon-security w-3 h-3 block" aria-hidden =" true" />
148+ <span class =" sr-only" >View vulnerabilities</span >
149+ </NuxtLink >
77150 <NuxtLink
78151 :to =" { name: 'package', params: { package: [...dep.split('/'), 'v', version] } }"
79152 class =" font-mono text-xs text-right truncate"
@@ -85,6 +158,9 @@ const sortedOptionalDependencies = computed(() => {
85158 <span v-if =" outdatedDeps[dep]" class =" sr-only" >
86159 ({{ getOutdatedTooltip(outdatedDeps[dep]) }})
87160 </span >
161+ <span v-if =" vulnerableDeps[dep]" class =" sr-only" >
162+ ({{ getVulnerabilityTooltip(vulnerableDeps[dep]) }})
163+ </span >
88164 </span >
89165 </li >
90166 </ul >
0 commit comments