Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 23 additions & 16 deletions server/utils/dependency-analysis.ts
Original file line number Diff line number Diff line change
Expand Up @@ -325,13 +325,28 @@ export const analyzeDependencyTree = defineCachedFunction(
})

// Aggregate total counts
const totalCounts = { total: 0, critical: 0, high: 0, moderate: 0, low: 0 }
for (const pkg of vulnerablePackages) {
totalCounts.total += pkg.counts.total
totalCounts.critical += pkg.counts.critical
totalCounts.high += pkg.counts.high
totalCounts.moderate += pkg.counts.moderate
totalCounts.low += pkg.counts.low
// Count how many native optional packages were included, to exclude them from the total
let nativeOptionalCount = 0
for (const pkg of resolved.values()) {
if (pkg.optional && pkg.isNative) {
nativeOptionalCount++
}
}

const result: VulnerabilityTreeResult = {
package: name,
version,
vulnerablePackages,
deprecatedPackages,
totalCounts: {
total: vulnerablePackages.reduce((sum, pkg) => sum + pkg.counts.total, 0),
critical: vulnerablePackages.reduce((sum, pkg) => sum + pkg.counts.critical, 0),
high: vulnerablePackages.reduce((sum, pkg) => sum + pkg.counts.high, 0),
moderate: vulnerablePackages.reduce((sum, pkg) => sum + pkg.counts.moderate, 0),
low: vulnerablePackages.reduce((sum, pkg) => sum + pkg.counts.low, 0),
},
totalPackages: resolved.size - nativeOptionalCount,
failedQueries,
}

// Log if batch query failed entirely
Expand All @@ -342,15 +357,7 @@ export const analyzeDependencyTree = defineCachedFunction(
)
}

return {
package: name,
version,
vulnerablePackages,
deprecatedPackages,
totalPackages: packages.length,
failedQueries,
totalCounts,
}
return result
},
{
maxAge: 60 * 60,
Expand Down
51 changes: 43 additions & 8 deletions server/utils/dependency-resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ export interface ResolvedPackage {
size: number
tarballUrl: string
optional: boolean
isNative?: boolean
/** Depth level (only when trackDepth is enabled) */
depth?: DependencyDepth
/** Dependency path from root (only when trackDepth is enabled) */
Expand All @@ -122,13 +123,17 @@ export async function resolveDependencyTree(

// Process level by level for correct depth tracking
// Each entry includes the path of package names leading to this dependency
let currentLevel = new Map<string, { range: string; optional: boolean; path: string[] }>([
[rootName, { range: rootVersion, optional: false, path: [] }],
])
let currentLevel = new Map<
string,
{ range: string; optional: boolean; isNativeParent?: boolean; path: string[] }
>([[rootName, { range: rootVersion, optional: false, isNativeParent: false, path: [] }]])
let level = 0

while (currentLevel.size > 0) {
const nextLevel = new Map<string, { range: string; optional: boolean; path: string[] }>()
const nextLevel = new Map<
string,
{ range: string; optional: boolean; isNativeParent?: boolean; path: string[] }
>()

// Mark all packages in current level as seen before processing
for (const name of currentLevel.keys()) {
Expand All @@ -139,7 +144,7 @@ export async function resolveDependencyTree(
const entries = [...currentLevel.entries()]
await mapWithConcurrency(
entries,
async ([name, { range, optional, path }]) => {
async ([name, { range, optional: parentOptional, isNativeParent, path }]) => {
const packument = await fetchPackument(name)
if (!packument) return

Expand All @@ -156,11 +161,30 @@ export async function resolveDependencyTree(
const tarballUrl = versionData.dist?.tarball ?? ''
const key = `${name}@${version}`

const libc = (versionData as { libc?: unknown }).libc
const selfIsNative = !!(
(versionData.os && Array.isArray(versionData.os) && versionData.os.length > 0) ||
(versionData.cpu && Array.isArray(versionData.cpu) && versionData.cpu.length > 0) ||
(Array.isArray(libc) && libc.length > 0) ||
name.includes('-wasm32-') ||
name.endsWith('-wasm32') ||
name.includes('wasm32-wasi')
)
const isNative = selfIsNative || isNativeParent
const optional = parentOptional

// Build path for this package (path to parent + this package with version)
const currentPath = [...path, `${name}@${version}`]

if (!resolved.has(key)) {
const pkg: ResolvedPackage = { name, version, size, tarballUrl, optional }
const pkg: ResolvedPackage = {
name,
version,
size,
tarballUrl,
optional,
isNative: isNative || undefined,
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (options.trackDepth) {
pkg.depth = level === 0 ? 'root' : level === 1 ? 'direct' : 'transitive'
pkg.path = currentPath
Expand All @@ -175,7 +199,13 @@ export async function resolveDependencyTree(
if (versionData.dependencies) {
for (const [depName, depRange] of Object.entries(versionData.dependencies)) {
if (!seen.has(depName) && !nextLevel.has(depName)) {
nextLevel.set(depName, { range: depRange, optional: false, path: currentPath })
// Inherit optional and isNative flags from parent if they are truthy
nextLevel.set(depName, {
range: depRange,
optional: optional,
isNativeParent: isNative,
path: currentPath,
})
}
}
}
Expand All @@ -184,7 +214,12 @@ export async function resolveDependencyTree(
if (versionData.optionalDependencies) {
for (const [depName, depRange] of Object.entries(versionData.optionalDependencies)) {
if (!seen.has(depName) && !nextLevel.has(depName)) {
nextLevel.set(depName, { range: depRange, optional: true, path: currentPath })
nextLevel.set(depName, {
range: depRange,
optional: true,
isNativeParent: isNative,
path: currentPath,
})
}
}
}
Expand Down
8 changes: 6 additions & 2 deletions server/utils/install-size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,13 @@ export const calculateInstallSize = defineCachedFunction(
size: dep.size,
tarballUrl: dep.tarballUrl,
optional: dep.optional || undefined,
isNative: dep.isNative || undefined,
})
totalSize += dep.size
dependencyCount++
// Do not count optional native packages as actual dependencies
if (!(dep.optional && dep.isNative)) {
totalSize += dep.size
dependencyCount++
}
}

// Sort by size descending
Expand Down
2 changes: 2 additions & 0 deletions shared/types/install-size.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export interface DependencySize {
tarballUrl: string
/** True if this is an optional dependency */
optional?: boolean
/** True if this is a native package (os/cpu specific) */
isNative?: boolean
}

export interface InstallSizeResult {
Expand Down
Loading