@@ -85,6 +85,28 @@ 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
90+ const {
91+ data : vulnTree,
92+ status : vulnTreeStatus,
93+ fetch : fetchVulnTree,
94+ } = useVulnerabilityTree (packageName , () => displayVersion .value ?.version ?? ' ' )
95+ onMounted (() => {
96+ // Fetch vulnerability tree once displayVersion is available
97+ if (displayVersion .value ) {
98+ fetchVulnTree ()
99+ }
100+ })
101+ watch (
102+ () => displayVersion .value ?.version ,
103+ () => {
104+ if (displayVersion .value ) {
105+ fetchVulnTree ()
106+ }
107+ },
108+ )
109+
88110// Keep latestVersion for comparison (to show "(latest)" badge)
89111const latestVersion = computed (() => {
90112 if (! pkg .value ) return null
@@ -131,6 +153,22 @@ const hasDependencies = computed(() => {
131153 )
132154})
133155
156+ // Vulnerability count for the stats banner
157+ const vulnCount = computed (() => vulnTree .value ?.totalCounts .total ?? 0 )
158+ const hasVulnerabilities = computed (() => vulnCount .value > 0 )
159+
160+ // Total transitive dependencies count (from either vuln tree or install size)
161+ // Subtract 1 to exclude the root package itself
162+ const totalDepsCount = computed (() => {
163+ if (vulnTree .value ) {
164+ return vulnTree .value .totalPackages - 1
165+ }
166+ if (installSize .value ) {
167+ return installSize .value .dependencyCount
168+ }
169+ return null
170+ })
171+
134172const repositoryUrl = computed (() => {
135173 const repo = displayVersion .value ?.repository
136174 if (! repo ?.url ) return null
@@ -728,7 +766,7 @@ defineOgImageComponent('Package', {
728766
729767 <!-- Stats grid -->
730768 <dl
731- class =" grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-3 sm:gap-4 py-4 sm:py-6 mt-4 sm:mt-6 border-t border-border"
769+ class =" grid grid-cols-2 sm:grid-cols-5 gap-3 sm:gap-4 py-4 sm:py-6 mt-4 sm:mt-6 border-t border-border"
732770 >
733771 <div v-if =" pkg.license" class =" space-y-1" >
734772 <dt class =" text-xs text-fg-subtle uppercase tracking-wider" >
@@ -744,7 +782,31 @@ defineOgImageComponent('Package', {
744782 {{ $t('package.stats.deps') }}
745783 </dt >
746784 <dd class =" font-mono text-sm text-fg flex items-center justify-start gap-2" >
747- {{ getDependencyCount(displayVersion) }}
785+ <!-- Direct deps (muted) -->
786+ <span class =" text-fg-muted" >{{ getDependencyCount(displayVersion) }}</span >
787+
788+ <!-- Separator and total transitive deps -->
789+ <span class =" text-fg-subtle mx-1" >/</span >
790+
791+ <ClientOnly >
792+ <span
793+ v-if ="
794+ vulnTreeStatus === 'pending' || (installSizeStatus === 'pending' && !vulnTree)
795+ "
796+ class =" inline-flex items-center gap-1 text-fg-subtle"
797+ >
798+ <span
799+ class =" i-carbon-circle-dash w-3 h-3 motion-safe:animate-spin"
800+ aria-hidden =" true"
801+ />
802+ </span >
803+ <span v-else-if =" totalDepsCount !== null" >{{ totalDepsCount }}</span >
804+ <span v-else class =" text-fg-subtle" >-</span >
805+ <template #fallback >
806+ <span class =" text-fg-subtle" >-</span >
807+ </template >
808+ </ClientOnly >
809+
748810 <a
749811 v-if =" getDependencyCount(displayVersion) > 0"
750812 :href =" `https://npmgraph.js.org/?q=${pkg.name}`"
@@ -811,6 +873,42 @@ defineOgImageComponent('Package', {
811873 </dd >
812874 </div >
813875
876+ <!-- Vulnerabilities count -->
877+ <ClientOnly >
878+ <div class =" space-y-1" >
879+ <dt class =" text-xs text-fg-subtle uppercase tracking-wider" >
880+ {{ $t('package.stats.vulns') }}
881+ </dt >
882+ <dd class =" font-mono text-sm text-fg" >
883+ <span
884+ v-if =" vulnTreeStatus === 'pending' || vulnTreeStatus === 'idle'"
885+ class =" inline-flex items-center gap-1 text-fg-subtle"
886+ >
887+ <span
888+ class =" i-carbon-circle-dash w-3 h-3 motion-safe:animate-spin"
889+ aria-hidden =" true"
890+ />
891+ </span >
892+ <span v-else-if =" vulnTreeStatus === 'success'" >
893+ <span v-if =" hasVulnerabilities" class =" text-amber-500" >{{ vulnCount }}</span >
894+ <span v-else class =" inline-flex items-center gap-1 text-fg-muted" >
895+ <span class =" i-carbon-checkmark w-3 h-3" aria-hidden =" true" />
896+ 0
897+ </span >
898+ </span >
899+ <span v-else class =" text-fg-subtle" >-</span >
900+ </dd >
901+ </div >
902+ <template #fallback >
903+ <div class =" space-y-1" >
904+ <dt class =" text-xs text-fg-subtle uppercase tracking-wider" >
905+ {{ $t('package.stats.vulns') }}
906+ </dt >
907+ <dd class =" font-mono text-sm text-fg-subtle" >-</dd >
908+ </div >
909+ </template >
910+ </ClientOnly >
911+
814912 <div v-if =" pkg.time?.modified" class =" space-y-1" >
815913 <dt class =" text-xs text-fg-subtle uppercase tracking-wider" >
816914 {{ $t('package.stats.updated') }}
0 commit comments