nx 23.1.0 → 23.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. package/dist/bin/nx.js +2 -1
  2. package/dist/release/changelog-renderer/index.js +4 -5
  3. package/dist/release/index.d.ts +1 -1
  4. package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
  5. package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
  6. package/dist/src/analytics/analytics.d.ts +6 -0
  7. package/dist/src/analytics/analytics.js +16 -8
  8. package/dist/src/analytics/index.d.ts +1 -1
  9. package/dist/src/analytics/index.js +2 -1
  10. package/dist/src/command-line/add/add.js +13 -1
  11. package/dist/src/command-line/affected/affected.js +1 -0
  12. package/dist/src/command-line/configure-ai-agents/configure-ai-agents.js +2 -2
  13. package/dist/src/command-line/graph/graph.d.ts +1 -0
  14. package/dist/src/command-line/graph/graph.js +15 -6
  15. package/dist/src/command-line/import/import.js +2 -3
  16. package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
  17. package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
  18. package/dist/src/command-line/init/command-object.js +10 -2
  19. package/dist/src/command-line/init/configure-plugins.js +3 -2
  20. package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
  21. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
  22. package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
  23. package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
  24. package/dist/src/command-line/init/implementation/angular/index.js +4 -2
  25. package/dist/src/command-line/init/implementation/angular/integrated-workspace.js +2 -5
  26. package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +3 -0
  27. package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +4 -1
  28. package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
  29. package/dist/src/command-line/init/implementation/utils.js +22 -3
  30. package/dist/src/command-line/init/init-v2.js +4 -5
  31. package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
  32. package/dist/src/command-line/migrate/execute-migration.js +389 -0
  33. package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
  34. package/dist/src/command-line/migrate/migrate.d.ts +5 -45
  35. package/dist/src/command-line/migrate/migrate.js +122 -402
  36. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +1 -1
  37. package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
  38. package/dist/src/command-line/release/changelog.js +5 -0
  39. package/dist/src/command-line/release/index.d.ts +1 -1
  40. package/dist/src/command-line/release/utils/git.js +7 -3
  41. package/dist/src/command-line/release/utils/release-graph.d.ts +19 -2
  42. package/dist/src/command-line/release/utils/release-graph.js +71 -19
  43. package/dist/src/command-line/release/utils/remote-release-clients/github.js +7 -1
  44. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +1 -1
  45. package/dist/src/command-line/release/utils/shared.d.ts +2 -2
  46. package/dist/src/command-line/release/version/release-group-processor.d.ts +24 -0
  47. package/dist/src/command-line/release/version/release-group-processor.js +80 -6
  48. package/dist/src/command-line/release/version/version-actions.d.ts +27 -2
  49. package/dist/src/command-line/release/version/version-actions.js +9 -1
  50. package/dist/src/command-line/release/version.js +9 -5
  51. package/dist/src/command-line/reset/reset.js +6 -1
  52. package/dist/src/command-line/run/run-one.js +1 -0
  53. package/dist/src/command-line/run-many/run-many.js +1 -0
  54. package/dist/src/command-line/show/show-target/info.js +11 -2
  55. package/dist/src/command-line/show/show-target/inputs.js +50 -153
  56. package/dist/src/command-line/show/show-target/outputs.js +48 -173
  57. package/dist/src/command-line/show/show-target/utils.d.ts +28 -0
  58. package/dist/src/command-line/show/show-target/utils.js +83 -3
  59. package/dist/src/command-line/watch/watch.js +13 -0
  60. package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
  61. package/dist/src/commands-runner/create-command-graph.js +5 -0
  62. package/dist/src/config/nx-json.d.ts +5 -3
  63. package/dist/src/core/graph/main.js +1 -1
  64. package/dist/src/core/graph/styles.js +1 -1
  65. package/dist/src/daemon/client/client.d.ts +52 -3
  66. package/dist/src/daemon/client/client.js +177 -55
  67. package/dist/src/daemon/client/daemon-environment.d.ts +17 -0
  68. package/dist/src/daemon/client/daemon-environment.js +110 -2
  69. package/dist/src/daemon/client/daemon-socket-messenger.js +4 -0
  70. package/dist/src/daemon/message-types/daemon-message.d.ts +32 -0
  71. package/dist/src/daemon/message-types/daemon-message.js +25 -0
  72. package/dist/src/daemon/server/file-watching/file-watcher-sockets.d.ts +5 -0
  73. package/dist/src/daemon/server/file-watching/file-watcher-sockets.js +16 -0
  74. package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +1 -1
  75. package/dist/src/daemon/server/latest-nx.js +5 -2
  76. package/dist/src/daemon/server/server.js +31 -4
  77. package/dist/src/daemon/server/shutdown-utils.d.ts +5 -0
  78. package/dist/src/daemon/server/shutdown-utils.js +11 -4
  79. package/dist/src/daemon/socket-utils.d.ts +1 -0
  80. package/dist/src/daemon/socket-utils.js +24 -3
  81. package/dist/src/daemon/tmp-dir.d.ts +43 -3
  82. package/dist/src/daemon/tmp-dir.js +437 -15
  83. package/dist/src/devkit-internals.d.ts +96 -3
  84. package/dist/src/devkit-internals.js +200 -4
  85. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.d.ts +5 -0
  86. package/dist/src/generators/internal-utils/format-changed-files-with-prettier-if-available.js +9 -2
  87. package/dist/src/generators/tree.d.ts +4 -2
  88. package/dist/src/generators/utils/nx-json.js +13 -3
  89. package/dist/src/hasher/check-task-files.d.ts +86 -0
  90. package/dist/src/hasher/check-task-files.js +378 -0
  91. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.d.ts +12 -0
  92. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.js +250 -0
  93. package/dist/src/migrations/update-23-2-0/set-cache-on-executor-target-defaults.md +50 -0
  94. package/dist/src/native/index.d.ts +31 -2
  95. package/dist/src/native/index.js +71 -28
  96. package/dist/src/native/native-bindings.js +3 -0
  97. package/dist/src/native/native-file-cache-location.d.ts +17 -1
  98. package/dist/src/native/native-file-cache-location.js +86 -12
  99. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  100. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  101. package/dist/src/nx-cloud/utilities/get-cloud-options.js +5 -2
  102. package/dist/src/nx-cloud/utilities/onboarding.js +5 -2
  103. package/dist/src/plugins/js/lock-file/bun-parser.js +43 -10
  104. package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
  105. package/dist/src/plugins/js/lock-file/pnpm-parser.js +110 -17
  106. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +15 -9
  107. package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +3 -0
  108. package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
  109. package/dist/src/plugins/js/project-graph/affected/npm-packages.js +51 -20
  110. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +14 -4
  111. package/dist/src/plugins/js/utils/register.d.ts +18 -0
  112. package/dist/src/plugins/js/utils/register.js +81 -0
  113. package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
  114. package/dist/src/plugins/js/utils/typescript.js +15 -0
  115. package/dist/src/project-graph/affected/affected-project-graph-models.d.ts +1 -1
  116. package/dist/src/project-graph/affected/affected-project-graph.d.ts +1 -1
  117. package/dist/src/project-graph/affected/affected-project-graph.js +2 -2
  118. package/dist/src/project-graph/affected/locators/project-glob-changes.js +5 -2
  119. package/dist/src/project-graph/build-project-graph.js +7 -1
  120. package/dist/src/project-graph/error-types.d.ts +12 -1
  121. package/dist/src/project-graph/error-types.js +68 -3
  122. package/dist/src/project-graph/file-utils.js +6 -1
  123. package/dist/src/project-graph/plugins/isolation/isolated-plugin.d.ts +1 -0
  124. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +22 -1
  125. package/dist/src/project-graph/plugins/isolation/messaging.d.ts +16 -3
  126. package/dist/src/project-graph/plugins/isolation/messaging.js +3 -0
  127. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +58 -8
  128. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
  129. package/dist/src/project-graph/plugins/transpiler.js +4 -0
  130. package/dist/src/project-graph/project-graph.js +23 -0
  131. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +10 -12
  132. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +93 -63
  133. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +5 -8
  134. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +16 -15
  135. package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +12 -5
  136. package/dist/src/project-graph/utils/project-configuration/source-maps.js +21 -7
  137. package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +1 -1
  138. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +26 -27
  139. package/dist/src/project-graph/utils/project-configuration/target-merging.js +50 -8
  140. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +149 -6
  141. package/dist/src/project-graph/utils/project-configuration/utils.d.ts +15 -0
  142. package/dist/src/project-graph/utils/project-configuration/utils.js +42 -6
  143. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +15 -12
  144. package/dist/src/project-graph/utils/project-configuration-utils.js +79 -101
  145. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +11 -4
  146. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
  147. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
  148. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
  149. package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
  150. package/dist/src/tasks-runner/run-command.js +4 -2
  151. package/dist/src/tasks-runner/task-env.d.ts +9 -0
  152. package/dist/src/tasks-runner/task-env.js +31 -4
  153. package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
  154. package/dist/src/tasks-runner/task-orchestrator.js +23 -10
  155. package/dist/src/tasks-runner/utils.js +2 -6
  156. package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
  157. package/dist/src/utils/acknowledge-build-scripts.js +102 -0
  158. package/dist/src/utils/analytics-prompt.d.ts +0 -6
  159. package/dist/src/utils/analytics-prompt.js +0 -51
  160. package/dist/src/utils/assert-workspace-validity.js +7 -2
  161. package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
  162. package/dist/src/utils/catalog/manager-utils.js +22 -5
  163. package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
  164. package/dist/src/utils/catalog/pnpm-manager.js +3 -15
  165. package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
  166. package/dist/src/utils/catalog/yarn-manager.js +3 -15
  167. package/dist/src/utils/command-line-utils.js +30 -20
  168. package/dist/src/utils/fileutils.d.ts +5 -0
  169. package/dist/src/utils/fileutils.js +7 -2
  170. package/dist/src/utils/git-revision.d.ts +12 -0
  171. package/dist/src/utils/git-revision.js +25 -0
  172. package/dist/src/utils/git-utils.d.ts +16 -3
  173. package/dist/src/utils/git-utils.js +146 -52
  174. package/dist/src/utils/git-worktrees.d.ts +30 -0
  175. package/dist/src/utils/git-worktrees.js +212 -0
  176. package/dist/src/utils/ignore.d.ts +128 -1
  177. package/dist/src/utils/ignore.js +182 -12
  178. package/dist/src/utils/long-running-target.d.ts +10 -0
  179. package/dist/src/utils/long-running-target.js +19 -0
  180. package/dist/src/utils/min-release-age/behavior/bun.js +7 -11
  181. package/dist/src/utils/min-release-age/behavior/npm.js +5 -2
  182. package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
  183. package/dist/src/utils/min-release-age/packument.js +1 -1
  184. package/dist/src/utils/nx-tmp-dir.d.ts +18 -0
  185. package/dist/src/utils/nx-tmp-dir.js +40 -0
  186. package/dist/src/utils/owned-private-dir.d.ts +132 -0
  187. package/dist/src/utils/owned-private-dir.js +323 -0
  188. package/dist/src/utils/package-json.js +11 -7
  189. package/dist/src/utils/package-manager-config/bunfig.d.ts +14 -0
  190. package/dist/src/utils/package-manager-config/bunfig.js +49 -0
  191. package/dist/src/utils/package-manager-config/npmrc.d.ts +23 -0
  192. package/dist/src/utils/package-manager-config/npmrc.js +124 -0
  193. package/dist/src/utils/package-manager-config/pnpm-config.d.ts +17 -0
  194. package/dist/src/utils/package-manager-config/pnpm-config.js +60 -0
  195. package/dist/src/utils/package-manager.d.ts +23 -2
  196. package/dist/src/utils/package-manager.js +264 -43
  197. package/dist/src/utils/provenance.js +18 -16
  198. package/dist/src/utils/registry-config/bun.d.ts +2 -0
  199. package/dist/src/utils/registry-config/bun.js +257 -0
  200. package/dist/src/utils/registry-config/index.d.ts +13 -0
  201. package/dist/src/utils/registry-config/index.js +102 -0
  202. package/dist/src/utils/registry-config/pnpm.d.ts +2 -0
  203. package/dist/src/utils/registry-config/pnpm.js +1551 -0
  204. package/dist/src/utils/registry-config/utils.d.ts +195 -0
  205. package/dist/src/utils/registry-config/utils.js +496 -0
  206. package/dist/src/utils/registry-config/yarn-berry.d.ts +2 -0
  207. package/dist/src/utils/registry-config/yarn-berry.js +612 -0
  208. package/dist/src/utils/registry-config/yarn-classic.d.ts +2 -0
  209. package/dist/src/utils/registry-config/yarn-classic.js +1001 -0
  210. package/dist/src/utils/safe-spawn.d.ts +24 -0
  211. package/dist/src/utils/safe-spawn.js +104 -0
  212. package/dist/src/utils/shell-quoting.d.ts +15 -0
  213. package/dist/src/utils/shell-quoting.js +35 -0
  214. package/dist/src/utils/wait-for-socket-connection.d.ts +11 -0
  215. package/dist/src/utils/wait-for-socket-connection.js +14 -8
  216. package/dist/src/utils/workspace-id.d.ts +20 -0
  217. package/dist/src/utils/workspace-id.js +54 -0
  218. package/migrations.json +13 -1
  219. package/package.json +22 -17
  220. package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
  221. package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
  222. package/dist/src/utils/min-release-age/npmrc.d.ts +0 -15
  223. package/dist/src/utils/min-release-age/npmrc.js +0 -45
@@ -133,6 +133,22 @@ function findV3Version(snapshot, packageName) {
133
133
  }
134
134
  function getDependencies(data, keyMap, ctx) {
135
135
  const dependencies = [];
136
+ // Memoizes semver `satisfies(version, range)` for this dependency walk. The
137
+ // same (version, range) pairs recur across many edges, so the range parse
138
+ // happens once per distinct pair instead of once per edge. Scoped to the walk
139
+ // (not module-global) so V8 collects it when dependency creation finishes
140
+ // rather than retaining it for the daemon's lifetime.
141
+ const versionSatisfiesCache = new Map();
142
+ const cachedSatisfies = (version, range) => {
143
+ const key = `${version}\n${range}`;
144
+ const cached = versionSatisfiesCache.get(key);
145
+ if (cached !== undefined) {
146
+ return cached;
147
+ }
148
+ const result = (0, semver_1.satisfies)(version, range);
149
+ versionSatisfiesCache.set(key, result);
150
+ return result;
151
+ };
136
152
  if (data.lockfileVersion > 1) {
137
153
  Object.entries(data.packages).forEach(([path, snapshot]) => {
138
154
  // we are skipping workspaces packages
@@ -147,7 +163,7 @@ function getDependencies(data, keyMap, ctx) {
147
163
  ].forEach((section) => {
148
164
  if (section) {
149
165
  Object.entries(section).forEach(([name, versionRange]) => {
150
- const target = findTarget(path, keyMap, name, versionRange);
166
+ const target = findTarget(path, keyMap, name, versionRange, cachedSatisfies);
151
167
  if (target) {
152
168
  const dep = {
153
169
  source: sourceName,
@@ -164,12 +180,12 @@ function getDependencies(data, keyMap, ctx) {
164
180
  }
165
181
  else {
166
182
  Object.entries(data.dependencies).forEach(([packageName, snapshot]) => {
167
- addV1NodeDependencies(`node_modules/${packageName}`, snapshot, dependencies, keyMap, ctx);
183
+ addV1NodeDependencies(`node_modules/${packageName}`, snapshot, dependencies, keyMap, ctx, cachedSatisfies);
168
184
  });
169
185
  }
170
186
  return dependencies;
171
187
  }
172
- function findTarget(sourcePath, keyMap, targetName, versionRange,
188
+ function findTarget(sourcePath, keyMap, targetName, versionRange, cachedSatisfies,
173
189
  // When a package is found at a path but its version doesn't satisfy the
174
190
  // range (e.g. due to npm overrides), we keep it as a fallback. npm already
175
191
  // resolved this dependency to that location, so it is the correct target
@@ -186,12 +202,13 @@ fallback) {
186
202
  versionRange.startsWith('npm:')) {
187
203
  const nodeVersion = child.data.version.slice(child.data.version.indexOf('@', 5) + 1);
188
204
  const depVersion = versionRange.slice(versionRange.indexOf('@', 5) + 1);
189
- if (nodeVersion === depVersion || (0, semver_1.satisfies)(nodeVersion, depVersion)) {
205
+ if (nodeVersion === depVersion ||
206
+ cachedSatisfies(nodeVersion, depVersion)) {
190
207
  return child;
191
208
  }
192
209
  }
193
210
  else if (child.data.version === versionRange ||
194
- (0, semver_1.satisfies)(child.data.version, versionRange)) {
211
+ cachedSatisfies(child.data.version, versionRange)) {
195
212
  return child;
196
213
  }
197
214
  // Version mismatch — save as fallback (could be an npm override)
@@ -203,13 +220,17 @@ fallback) {
203
220
  if (!sourcePath) {
204
221
  return fallback;
205
222
  }
206
- return findTarget(sourcePath.split('node_modules/').slice(0, -1).join('node_modules/'), keyMap, targetName, versionRange, fallback);
223
+ // Walk one level up the nesting chain by dropping the trailing
224
+ // `node_modules/<pkg>` segment. Slash-index arithmetic avoids the
225
+ // split/slice/join array allocation on every hop.
226
+ const lastNodeModules = sourcePath.lastIndexOf('node_modules/');
227
+ return findTarget(lastNodeModules === -1 ? '' : sourcePath.substring(0, lastNodeModules), keyMap, targetName, versionRange, cachedSatisfies, fallback);
207
228
  }
208
- function addV1NodeDependencies(path, snapshot, dependencies, keyMap, ctx) {
229
+ function addV1NodeDependencies(path, snapshot, dependencies, keyMap, ctx, cachedSatisfies) {
209
230
  if (keyMap.has(path) && snapshot.requires) {
210
231
  const source = keyMap.get(path).name;
211
232
  Object.entries(snapshot.requires).forEach(([name, versionRange]) => {
212
- const target = findTarget(path, keyMap, name, versionRange);
233
+ const target = findTarget(path, keyMap, name, versionRange, cachedSatisfies);
213
234
  if (target) {
214
235
  const dep = {
215
236
  source: source,
@@ -223,14 +244,14 @@ function addV1NodeDependencies(path, snapshot, dependencies, keyMap, ctx) {
223
244
  }
224
245
  if (snapshot.dependencies) {
225
246
  Object.entries(snapshot.dependencies).forEach(([depName, depSnapshot]) => {
226
- addV1NodeDependencies(`${path}/node_modules/${depName}`, depSnapshot, dependencies, keyMap, ctx);
247
+ addV1NodeDependencies(`${path}/node_modules/${depName}`, depSnapshot, dependencies, keyMap, ctx, cachedSatisfies);
227
248
  });
228
249
  }
229
250
  const { peerDependencies } = getPeerDependencies(path);
230
251
  if (peerDependencies) {
231
252
  const node = keyMap.get(path);
232
253
  Object.entries(peerDependencies).forEach(([depName, depSpec]) => {
233
- const target = findTarget(path, keyMap, depName, depSpec);
254
+ const target = findTarget(path, keyMap, depName, depSpec, cachedSatisfies);
234
255
  if (target) {
235
256
  const dep = {
236
257
  source: node.name,
@@ -358,10 +379,11 @@ function mapSnapshots(rootLockFile, graph) {
358
379
  const nestedNodes = new Set();
359
380
  const visitedNodes = new Map();
360
381
  const remappedPackages = new Map();
382
+ const packageIndex = buildV3Index(rootLockFile.packages);
361
383
  // add first level children
362
384
  Object.values(graph.externalNodes).forEach((node) => {
363
385
  if (node.name === `npm:${node.data.packageName}`) {
364
- const mappedPackage = mapPackage(rootLockFile, node.data.packageName, node.data.version);
386
+ const mappedPackage = mapPackage(rootLockFile, packageIndex, node.data.packageName, node.data.version);
365
387
  remappedPackages.set(mappedPackage.path, mappedPackage);
366
388
  visitedNodes.set(node, {
367
389
  packagePaths: new Set([mappedPackage.path]),
@@ -375,7 +397,7 @@ function mapSnapshots(rootLockFile, graph) {
375
397
  let remappedPackagesArray;
376
398
  if (nestedNodes.size) {
377
399
  const invertedGraph = (0, operators_1.reverse)(graph);
378
- nestMappedPackages(invertedGraph, remappedPackages, nestedNodes, visitedNodes, rootLockFile);
400
+ nestMappedPackages(invertedGraph, remappedPackages, nestedNodes, visitedNodes, rootLockFile, packageIndex);
379
401
  // initially we naively map package paths to topParent/../parent/child
380
402
  // but some of those should be nested higher up the tree
381
403
  remappedPackagesArray = elevateNestedPaths(remappedPackages);
@@ -385,14 +407,14 @@ function mapSnapshots(rootLockFile, graph) {
385
407
  }
386
408
  return remappedPackagesArray.sort((a, b) => a.path.localeCompare(b.path));
387
409
  }
388
- function mapPackage(rootLockFile, packageName, version, parentPath = '') {
410
+ function mapPackage(rootLockFile, packageIndex, packageName, version, parentPath = '') {
389
411
  const lockfileVersion = rootLockFile.lockfileVersion;
390
412
  let valueV3, valueV1;
391
413
  if (lockfileVersion < 3) {
392
414
  valueV1 = findMatchingPackageV1(rootLockFile.dependencies, packageName, version);
393
415
  }
394
416
  if (lockfileVersion > 1) {
395
- valueV3 = findMatchingPackageV3(rootLockFile.packages, packageName, version);
417
+ valueV3 = findMatchingPackageV3(packageIndex, packageName, version);
396
418
  }
397
419
  return {
398
420
  path: parentPath + `node_modules/${packageName}`,
@@ -401,7 +423,7 @@ function mapPackage(rootLockFile, packageName, version, parentPath = '') {
401
423
  valueV3,
402
424
  };
403
425
  }
404
- function nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, rootLockFile) {
426
+ function nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, rootLockFile, packageIndex) {
405
427
  const initialSize = nestedNodes.size;
406
428
  if (!initialSize) {
407
429
  return;
@@ -421,7 +443,7 @@ function nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, ro
421
443
  if (visitedNodes.has(targetNode) &&
422
444
  !visitedNodes.get(targetNode).unresolvedParents.size) {
423
445
  visitedNodes.get(targetNode).packagePaths.forEach((path) => {
424
- const mappedPackage = mapPackage(rootLockFile, node.data.packageName, node.data.version, path + '/');
446
+ const mappedPackage = mapPackage(rootLockFile, packageIndex, node.data.packageName, node.data.version, path + '/');
425
447
  result.set(mappedPackage.path, mappedPackage);
426
448
  visitedNodes.get(node).packagePaths.add(mappedPackage.path);
427
449
  visitedNodes.get(node).unresolvedParents.delete(target);
@@ -439,7 +461,7 @@ function nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, ro
439
461
  ].join('\n'));
440
462
  }
441
463
  else {
442
- nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, rootLockFile);
464
+ nestMappedPackages(invertedGraph, result, nestedNodes, visitedNodes, rootLockFile, packageIndex);
443
465
  }
444
466
  }
445
467
  // sort paths by number of segments and then alphabetically
@@ -495,16 +517,38 @@ function elevateNestedPaths(remappedPackages) {
495
517
  });
496
518
  return Array.from(result.values());
497
519
  }
498
- function findMatchingPackageV3(packages, name, version) {
499
- for (const [key, { dev, peer, ...snapshot }] of Object.entries(packages)) {
500
- if (key.endsWith(`node_modules/${name}`)) {
501
- if ([
502
- snapshot.version,
503
- snapshot.resolved,
504
- `npm:${snapshot.name}@${snapshot.version}`,
505
- ].includes(version)) {
506
- return snapshot;
507
- }
520
+ // Bucket packages by their trailing "node_modules/<name>" segment so a lookup
521
+ // scans only that name's copies instead of every package (was O(nodes *
522
+ // allPackages)). Mirrors the old `key.endsWith(node_modules/<name>)` match:
523
+ // the name is whatever follows the last "node_modules/" in the key.
524
+ function buildV3Index(packages) {
525
+ const index = new Map();
526
+ if (!packages)
527
+ return index;
528
+ const marker = 'node_modules/';
529
+ for (const key of Object.keys(packages)) {
530
+ const i = key.lastIndexOf(marker);
531
+ if (i === -1)
532
+ continue; // root "" / workspace paths never matched endsWith
533
+ const name = key.slice(i + marker.length);
534
+ let bucket = index.get(name);
535
+ if (!bucket)
536
+ index.set(name, (bucket = []));
537
+ bucket.push(packages[key]);
538
+ }
539
+ return index;
540
+ }
541
+ function findMatchingPackageV3(packageIndex, name, version) {
542
+ const bucket = packageIndex.get(name);
543
+ if (!bucket)
544
+ return undefined;
545
+ for (const { dev, peer, ...snapshot } of bucket) {
546
+ if ([
547
+ snapshot.version,
548
+ snapshot.resolved,
549
+ `npm:${snapshot.name}@${snapshot.version}`,
550
+ ].includes(version)) {
551
+ return snapshot;
508
552
  }
509
553
  }
510
554
  }
@@ -12,6 +12,7 @@ const file_hasher_1 = require("../../../hasher/file-hasher");
12
12
  const catalog_1 = require("../../../utils/catalog");
13
13
  const project_graph_pruning_1 = require("./project-graph-pruning");
14
14
  const path_1 = require("path");
15
+ const workspace_root_1 = require("../../../utils/workspace-root");
15
16
  const get_workspace_packages_from_graph_1 = require("../utils/get-workspace-packages-from-graph");
16
17
  const semver_1 = require("semver");
17
18
  const WORKSPACE_DEP_TYPES = [
@@ -133,6 +134,17 @@ function findPatchHash(patchEntriesByPackage, packageName, version) {
133
134
  const nameOnlyMatch = entries.find((entry) => entry.versionSpecifier === null);
134
135
  return nameOnlyMatch?.hash;
135
136
  }
137
+ // Segment-aware: `..` and `../x` escape, a directory literally named `..cache`
138
+ // does not. Absolute targets are resolved against the workspace root.
139
+ function linkTargetEscapesWorkspace(importerPath, depVersion) {
140
+ const rawTarget = depVersion.slice('link:'.length);
141
+ if (path_1.posix.isAbsolute(rawTarget) || (0, path_1.isAbsolute)(rawTarget)) {
142
+ const rel = (0, path_1.relative)(workspace_root_1.workspaceRoot, rawTarget);
143
+ return rel === '..' || rel.startsWith(`..${path_1.sep}`) || (0, path_1.isAbsolute)(rel);
144
+ }
145
+ const combined = path_1.posix.normalize(path_1.posix.join(importerPath === '.' ? '' : importerPath, rawTarget));
146
+ return combined === '..' || combined.startsWith('../');
147
+ }
136
148
  function getNodes(data, isV5) {
137
149
  cachedInvertedRecords.clear();
138
150
  const keyMap = new Map();
@@ -142,7 +154,12 @@ function getNodes(data, isV5) {
142
154
  if (data.patchedDependencies) {
143
155
  for (const specifier of Object.keys(data.patchedDependencies)) {
144
156
  const patchInfo = data.patchedDependencies[specifier];
145
- if (patchInfo && typeof patchInfo === 'object' && 'hash' in patchInfo) {
157
+ const patchHash = typeof patchInfo === 'string'
158
+ ? patchInfo
159
+ : patchInfo && typeof patchInfo === 'object' && 'hash' in patchInfo
160
+ ? patchInfo.hash
161
+ : undefined;
162
+ if (patchHash) {
146
163
  const packageName = extractNameFromKey(specifier, false);
147
164
  const versionSpecifier = getVersion(specifier, packageName) || null;
148
165
  if (!patchEntriesByPackage.has(packageName)) {
@@ -150,7 +167,7 @@ function getNodes(data, isV5) {
150
167
  }
151
168
  patchEntriesByPackage.get(packageName).push({
152
169
  versionSpecifier,
153
- hash: patchInfo.hash,
170
+ hash: patchHash,
154
171
  });
155
172
  }
156
173
  }
@@ -178,7 +195,8 @@ function getNodes(data, isV5) {
178
195
  }
179
196
  }
180
197
  const packageNames = new Set();
181
- for (const [key, snapshot] of Object.entries(data.packages)) {
198
+ // pnpm omits the packages block for workspace-only lockfiles (no external deps)
199
+ for (const [key, snapshot] of Object.entries(data.packages ?? {})) {
182
200
  let packageNameObj;
183
201
  const originalPackageName = extractNameFromKey(key, isV5);
184
202
  if (!originalPackageName) {
@@ -339,6 +357,54 @@ function getNodes(data, isV5) {
339
357
  results[node.name] = node;
340
358
  });
341
359
  }
360
+ // `link:` dependencies pointing outside the workspace never appear in the
361
+ // packages section, so nothing above minted a node for them — and a task
362
+ // input naming one via `externalDependencies` fails hashing with "could not
363
+ // be found". Mint a node under the bare `npm:<name>`, hashed on the link
364
+ // path: the hash changes only when the path does, not when the linked
365
+ // content does, which is how linked dependencies behave everywhere else in
366
+ // Nx. Links that resolve inside the workspace are workspace projects, not
367
+ // externals, and are skipped.
368
+ //
369
+ // One node per package name, chosen deterministically by visiting importers
370
+ // root-first, then lexicographically. An `externalDependencies` input names
371
+ // a package, not a link target, so it could not distinguish two linked
372
+ // targets anyway.
373
+ const importerPaths = Object.keys(data.importers ?? {}).sort((a, b) => a === '.' ? -1 : b === '.' ? 1 : a.localeCompare(b));
374
+ for (const importerPath of importerPaths) {
375
+ const importer = data.importers[importerPath];
376
+ for (const depType of [
377
+ 'dependencies',
378
+ 'devDependencies',
379
+ 'optionalDependencies',
380
+ ]) {
381
+ const deps = importer[depType];
382
+ if (!deps) {
383
+ continue;
384
+ }
385
+ for (const [depName, depVersion] of Object.entries(deps)) {
386
+ if (typeof depVersion !== 'string' || !depVersion.startsWith('link:')) {
387
+ continue;
388
+ }
389
+ if (!linkTargetEscapesWorkspace(importerPath, depVersion)) {
390
+ continue; // inside the workspace -> a workspace project
391
+ }
392
+ const bareName = `npm:${depName}`;
393
+ if (results[bareName]) {
394
+ continue; // a hoisted registry version or an earlier link wins
395
+ }
396
+ results[bareName] = {
397
+ type: 'npm',
398
+ name: bareName,
399
+ data: {
400
+ version: depVersion,
401
+ packageName: depName,
402
+ hash: (0, file_hasher_1.hashArray)([depName, depVersion]),
403
+ },
404
+ };
405
+ }
406
+ }
407
+ }
342
408
  return { nodes: results, keyMap };
343
409
  }
344
410
  function getHoistedVersion(packageName, isV5, hoistedKeysByPackage) {
@@ -359,7 +425,8 @@ function getHoistedVersion(packageName, isV5, hoistedKeysByPackage) {
359
425
  }
360
426
  function getDependencies(data, keyMap, isV5, ctx) {
361
427
  const results = [];
362
- Object.keys(data.packages).forEach((key) => {
428
+ // pnpm omits the packages block for workspace-only lockfiles (no external deps)
429
+ Object.keys(data.packages ?? {}).forEach((key) => {
363
430
  const snapshot = data.packages[key];
364
431
  const nodes = keyMap.get(key);
365
432
  nodes.forEach((node) => {
@@ -391,9 +458,12 @@ function parseBaseVersion(rawVersion, isV5) {
391
458
  }
392
459
  function stringifyPnpmLockfile(graph, rootLockFileContent, packageJson, workspaceRoot) {
393
460
  const data = (0, pnpm_normalizer_1.parseAndNormalizePnpmLockfile)(rootLockFileContent);
394
- const { lockfileVersion, packages, importers } = data;
395
- const { snapshot: rootSnapshot, importers: requiredImporters } = mapRootSnapshot(packageJson, importers, packages, graph, +lockfileVersion, workspaceRoot);
396
- const snapshots = mapSnapshots(data.packages, graph.externalNodes, +lockfileVersion);
461
+ const { lockfileVersion, importers } = data;
462
+ // pnpm omits the packages block for workspace-only lockfiles (no external deps)
463
+ const packages = data.packages ?? {};
464
+ const packageIndex = indexPackagesByName(packages, +lockfileVersion);
465
+ const { snapshot: rootSnapshot, importers: requiredImporters } = mapRootSnapshot(packageJson, importers, packages, packageIndex, graph, +lockfileVersion, workspaceRoot);
466
+ const snapshots = mapSnapshots(packages, packageIndex, graph.externalNodes, +lockfileVersion);
397
467
  const workspaceModules = (0, get_workspace_packages_from_graph_1.getWorkspacePackagesFromGraph)(graph);
398
468
  // Walk transitive workspace deps so every package copy-workspace-modules
399
469
  // writes to disk has a matching importer block. Without this, pnpm errors
@@ -455,12 +525,10 @@ function stringifyPnpmLockfile(graph, rootLockFileContent, packageJson, workspac
455
525
  };
456
526
  return (0, pnpm_normalizer_1.stringifyToPnpmYaml)(output);
457
527
  }
458
- function mapSnapshots(packages, nodes, lockfileVersion) {
528
+ function mapSnapshots(packages, packageIndex, nodes, lockfileVersion) {
459
529
  const result = {};
460
530
  Object.values(nodes).forEach((node) => {
461
- const matchedKeys = findOriginalKeys(packages, node, lockfileVersion, {
462
- returnFullKey: true,
463
- });
531
+ const matchedKeys = findOriginalKeys(packages, packageIndex, node, lockfileVersion, { returnFullKey: true });
464
532
  // the package manager doesn't check for types of dependencies
465
533
  // so we can safely set all to prod
466
534
  matchedKeys.forEach(([key, snapshot]) => {
@@ -500,10 +568,35 @@ function remapDependencies(snapshot) {
500
568
  }
501
569
  });
502
570
  }
503
- function findOriginalKeys(packages, { data: { packageName, version } }, lockfileVersion, { returnFullKey } = {}) {
504
- const matchedKeys = [];
571
+ // Bucket package keys by their package name so a node only scans its own name's
572
+ // versions instead of every key (was O(nodes * allPackages)). v5 is excluded
573
+ // below and keeps the full scan: its standard keys use a "/" separator while
574
+ // tarball keys use "@", so a single name index would misfile v5 tarballs.
575
+ function indexPackagesByName(packages, lockfileVersion) {
576
+ const isV5 = lockfileVersion < 6;
577
+ const index = new Map();
505
578
  for (const key of Object.keys(packages)) {
506
- const snapshot = packages[key];
579
+ const name = extractNameFromKey(key, isV5);
580
+ let bucket = index.get(name);
581
+ if (!bucket)
582
+ index.set(name, (bucket = []));
583
+ bucket.push([key, packages[key]]);
584
+ }
585
+ return index;
586
+ }
587
+ // npm alias version is "npm:<name>@<ver>"; extract <name> the same way
588
+ // versionIsAlias does so the index lookup matches the alias branch below.
589
+ function aliasTargetName(version) {
590
+ return version.slice('npm:'.length, version.indexOf('@', 'npm:'.length + 1));
591
+ }
592
+ const NO_CANDIDATES = [];
593
+ function findOriginalKeys(packages, packageIndex, node, lockfileVersion, { returnFullKey } = {}) {
594
+ const { data: { packageName, version }, } = node;
595
+ const candidates = lockfileVersion >= 6
596
+ ? (packageIndex.get(version.startsWith('npm:') ? aliasTargetName(version) : packageName) ?? NO_CANDIDATES)
597
+ : Object.entries(packages);
598
+ const matchedKeys = [];
599
+ for (const [key, snapshot] of candidates) {
507
600
  // tarball package
508
601
  if (key.startsWith(`${packageName}@${version}`) &&
509
602
  snapshot.resolution?.['tarball']) {
@@ -562,10 +655,11 @@ function versionIsAlias(key, versionExpr, lockfileVersion) {
562
655
  ? key.startsWith(`${packageName}/${version}`)
563
656
  : key.startsWith(`${packageName}@${version}`);
564
657
  }
565
- function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVersion, workspaceRoot) {
658
+ function mapRootSnapshot(packageJson, rootImporters, packages, packageIndex, graph, lockfileVersion, workspaceRoot) {
566
659
  const workspaceModules = (0, get_workspace_packages_from_graph_1.getWorkspacePackagesFromGraph)(graph);
567
660
  const snapshot = { specifiers: {} };
568
661
  const importers = {};
662
+ const manager = (0, catalog_1.getCatalogManager)(workspaceRoot);
569
663
  [
570
664
  'dependencies',
571
665
  'optionalDependencies',
@@ -575,7 +669,6 @@ function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVe
575
669
  if (packageJson[depType]) {
576
670
  Object.keys(packageJson[depType]).forEach((packageName) => {
577
671
  let version = packageJson[depType][packageName];
578
- const manager = (0, catalog_1.getCatalogManager)(workspaceRoot);
579
672
  if (manager?.isCatalogReference(version)) {
580
673
  version = manager.resolveCatalogReference(workspaceRoot, packageName, version);
581
674
  if (!version) {
@@ -612,7 +705,7 @@ function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVe
612
705
  // peer dependencies are mapped to dependencies
613
706
  let section = depType === 'peerDependencies' ? 'dependencies' : depType;
614
707
  snapshot[section] = snapshot[section] || {};
615
- snapshot[section][packageName] = findOriginalKeys(packages, node, lockfileVersion)[0][0];
708
+ snapshot[section][packageName] = findOriginalKeys(packages, packageIndex, node, lockfileVersion)[0][0];
616
709
  }
617
710
  });
618
711
  }
@@ -38,9 +38,9 @@ function normalizeDependencies(packageJson, graph, workspacePackages, workspaceR
38
38
  ...optionalDependencies,
39
39
  ...peerDependencies,
40
40
  };
41
+ const manager = (0, catalog_1.getCatalogManager)(workspaceRootPath);
41
42
  Object.entries(combinedDependencies).forEach(([packageName, versionRange]) => {
42
43
  let resolvedVersionRange = versionRange;
43
- const manager = (0, catalog_1.getCatalogManager)(workspaceRootPath);
44
44
  if (manager?.isCatalogReference(versionRange)) {
45
45
  resolvedVersionRange = manager.resolveCatalogReference(workspaceRootPath, packageName, versionRange);
46
46
  if (!resolvedVersionRange) {
@@ -177,17 +177,23 @@ function switchNodeToHoisted(node, builder, invBuilder) {
177
177
  const sources = Object.keys(builder.graph.dependencies).filter((name) => builder.graph.dependencies[name].some((d) => d.target === previousName));
178
178
  builder.removeNode(node.name);
179
179
  invBuilder.removeNode(node.name);
180
- // modify the node and re-add it
181
- node.name = `npm:${node.data.packageName}`;
182
- builder.addExternalNode(node);
183
- invBuilder.addExternalNode(node);
180
+ // Re-add under the hoisted name as a new object. The node is shared by
181
+ // reference with the caller's graph, so renaming it in place would leave that
182
+ // graph with a node keyed `npm:<pkg>@<version>` but named `npm:<pkg>`, and
183
+ // every later prune of the same graph would fail to resolve the edge.
184
+ const hoistedNode = {
185
+ ...node,
186
+ name: `npm:${node.data.packageName}`,
187
+ };
188
+ builder.addExternalNode(hoistedNode);
189
+ invBuilder.addExternalNode(hoistedNode);
184
190
  targets.forEach((target) => {
185
- builder.addStaticDependency(node.name, target);
186
- invBuilder.addStaticDependency(target, node.name);
191
+ builder.addStaticDependency(hoistedNode.name, target);
192
+ invBuilder.addStaticDependency(target, hoistedNode.name);
187
193
  });
188
194
  sources.forEach((source) => {
189
- builder.addStaticDependency(source, node.name);
190
- invBuilder.addStaticDependency(node.name, source);
195
+ builder.addStaticDependency(source, hoistedNode.name);
196
+ invBuilder.addStaticDependency(hoistedNode.name, source);
191
197
  });
192
198
  }
193
199
  // BFS to find the shortest path to a dependency specified in package.json
@@ -54,6 +54,9 @@ const YAML_DOCUMENT_SEPARATOR = '\n---\n';
54
54
  // always read the workspace document.
55
55
  // https://github.com/pnpm/pnpm/blob/main/lockfile/fs/src/yamlDocuments.ts
56
56
  function extractMainLockfileDocument(content) {
57
+ // Lockfiles written on Windows may use CRLF line endings, which would never
58
+ // match the LF-only document markers.
59
+ content = content.replace(/\r\n/g, '\n');
57
60
  if (!content.startsWith(YAML_DOCUMENT_START)) {
58
61
  return content;
59
62
  }
@@ -314,9 +314,10 @@ function mapSnapshots(rootDependencies, nodes, packageJson, workspaceModules, is
314
314
  };
315
315
  // yarn classic splits keys when parsing so we need to stich them back together
316
316
  const groupedDependencies = groupDependencies(rootDependencies, isBerry);
317
+ const keyIndex = buildYarnKeyIndex(groupedDependencies);
317
318
  // collect snapshots and their matching keys
318
319
  Object.values(nodes).forEach((node) => {
319
- const foundOriginalKeys = findOriginalKeys(groupedDependencies, node, workspaceModules);
320
+ const foundOriginalKeys = findOriginalKeys(groupedDependencies, keyIndex, node, workspaceModules);
320
321
  if (!foundOriginalKeys) {
321
322
  throw new Error(`Original key(s) not found for "${node.data.packageName}@${node.data.version}" while pruning yarn.lock.`);
322
323
  }
@@ -349,7 +350,7 @@ function mapSnapshots(rootDependencies, nodes, packageJson, workspaceModules, is
349
350
  }
350
351
  if (isBerry) {
351
352
  // look for patched versions
352
- const patch = findPatchedKeys(groupedDependencies, node, resolutions[node.data.packageName]);
353
+ const patch = findPatchedKeys(groupedDependencies, keyIndex, node, resolutions[node.data.packageName]);
353
354
  if (patch) {
354
355
  const [matchedKeys, snapshot] = patch;
355
356
  snapshotMap.set(snapshot, new Set(matchedKeys));
@@ -424,8 +425,34 @@ function isClassicAlias(node, keys) {
424
425
  return (node.data.version.startsWith('npm:') &&
425
426
  keys.some((k) => k === `${node.data.packageName}@${node.data.version}`));
426
427
  }
427
- function findOriginalKeys(dependencies, node, workspaceModules) {
428
+ // Bucket grouped-dependency key expressions by the package names they contain
429
+ // so a node scans only its own name's entries (was O(nodes * allEntries)). A
430
+ // key like "foo@npm:1.0" indexes under "foo"; the inner match checks below are
431
+ // unchanged, so candidates are exactly the entries the old full scan would not
432
+ // have skipped and the result is identical.
433
+ function extractYarnKeyName(key) {
434
+ const at = key.indexOf('@', 1);
435
+ return at === -1 ? key : key.slice(0, at);
436
+ }
437
+ function buildYarnKeyIndex(dependencies) {
438
+ const index = new Map();
428
439
  for (const keyExpr of Object.keys(dependencies)) {
440
+ const seen = new Set();
441
+ for (const k of keyExpr.split(', ')) {
442
+ const name = extractYarnKeyName(k);
443
+ if (seen.has(name))
444
+ continue;
445
+ seen.add(name);
446
+ let bucket = index.get(name);
447
+ if (!bucket)
448
+ index.set(name, (bucket = []));
449
+ bucket.push(keyExpr);
450
+ }
451
+ }
452
+ return index;
453
+ }
454
+ function findOriginalKeys(dependencies, keyIndex, node, workspaceModules) {
455
+ for (const keyExpr of keyIndex.get(node.data.packageName) ?? []) {
429
456
  const snapshot = dependencies[keyExpr];
430
457
  const keys = keyExpr.split(', ');
431
458
  if (!keys.some((k) => k.startsWith(`${node.data.packageName}@`))) {
@@ -450,8 +477,8 @@ function findOriginalKeys(dependencies, node, workspaceModules) {
450
477
  }
451
478
  }
452
479
  }
453
- function findPatchedKeys(dependencies, node, resolutionVersion) {
454
- for (const keyExpr of Object.keys(dependencies)) {
480
+ function findPatchedKeys(dependencies, keyIndex, node, resolutionVersion) {
481
+ for (const keyExpr of keyIndex.get(node.data.packageName) ?? []) {
455
482
  const snapshot = dependencies[keyExpr];
456
483
  const keys = keyExpr.split(', ');
457
484
  if (!keys[0].startsWith(`${node.data.packageName}@patch:`)) {
@@ -13,6 +13,7 @@ const getTouchedNpmPackages = (touchedFiles, _, nxJson, packageJson, projectGrap
13
13
  let touched = [];
14
14
  const changes = packageJsonChange.getChanges();
15
15
  const npmPackages = Object.values(projectGraph.externalNodes);
16
+ let packagesByName;
16
17
  const missingTouchedNpmPackages = [];
17
18
  for (const c of changes) {
18
19
  if ((0, json_diff_1.isJsonChange)(c) &&
@@ -53,26 +54,20 @@ const getTouchedNpmPackages = (touchedFiles, _, nxJson, packageJson, projectGrap
53
54
  (c.path[0] === 'overrides' ||
54
55
  c.path[0] === 'resolutions' ||
55
56
  (c.path[0] === 'pnpm' && c.path[1] === 'overrides'))) {
56
- // Changes to overrides, resolutions, or pnpm.overrides
57
- // Find which package was changed and mark projects that depend on it as affected
58
- const packageName = c.path[0] === 'pnpm' ? c.path[2] : c.path[1];
59
- if (packageName) {
60
- // Look for the npm package in external nodes
61
- let npmPackage = npmPackages.find((pkg) => pkg.data.packageName === packageName);
62
- if (npmPackage) {
63
- touched.push(npmPackage.name);
64
- // If it's a global package, all projects are affected
65
- if ('packageName' in npmPackage.data &&
66
- globalPackages.has(npmPackage.data.packageName)) {
67
- return Object.keys(projectGraph.nodes);
68
- }
69
- }
70
- else {
71
- // If the package isn't found in external nodes, it might affect all projects
72
- // since overrides can affect transitive dependencies
73
- return Object.keys(projectGraph.nodes);
74
- }
57
+ const packageSelector = getPackageSelector(c);
58
+ if (!packageSelector)
59
+ continue;
60
+ packagesByName ??= groupPackagesByName(npmPackages);
61
+ const matchingNpmPackages = findPackagesForSelector(packageSelector, packagesByName, c.path[0] === 'pnpm');
62
+ // An unresolved selector can still target a transitive dependency,
63
+ // so fall back to marking every project affected.
64
+ if (!matchingNpmPackages.length) {
65
+ return Object.keys(projectGraph.nodes);
75
66
  }
67
+ if (matchingNpmPackages.some((pkg) => globalPackages.has(pkg.data.packageName))) {
68
+ return Object.keys(projectGraph.nodes);
69
+ }
70
+ touched.push(...matchingNpmPackages.map((pkg) => pkg.name));
76
71
  }
77
72
  else if ((0, file_utils_1.isWholeFileChange)(c)) {
78
73
  // Whole file was touched, so all npm packages are touched.
@@ -83,9 +78,45 @@ const getTouchedNpmPackages = (touchedFiles, _, nxJson, packageJson, projectGrap
83
78
  if (missingTouchedNpmPackages.length) {
84
79
  logger_1.logger.warn(`The affected projects might have not been identified properly. The package(s) ${missingTouchedNpmPackages.join(', ')} were not found. Please open an issue in GitHub including the package.json file.`);
85
80
  }
86
- return touched;
81
+ return [...new Set(touched)];
87
82
  };
88
83
  exports.getTouchedNpmPackages = getTouchedNpmPackages;
84
+ function getPackageSelector(change) {
85
+ if (typeof change.value.lhs !== 'string' &&
86
+ typeof change.value.rhs !== 'string') {
87
+ return;
88
+ }
89
+ const selectorIndex = change.path[0] === 'pnpm' ? 2 : change.path.length - 1;
90
+ const selector = change.path[selectorIndex];
91
+ return selector === '.' ? change.path[selectorIndex - 1] : selector;
92
+ }
93
+ function groupPackagesByName(npmPackages) {
94
+ const packagesByName = new Map();
95
+ for (const pkg of npmPackages) {
96
+ const packageName = pkg.data.packageName;
97
+ if (!packageName)
98
+ continue;
99
+ const packages = packagesByName.get(packageName);
100
+ if (packages) {
101
+ packages.push(pkg);
102
+ }
103
+ else {
104
+ packagesByName.set(packageName, [pkg]);
105
+ }
106
+ }
107
+ return packagesByName;
108
+ }
109
+ function findPackagesForSelector(selector, packagesByName, isPnpmOverride) {
110
+ if (isPnpmOverride) {
111
+ // Pnpm does not treat `>` as a parent delimiter when it starts a range.
112
+ const parentDelimiterIndex = selector.search(/[^ |@]>/);
113
+ if (parentDelimiterIndex !== -1) {
114
+ selector = selector.slice(parentDelimiterIndex + 2);
115
+ }
116
+ }
117
+ const packageName = selector.match(/(?:^|\/)(@[^/@>\s]+\/[^/@>\s]+|[^/@>\s]+)(?:@[^/]*)?$/)?.[1];
118
+ return packageName ? (packagesByName.get(packageName) ?? []) : [];
119
+ }
89
120
  function getGlobalPackages(plugins) {
90
121
  return (plugins ?? [])
91
122
  .map((p) => (0, get_package_name_from_import_path_1.getPackageNameFromImportPath)(typeof p === 'string' ? p : p.plugin))