pnpm
134 строки · 4.7 Кб
1import path from 'path'2import npa from '@pnpm/npm-package-arg'3import { resolveWorkspaceRange } from '@pnpm/resolve-workspace-range'4import { parsePref, workspacePrefToNpm } from '@pnpm/npm-resolver'5import { type BaseManifest } from '@pnpm/types'6import mapValues from 'ramda/src/map'7
8export interface Package {9manifest: BaseManifest10dir: string11}
12
13export interface PackageNode<Pkg extends Package> {14package: Pkg15dependencies: string[]16}
17
18export function createPkgGraph<Pkg extends Package> (pkgs: Pkg[], opts?: {19ignoreDevDeps?: boolean20linkWorkspacePackages?: boolean21}): {22graph: Record<string, PackageNode<Pkg>>23unmatched: Array<{ pkgName: string, range: string }>24} {25const pkgMap = createPkgMap(pkgs)26const pkgMapValues = Object.values(pkgMap)27let pkgMapByManifestName: Record<string, Package[] | undefined> | undefined28let pkgMapByDir: Record<string, Package | undefined> | undefined29const unmatched: Array<{ pkgName: string, range: string }> = []30const graph = mapValues((pkg) => ({31dependencies: createNode(pkg),32package: pkg,33}), pkgMap) as Record<string, PackageNode<Pkg>>34return { graph, unmatched }35
36function createNode (pkg: Package): string[] {37const dependencies = {38...pkg.manifest.peerDependencies,39...(!opts?.ignoreDevDeps && pkg.manifest.devDependencies),40...pkg.manifest.optionalDependencies,41...pkg.manifest.dependencies,42}43
44return Object.entries(dependencies)45.map(([depName, rawSpec]) => {46let spec!: { fetchSpec: string, type: string }47const isWorkspaceSpec = rawSpec.startsWith('workspace:')48try {49if (isWorkspaceSpec) {50const { fetchSpec, name } = parsePref(workspacePrefToNpm(rawSpec), depName, 'latest', '')!51rawSpec = fetchSpec52depName = name53}54spec = npa.resolve(depName, rawSpec, pkg.dir)55} catch {56return ''57}58
59if (spec.type === 'directory') {60pkgMapByDir ??= getPkgMapByDir(pkgMapValues)61const resolvedPath = path.resolve(pkg.dir, spec.fetchSpec)62const found = pkgMapByDir[resolvedPath]63if (found) {64return found.dir65}66
67// Slow path; only needed when there are case mismatches on case-insensitive filesystems.68const matchedPkg = pkgMapValues.find(pkg => path.relative(pkg.dir, spec.fetchSpec) === '')69if (matchedPkg == null) {70return ''71}72pkgMapByDir[resolvedPath] = matchedPkg73return matchedPkg.dir74}75
76if (spec.type !== 'version' && spec.type !== 'range') return ''77
78pkgMapByManifestName ??= getPkgMapByManifestName(pkgMapValues)79const pkgs = pkgMapByManifestName[depName]80if (!pkgs || pkgs.length === 0) return ''81const versions = pkgs.filter(({ manifest }) => manifest.version)82.map(pkg => pkg.manifest.version) as string[]83
84// explicitly check if false, backwards-compatibility (can be undefined)85const strictWorkspaceMatching = opts?.linkWorkspacePackages === false && !isWorkspaceSpec86if (strictWorkspaceMatching) {87unmatched.push({ pkgName: depName, range: rawSpec })88return ''89}90if (isWorkspaceSpec && versions.length === 0) {91const matchedPkg = pkgs.find(pkg => pkg.manifest.name === depName)92return matchedPkg!.dir93}94if (versions.includes(rawSpec)) {95const matchedPkg = pkgs.find(pkg => pkg.manifest.name === depName && pkg.manifest.version === rawSpec)96return matchedPkg!.dir97}98const matched = resolveWorkspaceRange(rawSpec, versions)99if (!matched) {100unmatched.push({ pkgName: depName, range: rawSpec })101return ''102}103const matchedPkg = pkgs.find(pkg => pkg.manifest.name === depName && pkg.manifest.version === matched)104return matchedPkg!.dir105})106.filter(Boolean)107}108}
109
110function createPkgMap (pkgs: Package[]): Record<string, Package> {111const pkgMap: Record<string, Package> = {}112for (const pkg of pkgs) {113pkgMap[pkg.dir] = pkg114}115return pkgMap116}
117
118function getPkgMapByManifestName (pkgMapValues: Package[]): Record<string, Package[] | undefined> {119const pkgMapByManifestName: Record<string, Package[] | undefined> = {}120for (const pkg of pkgMapValues) {121if (pkg.manifest.name) {122(pkgMapByManifestName[pkg.manifest.name] ??= []).push(pkg)123}124}125return pkgMapByManifestName126}
127
128function getPkgMapByDir (pkgMapValues: Package[]): Record<string, Package | undefined> {129const pkgMapByDir: Record<string, Package | undefined> = {}130for (const pkg of pkgMapValues) {131pkgMapByDir[path.resolve(pkg.dir)] = pkg132}133return pkgMapByDir134}
135