diff --git a/app/components/Package/Dependencies.vue b/app/components/Package/Dependencies.vue index 817aa2458e..6d8778371d 100644 --- a/app/components/Package/Dependencies.vue +++ b/app/components/Package/Dependencies.vue @@ -37,6 +37,17 @@ function getDeprecatedDepInfo(depName: string) { return vulnTree.value.deprecatedPackages.find(p => p.name === depName && p.depth === 'direct') } +// Cache URL dependency lookups with computed map +const urlDepMap = computed(() => { + if (!vulnTree.value) return new Map() + return new Map(vulnTree.value.urlDependencies.map(dep => [dep.name, dep])) +}) + +// Check if a dependency uses git: or https: URL +function getUrlDepInfo(depName: string) { + return urlDepMap.value.get(depName) ?? null +} + // Expanded state for each section const depsExpanded = shallowRef(false) const peerDepsExpanded = shallowRef(false) @@ -73,6 +84,8 @@ const sortedOptionalDependencies = computed(() => { // Get version tooltip function getDepVersionTooltip(dep: string, version: string) { + const urlDep = getUrlDepInfo(dep) + if (urlDep) return urlDep.url const outdated = outdatedDeps.value[dep] if (outdated) return getOutdatedTooltip(outdated, t) if (getVulnerableDepInfo(dep) || getDeprecatedDepInfo(dep)) return version @@ -82,6 +95,7 @@ function getDepVersionTooltip(dep: string, version: string) { // Get version class function getDepVersionClass(dep: string) { + if (getUrlDepInfo(dep)) return 'text-orange-700 dark:text-orange-500' const outdated = outdatedDeps.value[dep] if (outdated) return getVersionClass(outdated) if (getVulnerableDepInfo(dep) || getDeprecatedDepInfo(dep)) return getVersionClass(undefined) @@ -164,6 +178,19 @@ const numberFormatter = useNumberFormatter() > {{ $t('package.deprecated.label') }} + + + { + try { + const packument = await fetchNpmPackage(name) + const versionData = packument.versions[version] + if (!versionData) return [] + + const urlDeps: UrlDependencyInfo[] = [] + // Include devDependencies only for the root package + const allDeps = depth === 'root' + ? { + ...versionData.dependencies, + ...versionData.optionalDependencies, + ...versionData.devDependencies, + } + : { + ...versionData.dependencies, + ...versionData.optionalDependencies, + } + + // URL dependencies are children of the current package, so their depth is one level deeper + const dependencyDepth: DependencyDepth = depth === 'root' ? 'direct' : 'transitive' + + for (const [depName, depUrl] of Object.entries(allDeps || {})) { + if (isUrlDependency(depUrl)) { + urlDeps.push({ + name: depName, + url: depUrl, + depth: dependencyDepth, + path: [...path, `${depName}@${depUrl}`], + }) + } + } + + return urlDeps + } catch (error) { + // oxlint-disable-next-line no-console -- log URL dependency scan failures for debugging + console.warn(`[dep-analysis] URL dependency scan failed for ${name}@${version}:`, error) + return [] + } +} + /** * Analyze entire dependency tree for vulnerabilities and deprecated packages. * Uses OSV batch API for efficient vulnerability discovery, then fetches @@ -289,6 +353,14 @@ export const analyzeDependencyTree = defineCachedFunction( return depthOrder[a.depth] - depthOrder[b.depth] }) + // Scan for git: and https: URL dependencies in all packages + const urlDepResults = await mapWithConcurrency( + packages, + pkg => scanUrlDependencies(pkg.name, pkg.version, pkg.depth, pkg.path), + OSV_DETAIL_CONCURRENCY, + ) + const urlDependencies = urlDepResults.flat() + // Step 1: Use batch API to find which packages have vulnerabilities // This is much faster than individual queries - one request for all packages const { vulnerableIndices, failed: batchFailed } = await queryOsvBatch(packages) @@ -347,6 +419,7 @@ export const analyzeDependencyTree = defineCachedFunction( version, vulnerablePackages, deprecatedPackages, + urlDependencies, totalPackages: packages.length, failedQueries, totalCounts, @@ -356,6 +429,6 @@ export const analyzeDependencyTree = defineCachedFunction( maxAge: 60 * 60, swr: true, name: 'dependency-analysis', - getKey: (name: string, version: string) => `v2:${name}@${version}`, + getKey: (name: string, version: string) => `v3:${name}@${version}`, }, ) diff --git a/shared/types/dependency-analysis.ts b/shared/types/dependency-analysis.ts index a733c302e5..7c51b63cfb 100644 --- a/shared/types/dependency-analysis.ts +++ b/shared/types/dependency-analysis.ts @@ -180,6 +180,19 @@ export interface DeprecatedPackageInfo { message: string } +/** + * URL-based dependency info (git:, https:) + */ +export interface UrlDependencyInfo { + name: string + /** The git: or https: URL */ + url: string + /** Depth in dependency tree: root (0), direct (1), transitive (2+) */ + depth: DependencyDepth + /** Dependency path from root package */ + path: string[] +} + /** * Result of dependency tree analysis */ @@ -192,6 +205,8 @@ export interface VulnerabilityTreeResult { vulnerablePackages: PackageVulnerabilityInfo[] /** All deprecated packages in the tree */ deprecatedPackages: DeprecatedPackageInfo[] + /** All dependencies using git: or https: URLs */ + urlDependencies: UrlDependencyInfo[] /** Total packages analyzed */ totalPackages: number /** Number of packages that could not be checked (OSV query failed) */