Browse Source

fix(profile): deduplicate fallback manifest traversal

fengsy 1 month ago
parent
commit
42378a987e
1 changed files with 16 additions and 6 deletions
  1. 16 6
      packages/boot/app-boot/src/profile.ts

+ 16 - 6
packages/boot/app-boot/src/profile.ts

@@ -483,11 +483,21 @@ type ModuleFallbackEntry =
   | { kind: 'symlink'; packageName: string; packageDir: string }
   | { kind: 'proxy'; packageName: string; version: string; targets: Record<string, string> }
 
+/** Read one package manifest used while traversing a module-fallback dependency graph. */
+function readModuleFallbackManifest(anchor: string): ProfileManifest {
+  return JSON.parse(readFileSync(anchor, 'utf8')) as ProfileManifest
+}
+
+/** Return dependency names that may be imported by a loader-visible plugin. */
+function profileDependencyNames(manifest: ProfileManifest): string[] {
+  return [...Object.keys(manifest.dependencies ?? {}), ...Object.keys(manifest.peerDependencies ?? {})]
+}
+
 /** Resolve the installation generation that every profile must find through the fallback directory. */
 function resolveModuleFallbackEntries(
   installAnchor: string,
 ): { entries: ModuleFallbackEntry[]; packageNames: ReadonlySet<string> } {
-  const appManifest = JSON.parse(readFileSync(installAnchor, 'utf8')) as ProfileManifest
+  const appManifest = readModuleFallbackManifest(installAnchor)
   const links = new Map<string, string>()
   /* v8 ignore next -- a real app manifest always declares its name */
   if (appManifest.name !== undefined) links.set(appManifest.name, dirname(installAnchor))
@@ -499,7 +509,7 @@ function resolveModuleFallbackEntries(
     // dsh-compaction, ...) are peers of their implementations, never plain
     // dependencies, yet out-of-tree plugins import them directly.
     /* v8 ignore next -- a real app manifest always declares dependencies */
-    for (const dep of [...Object.keys(next.manifest.dependencies ?? {}), ...Object.keys(next.manifest.peerDependencies ?? {})]) {
+    for (const dep of profileDependencyNames(next.manifest)) {
       if (links.has(dep)) continue
       const dir = packageDirFromAnchor(next.anchor, dep)
       // A declared-but-uninstalled dependency cannot be a loader-visible
@@ -507,7 +517,7 @@ function resolveModuleFallbackEntries(
       if (dir === undefined) continue
       links.set(dep, dir)
       const manifestPath = join(dir, 'package.json')
-      queue.push({ anchor: manifestPath, manifest: JSON.parse(readFileSync(manifestPath, 'utf8')) as ProfileManifest })
+      queue.push({ anchor: manifestPath, manifest: readModuleFallbackManifest(manifestPath) })
     }
   }
   const entries = !isPackagedExecutable()
@@ -603,7 +613,7 @@ function dependencyClosure(
   const visited = new Set(reserved)
   for (const anchor of anchors) {
     const canonicalAnchor = realpathSync.native(anchor)
-    const manifest = JSON.parse(readFileSync(canonicalAnchor, 'utf8')) as ProfileManifest
+    const manifest = readModuleFallbackManifest(canonicalAnchor)
     /* v8 ignore next -- an installable package manifest always declares its name */
     if (manifest.name === undefined) continue
     if (!visited.has(manifest.name)) {
@@ -614,7 +624,7 @@ function dependencyClosure(
     for (let next = queue.shift(); next !== undefined; next = queue.shift()) {
       // Service Provider packages commonly expose Service Definitions as peers.
       /* v8 ignore next -- an installable package manifest always declares dependencies or peers */
-      for (const dep of [...Object.keys(next.manifest.dependencies ?? {}), ...Object.keys(next.manifest.peerDependencies ?? {})]) {
+      for (const dep of profileDependencyNames(next.manifest)) {
         if (visited.has(dep)) continue
         const dir = packageDirFromAnchor(next.anchor, dep, exclude)
         // A declared-but-uninstalled dependency cannot be loader-visible.
@@ -622,7 +632,7 @@ function dependencyClosure(
         visited.add(dep)
         links.set(dep, dir)
         const manifestPath = join(dir, 'package.json')
-        queue.push({ anchor: manifestPath, manifest: JSON.parse(readFileSync(manifestPath, 'utf8')) as ProfileManifest })
+        queue.push({ anchor: manifestPath, manifest: readModuleFallbackManifest(manifestPath) })
       }
     }
   }