(
rootName: string,
rootVersion: string,
options: { trackDepth?: boolean } = {},
)
| 113 | * Uses level-by-level BFS to ensure correct depth assignment when trackDepth is enabled. |
| 114 | */ |
| 115 | export async function resolveDependencyTree( |
| 116 | rootName: string, |
| 117 | rootVersion: string, |
| 118 | options: { trackDepth?: boolean } = {}, |
| 119 | ): Promise<Map<string, ResolvedPackage>> { |
| 120 | const resolved = new Map<string, ResolvedPackage>() |
| 121 | const seen = new Set<string>() |
| 122 | |
| 123 | // Process level by level for correct depth tracking |
| 124 | // Each entry includes the path of package names leading to this dependency |
| 125 | let currentLevel = new Map<string, { range: string; optional: boolean; path: string[] }>([ |
| 126 | [rootName, { range: rootVersion, optional: false, path: [] }], |
| 127 | ]) |
| 128 | let level = 0 |
| 129 | |
| 130 | while (currentLevel.size > 0) { |
| 131 | const nextLevel = new Map<string, { range: string; optional: boolean; path: string[] }>() |
| 132 | |
| 133 | // Mark all packages in current level as seen before processing |
| 134 | for (const name of currentLevel.keys()) { |
| 135 | seen.add(name) |
| 136 | } |
| 137 | |
| 138 | // Process current level with concurrency limit |
| 139 | const entries = [...currentLevel.entries()] |
| 140 | await mapWithConcurrency( |
| 141 | entries, |
| 142 | async ([name, { range, optional, path }]) => { |
| 143 | const packument = await fetchPackument(name) |
| 144 | if (!packument) return |
| 145 | |
| 146 | const versions = Object.keys(packument.versions) |
| 147 | const version = resolveVersion(range, versions) |
| 148 | if (!version) return |
| 149 | |
| 150 | const versionData = packument.versions[version] |
| 151 | if (!versionData) return |
| 152 | |
| 153 | if (!matchesPlatform(versionData)) return |
| 154 | |
| 155 | const size = (versionData.dist as { unpackedSize?: number })?.unpackedSize ?? 0 |
| 156 | const tarballUrl = versionData.dist?.tarball ?? '' |
| 157 | const key = `${name}@${version}` |
| 158 | |
| 159 | // Build path for this package (path to parent + this package with version) |
| 160 | const currentPath = [...path, `${name}@${version}`] |
| 161 | |
| 162 | if (!resolved.has(key)) { |
| 163 | const pkg: ResolvedPackage = { name, version, size, tarballUrl, optional } |
| 164 | if (options.trackDepth) { |
| 165 | pkg.depth = level === 0 ? 'root' : level === 1 ? 'direct' : 'transitive' |
| 166 | pkg.path = currentPath |
| 167 | } |
| 168 | if (versionData.deprecated) { |
| 169 | pkg.deprecated = versionData.deprecated |
| 170 | } |
| 171 | resolved.set(key, pkg) |
| 172 | } |
no test coverage detected