@@ -83,6 +83,28 @@ const displayVersion = computed(() => {
8383 return pkg .value .versions [latestTag ] ?? null
8484})
8585
86+ // Fetch vulnerability tree (lazy, client-side)
87+ // This is the same composable used by PackageVulnerabilityTree
88+ const {
89+ data : vulnTree,
90+ status : vulnTreeStatus,
91+ fetch : fetchVulnTree,
92+ } = useVulnerabilityTree (packageName , () => displayVersion .value ?.version ?? ' ' )
93+ onMounted (() => {
94+ // Fetch vulnerability tree once displayVersion is available
95+ if (displayVersion .value ) {
96+ fetchVulnTree ()
97+ }
98+ })
99+ watch (
100+ () => displayVersion .value ?.version ,
101+ () => {
102+ if (displayVersion .value ) {
103+ fetchVulnTree ()
104+ }
105+ },
106+ )
107+
86108// Keep latestVersion for comparison (to show "(latest)" badge)
87109const latestVersion = computed (() => {
88110 if (! pkg .value ) return null
@@ -129,6 +151,22 @@ const hasDependencies = computed(() => {
129151 )
130152})
131153
154+ // Vulnerability count for the stats banner
155+ const vulnCount = computed (() => vulnTree .value ?.totalCounts .total ?? 0 )
156+ const hasVulnerabilities = computed (() => vulnCount .value > 0 )
157+
158+ // Total transitive dependencies count (from either vuln tree or install size)
159+ // Subtract 1 to exclude the root package itself
160+ const totalDepsCount = computed (() => {
161+ if (vulnTree .value ) {
162+ return vulnTree .value .totalPackages - 1
163+ }
164+ if (installSize .value ) {
165+ return installSize .value .dependencyCount
166+ }
167+ return null
168+ })
169+
132170const repositoryUrl = computed (() => {
133171 const repo = displayVersion .value ?.repository
134172 if (! repo ?.url ) return null
@@ -726,7 +764,7 @@ defineOgImageComponent('Package', {
726764
727765 <!-- Stats grid -->
728766 <dl
729- 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"
767+ 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"
730768 >
731769 <div v-if =" pkg.license" class =" space-y-1" >
732770 <dt class =" text-xs text-fg-subtle uppercase tracking-wider" >
@@ -742,7 +780,31 @@ defineOgImageComponent('Package', {
742780 {{ $t('package.stats.deps') }}
743781 </dt >
744782 <dd class =" font-mono text-sm text-fg flex items-center justify-start gap-2" >
745- {{ getDependencyCount(displayVersion) }}
783+ <!-- Direct deps (muted) -->
784+ <span class =" text-fg-muted" >{{ getDependencyCount(displayVersion) }}</span >
785+
786+ <!-- Separator and total transitive deps -->
787+ <span class =" text-fg-subtle mx-1" >/</span >
788+
789+ <ClientOnly >
790+ <span
791+ v-if ="
792+ vulnTreeStatus === 'pending' || (installSizeStatus === 'pending' && !vulnTree)
793+ "
794+ class =" inline-flex items-center gap-1 text-fg-subtle"
795+ >
796+ <span
797+ class =" i-carbon-circle-dash w-3 h-3 motion-safe:animate-spin"
798+ aria-hidden =" true"
799+ />
800+ </span >
801+ <span v-else-if =" totalDepsCount !== null" >{{ totalDepsCount }}</span >
802+ <span v-else class =" text-fg-subtle" >-</span >
803+ <template #fallback >
804+ <span class =" text-fg-subtle" >-</span >
805+ </template >
806+ </ClientOnly >
807+
746808 <a
747809 v-if =" getDependencyCount(displayVersion) > 0"
748810 :href =" `https://npmgraph.js.org/?q=${pkg.name}`"
@@ -809,6 +871,42 @@ defineOgImageComponent('Package', {
809871 </dd >
810872 </div >
811873
874+ <!-- Vulnerabilities count -->
875+ <ClientOnly >
876+ <div class =" space-y-1" >
877+ <dt class =" text-xs text-fg-subtle uppercase tracking-wider" >
878+ {{ $t('package.stats.vulns') }}
879+ </dt >
880+ <dd class =" font-mono text-sm text-fg" >
881+ <span
882+ v-if =" vulnTreeStatus === 'pending' || vulnTreeStatus === 'idle'"
883+ class =" inline-flex items-center gap-1 text-fg-subtle"
884+ >
885+ <span
886+ class =" i-carbon-circle-dash w-3 h-3 motion-safe:animate-spin"
887+ aria-hidden =" true"
888+ />
889+ </span >
890+ <span v-else-if =" vulnTreeStatus === 'success'" >
891+ <span v-if =" hasVulnerabilities" class =" text-amber-500" >{{ vulnCount }}</span >
892+ <span v-else class =" inline-flex items-center gap-1 text-fg-muted" >
893+ <span class =" i-carbon-checkmark w-3 h-3" aria-hidden =" true" />
894+ 0
895+ </span >
896+ </span >
897+ <span v-else class =" text-fg-subtle" >-</span >
898+ </dd >
899+ </div >
900+ <template #fallback >
901+ <div class =" space-y-1" >
902+ <dt class =" text-xs text-fg-subtle uppercase tracking-wider" >
903+ {{ $t('package.stats.vulns') }}
904+ </dt >
905+ <dd class =" font-mono text-sm text-fg-subtle" >-</dd >
906+ </div >
907+ </template >
908+ </ClientOnly >
909+
812910 <div v-if =" pkg.time?.modified" class =" space-y-1" >
813911 <dt class =" text-xs text-fg-subtle uppercase tracking-wider" >
814912 {{ $t('package.stats.updated') }}
0 commit comments