Repository: denoland/deno
Pull request: fix(npm): peerDependency resolution leading to multiple versions being installed + hanging

There are still weekly reports of Fresh users having errors because of duplicated `preact` or `@preact/signals` packages. So I spend the day hunting down various peer dependency installation issues.

Essentially, this PR rewrites our dependency resolution code to use a two-phase approach, which fixes duplicate peer dependencies or hanging installation.

Peer deps were resolved inline during BFS traversal, making results dependent on traversal order. If package lib (which peers with `react`) was encountered before `react` was added to the graph by a sibling, it would auto-resolve `react@18.2.0 (latest)`. When encountered again in a subtree where `react@18.3.0` already existed, it got a different resolution, which creates a duplicate lib entry. With patterns like Expo/React Native (100+ plugins all peering with `react`, `react-native`, and `expo`), this caused exponential blowup.

This issue affects practically every frontend project as the framework is always a peerDependency (`react`/`preact`/`vue`/etc) and most of them have some form of singletons which only work when one copy of the framework is installed.

Fixes https://github.com/denoland/deno/issues/31179
Fixes https://github.com/denoland/deno/issues/21394

Changed files:
filename: tests/specs/npm/info_peer_deps/main_info.out
patch: @@ -4,11 +4,11 @@ dependencies: 6 unique
 size: [WILDCARD]
 
 file:///[WILDCARD]/main.ts (171B)
-├─┬ npm:/@denotest/peer-dep-test-child@1.0.0_@denotest+peer-dep-test-peer@1.0.0 ([WILDCARD])
+├─┬ npm:/@denotest/peer-dep-test-child@1.0.0 ([WILDCARD])
 │ ├─┬ npm:/@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@1.0.0 ([WILDCARD])
 │ │ └── npm:/@denotest/peer-dep-test-peer@1.0.0 ([WILDCARD])
 │ └── npm:/@denotest/peer-dep-test-peer@1.0.0 ([WILDCARD])
-└─┬ npm:/@denotest/peer-dep-test-child@2.0.0_@denotest+peer-dep-test-peer@2.0.0 ([WILDCARD])
+└─┬ npm:/@denotest/peer-dep-test-child@2.0.0 ([WILDCARD])
   ├─┬ npm:/@denotest/peer-dep-test-grandchild@1.0.0_@denotest+peer-dep-test-peer@2.0.0 ([WILDCARD])
   │ └── npm:/@denotest/peer-dep-test-peer@2.0.0 ([WILDCARD])
   └── npm:/@denotest/peer-dep-test-peer@2.0.0 ([WILDCARD])

filename: tests/specs/npm/info_peer_deps_json/main_info_json.out
patch: @@ -22,7 +22,7 @@
               }
             }
           },
-          "npmPackage": "@denotest/peer-dep-test-child@1.0.0_@denotest+peer-dep-test-peer@1.0.0"
+          "npmPackage": "@denotest/peer-dep-test-child@1.0.0"
         },
         {
           "specifier": "npm:@denotest/peer-dep-test-child@2",
@@ -39,7 +39,7 @@
               }
             }
           },
-          "npmPackage": "@denotest/peer-dep-test-child@2.0.0_@denotest+peer-dep-test-peer@2.0.0"
+          "npmPackage": "@denotest/peer-dep-test-child@2.0.0"
         }
       ],
       "local": "[WILDCARD]main.ts",
@@ -50,20 +50,20 @@
     {
       "kind": "npm",
       "specifier": "npm:/@denotest/peer-dep-test-child@1.0.0",
-      "npmPackage": "@denotest/peer-dep-test-child@1.0.0_@denotest+peer-dep-test-peer@1.0.0"
+      "npmPackage": "@denotest/peer-dep-test-child@1.0.0"
     },
     {
       "kind": "npm",
       "specifier": "npm:/@denotest/peer-dep-test-child@2.0.0",
-      "npmPackage": "@denotest/peer-dep-test-child@2.0.0_@denotest+peer-dep-test-peer@2.0.0"
+      "npmPackage": "@denotest/peer-dep-test-child@2.0.0"
     }
   ],
   "redirects": {
     "npm:@denotest/peer-dep-test-child@1": "npm:/@denotest/peer-dep-test-child@1.0.0",
     "npm:@denotest/peer-dep-test-child@2": "npm:/@denotest/peer-dep-test-child@2.0.0"
   },
   "npmPackages": {
-    "@denotest/peer-dep-test-child@1.0.0_@denotest+peer-dep-test-peer@1.0.0": {
+    "@denotest/peer-dep-test-child@1.0.0": {
       "name": "@denotest/peer-dep-test-child",
       "version": "1.0.0",
       "dependencies": [
@@ -72,7 +72,7 @@
       ],
       "registryUrl": "http://localhost:4260/"
     },
-    "@denotest/peer-dep-test-child@2.0.0_@denotest+peer-dep-test-peer@2.0.0": {
+    "@denotest/peer-dep-test-child@2.0.0": {
       "name": "@denotest/peer-dep-test-child",
       "version": "2.0.0",
       "dependencies": [