nx 23.1.0 → 23.2.0-beta.1

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 (79) hide show
  1. package/dist/bin/nx.js +1 -1
  2. package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
  3. package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
  4. package/dist/src/command-line/add/add.js +8 -1
  5. package/dist/src/command-line/import/import.js +1 -1
  6. package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
  7. package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
  8. package/dist/src/command-line/init/command-object.js +10 -2
  9. package/dist/src/command-line/init/configure-plugins.js +3 -2
  10. package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
  11. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
  12. package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
  13. package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
  14. package/dist/src/command-line/init/implementation/angular/index.js +4 -2
  15. package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
  16. package/dist/src/command-line/init/implementation/utils.js +22 -3
  17. package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
  18. package/dist/src/command-line/migrate/execute-migration.js +389 -0
  19. package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
  20. package/dist/src/command-line/migrate/migrate.d.ts +14 -45
  21. package/dist/src/command-line/migrate/migrate.js +58 -375
  22. package/dist/src/command-line/release/utils/git.js +6 -2
  23. package/dist/src/command-line/release/utils/release-graph.d.ts +2 -2
  24. package/dist/src/command-line/release/utils/shared.d.ts +2 -2
  25. package/dist/src/command-line/show/show-target/info.js +11 -2
  26. package/dist/src/commands-runner/create-command-graph.js +5 -0
  27. package/dist/src/config/nx-json.d.ts +1 -1
  28. package/dist/src/core/graph/main.js +1 -1
  29. package/dist/src/daemon/client/client.d.ts +0 -2
  30. package/dist/src/daemon/client/client.js +17 -19
  31. package/dist/src/devkit-internals.d.ts +2 -0
  32. package/dist/src/devkit-internals.js +7 -1
  33. package/dist/src/generators/utils/nx-json.js +13 -3
  34. package/dist/src/native/index.d.ts +1 -1
  35. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  36. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  37. package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
  38. package/dist/src/plugins/js/lock-file/pnpm-parser.js +46 -15
  39. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +1 -1
  40. package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
  41. package/dist/src/plugins/js/utils/register.d.ts +18 -0
  42. package/dist/src/plugins/js/utils/register.js +81 -0
  43. package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
  44. package/dist/src/plugins/js/utils/typescript.js +15 -0
  45. package/dist/src/project-graph/error-types.js +41 -1
  46. package/dist/src/project-graph/file-utils.js +6 -1
  47. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +4 -0
  48. package/dist/src/project-graph/plugins/transpiler.js +4 -0
  49. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +9 -12
  50. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +77 -61
  51. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +1 -1
  52. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +1 -1
  53. package/dist/src/project-graph/utils/project-configuration-utils.js +4 -6
  54. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +11 -4
  55. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
  56. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
  57. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
  58. package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
  59. package/dist/src/tasks-runner/run-command.js +5 -3
  60. package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
  61. package/dist/src/utils/acknowledge-build-scripts.js +100 -0
  62. package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
  63. package/dist/src/utils/catalog/manager-utils.js +22 -5
  64. package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
  65. package/dist/src/utils/catalog/pnpm-manager.js +3 -15
  66. package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
  67. package/dist/src/utils/catalog/yarn-manager.js +3 -15
  68. package/dist/src/utils/command-line-utils.js +30 -20
  69. package/dist/src/utils/git-revision.d.ts +12 -0
  70. package/dist/src/utils/git-revision.js +25 -0
  71. package/dist/src/utils/git-utils.d.ts +2 -3
  72. package/dist/src/utils/git-utils.js +101 -47
  73. package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
  74. package/dist/src/utils/package-manager.js +6 -2
  75. package/dist/src/utils/provenance.js +12 -2
  76. package/migrations.json +7 -1
  77. package/package.json +11 -11
  78. package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
  79. package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
@@ -26,8 +26,6 @@ export declare class DaemonClient {
26
26
  private _daemonStatus;
27
27
  private _waitForDaemonReady;
28
28
  private _daemonReady;
29
- private _out;
30
- private _err;
31
29
  private fileWatcherMessenger;
32
30
  private fileWatcherReconnecting;
33
31
  private fileWatcherCallbacks;
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.daemonClient = exports.DaemonClient = void 0;
4
4
  exports.isDaemonEnabled = isDaemonEnabled;
5
5
  const child_process_1 = require("child_process");
6
- const promises_1 = require("fs/promises");
7
6
  const net_1 = require("net");
8
7
  const node_fs_1 = require("node:fs");
9
8
  const path_1 = require("path");
@@ -12,6 +11,7 @@ const configuration_1 = require("../../config/configuration");
12
11
  const nx_json_1 = require("../../config/nx-json");
13
12
  const native_1 = require("../../native");
14
13
  const error_types_1 = require("../../project-graph/error-types");
14
+ const typescript_1 = require("../../plugins/js/utils/typescript");
15
15
  const project_graph_1 = require("../../project-graph/project-graph");
16
16
  const consume_messages_from_socket_1 = require("../../utils/consume-messages-from-socket");
17
17
  const delayed_spinner_1 = require("../../utils/delayed-spinner");
@@ -62,8 +62,6 @@ class DaemonClient {
62
62
  this._daemonStatus = DaemonStatus.DISCONNECTED;
63
63
  this._waitForDaemonReady = null;
64
64
  this._daemonReady = null;
65
- this._out = null;
66
- this._err = null;
67
65
  this.fileWatcherReconnecting = false;
68
66
  this.fileWatcherCallbacks = new Map();
69
67
  this.fileWatcherConfigs = new Map();
@@ -127,10 +125,6 @@ class DaemonClient {
127
125
  this.currentResolve = null;
128
126
  this.currentReject = null;
129
127
  this._enabled = undefined;
130
- this._out?.close();
131
- this._err?.close();
132
- this._out = null;
133
- this._err = null;
134
128
  // Clean up file watcher and project graph listener connections
135
129
  this.fileWatcherMessenger?.close();
136
130
  this.fileWatcherMessenger = undefined;
@@ -949,25 +943,29 @@ class DaemonClient {
949
943
  if (!(0, node_fs_1.existsSync)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE)) {
950
944
  (0, node_fs_1.writeFileSync)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, '');
951
945
  }
952
- // Open the log handles into locals first. If the previous daemon's
953
- // socket close handler fires reset() while we're awaiting these opens,
954
- // it would null out this._out/this._err and the spawn below would hit
955
- // `Cannot read properties of null (reading 'fd')`.
956
- const [out, err] = await Promise.all([
957
- (0, promises_1.open)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a'),
958
- (0, promises_1.open)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a'),
959
- ]);
960
- this._out = out;
961
- this._err = err;
946
+ // Redirect the detached daemon's stdout/stderr into the log file. The
947
+ // child dup's these descriptors at spawn, so we close ours right after
948
+ // instead of holding them for the life of this process (Node >=26 turns a
949
+ // file descriptor closed during garbage collection into a fatal error).
950
+ const outFd = (0, node_fs_1.openSync)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a');
951
+ const errFd = (0, node_fs_1.openSync)(tmp_dir_1.DAEMON_OUTPUT_LOG_FILE, 'a');
962
952
  logger_1.clientLogger.log(`[Client] Starting new daemon server in background`);
963
- const backgroundProcess = (0, child_process_1.spawn)(process.execPath, [(0, path_1.join)(__dirname, `../server/start.js`)], {
953
+ const backgroundProcess = (0, child_process_1.spawn)(process.execPath, [
954
+ // Spawn with the same resolve conditions Nx uses for plugin entries so a
955
+ // source-loaded plugin's transitive workspace imports resolve to source.
956
+ ...(0, typescript_1.getPluginResolveConditionNodeArgs)(),
957
+ (0, path_1.join)(__dirname, `../server/start.js`),
958
+ ], {
964
959
  cwd: workspace_root_1.workspaceRoot,
965
- stdio: ['ignore', out.fd, err.fd],
960
+ stdio: ['ignore', outFd, errFd],
966
961
  detached: true,
967
962
  windowsHide: true,
968
963
  shell: false,
969
964
  env: (0, daemon_environment_1.getDaemonEnv)(),
970
965
  });
966
+ // The child now owns dup'd copies of the descriptors, so release ours.
967
+ (0, node_fs_1.closeSync)(outFd);
968
+ (0, node_fs_1.closeSync)(errFd);
971
969
  // if this process is the process that spawned the daemon,
972
970
  // the daemon env is already up to date
973
971
  this.envReflectionSent = true;
@@ -38,3 +38,5 @@ export { PluginCache, safeWriteFileCache } from './utils/plugin-cache-utils';
38
38
  export { emitPluginWorkerLog } from './project-graph/plugins/isolation/worker-streaming';
39
39
  export { resolveImplementation, resolveSchema, ImplementationResolutionError, SchemaResolutionError, } from './config/schema-utils';
40
40
  export { resolvePrompt, PromptResolutionError, } from './command-line/migrate/prompt-files';
41
+ export { getCatalogManager, getCatalogDependenciesFromPackageJson, } from './utils/catalog';
42
+ export { acknowledgeBuildScripts } from './utils/acknowledge-build-scripts';
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.PromptResolutionError = exports.resolvePrompt = exports.SchemaResolutionError = exports.ImplementationResolutionError = exports.resolveSchema = exports.resolveImplementation = exports.emitPluginWorkerLog = exports.safeWriteFileCache = exports.PluginCache = exports.handleImport = exports.signalToCode = exports.globalSpinner = exports.readYamlFile = exports.isUsingPrettierInTree = exports.isCI = exports.interpolate = exports.requireWithTsconfigFallback = exports.forceRegisterEsmLoader = exports.loadTsFile = exports.registerTsProject = exports.LoadedNxPlugin = exports.retrieveProjectConfigurations = exports.findProjectForPath = exports.createProjectRootMappings = exports.createProjectRootMappingsFromProjectConfigurations = exports.hashMultiGlobWithWorkspaceContext = exports.hashWithWorkspaceContext = exports.hashObject = exports.splitByColons = exports.installPackageToTmpAsync = exports.installPackageToTmp = exports.readModulePackageJson = exports.stripIndent = exports.sortObjectByKeys = exports.combineOptionsForExecutor = exports.splitTarget = exports.getIgnoreObjectForTree = exports.readTargetDefaultsForTarget = exports.findMatchingProjects = exports.findMatchingConfigFiles = exports.readProjectConfigurationsFromRootMap = exports.mergeTargetConfigurations = exports.retrieveProjectConfigurationsWithAngularProjects = exports.calculateDefaultProjectName = exports.readNxJsonFromDisk = exports.parseExecutor = exports.getExecutorInformation = exports.createTempNpmDirectory = void 0;
3
+ exports.getCatalogDependenciesFromPackageJson = exports.getCatalogManager = exports.PromptResolutionError = exports.resolvePrompt = exports.SchemaResolutionError = exports.ImplementationResolutionError = exports.resolveSchema = exports.resolveImplementation = exports.emitPluginWorkerLog = exports.safeWriteFileCache = exports.PluginCache = exports.handleImport = exports.signalToCode = exports.globalSpinner = exports.readYamlFile = exports.isUsingPrettierInTree = exports.isCI = exports.interpolate = exports.requireWithTsconfigFallback = exports.forceRegisterEsmLoader = exports.loadTsFile = exports.registerTsProject = exports.LoadedNxPlugin = exports.retrieveProjectConfigurations = exports.findProjectForPath = exports.createProjectRootMappings = exports.createProjectRootMappingsFromProjectConfigurations = exports.hashMultiGlobWithWorkspaceContext = exports.hashWithWorkspaceContext = exports.hashObject = exports.splitByColons = exports.installPackageToTmpAsync = exports.installPackageToTmp = exports.readModulePackageJson = exports.stripIndent = exports.sortObjectByKeys = exports.combineOptionsForExecutor = exports.splitTarget = exports.getIgnoreObjectForTree = exports.readTargetDefaultsForTarget = exports.findMatchingProjects = exports.findMatchingConfigFiles = exports.readProjectConfigurationsFromRootMap = exports.mergeTargetConfigurations = exports.retrieveProjectConfigurationsWithAngularProjects = exports.calculateDefaultProjectName = exports.readNxJsonFromDisk = exports.parseExecutor = exports.getExecutorInformation = exports.createTempNpmDirectory = void 0;
4
+ exports.acknowledgeBuildScripts = void 0;
4
5
  const tslib_1 = require("tslib");
5
6
  /**
6
7
  * Note to developers: STOP! These exports are available via requireNx in @nx/devkit.
@@ -90,3 +91,8 @@ Object.defineProperty(exports, "SchemaResolutionError", { enumerable: true, get:
90
91
  var prompt_files_1 = require("./command-line/migrate/prompt-files");
91
92
  Object.defineProperty(exports, "resolvePrompt", { enumerable: true, get: function () { return prompt_files_1.resolvePrompt; } });
92
93
  Object.defineProperty(exports, "PromptResolutionError", { enumerable: true, get: function () { return prompt_files_1.PromptResolutionError; } });
94
+ var catalog_1 = require("./utils/catalog");
95
+ Object.defineProperty(exports, "getCatalogManager", { enumerable: true, get: function () { return catalog_1.getCatalogManager; } });
96
+ Object.defineProperty(exports, "getCatalogDependenciesFromPackageJson", { enumerable: true, get: function () { return catalog_1.getCatalogDependenciesFromPackageJson; } });
97
+ var acknowledge_build_scripts_1 = require("./utils/acknowledge-build-scripts");
98
+ Object.defineProperty(exports, "acknowledgeBuildScripts", { enumerable: true, get: function () { return acknowledge_build_scripts_1.acknowledgeBuildScripts; } });
@@ -42,9 +42,19 @@ function updateNxJson(tree, nxJson) {
42
42
  }
43
43
  function readNxJsonExtends(tree, extendsPath) {
44
44
  try {
45
- return (0, json_1.readJson)(tree, (0, path_1.relative)(tree.root, require.resolve(extendsPath, {
46
- paths: [tree.root],
47
- })));
45
+ let resolvedExtendsPath;
46
+ try {
47
+ resolvedExtendsPath = require.resolve(extendsPath, {
48
+ paths: [tree.root],
49
+ });
50
+ }
51
+ catch {
52
+ // Tree roots without a node_modules folder (e.g. the in-memory trees
53
+ // used in tests) can't anchor module resolution; fall back to
54
+ // resolving from the running nx package.
55
+ resolvedExtendsPath = require.resolve(extendsPath);
56
+ }
57
+ return (0, json_1.readJson)(tree, (0, path_1.relative)(tree.root, resolvedExtendsPath));
48
58
  }
49
59
  catch (e) {
50
60
  throw new Error(`Unable to resolve nx.json extends. Error: ${e.message}`);
@@ -28,7 +28,7 @@ export declare class ExternalObject<T> {
28
28
  }
29
29
  }
30
30
  export declare class AppLifeCycle {
31
- constructor(tasks: Array<Task>, initiatingTasks: Array<string>, runMode: RunMode, pinnedTasks: Array<string>, tuiCliArgs: TuiCliArgs, tuiConfig: TuiConfig, titleText: string, workspaceRoot: string, taskGraph: TaskGraph)
31
+ constructor(tasks: Array<Task>, initiatingTasks: Array<string>, runMode: RunMode, pinnedTasks: Array<string>, tuiCliArgs: TuiCliArgs, tuiConfig: TuiConfig, titleText: string, workspaceRoot: string, taskGraph: TaskGraph, isCloudEnabled?: boolean | undefined | null)
32
32
  startCommand(threadCount?: number | undefined | null): void
33
33
  scheduleTask(task: Task): void
34
34
  startTasks(tasks: Array<Task>, metadata: object): void
Binary file
@@ -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
  }
@@ -142,7 +142,12 @@ function getNodes(data, isV5) {
142
142
  if (data.patchedDependencies) {
143
143
  for (const specifier of Object.keys(data.patchedDependencies)) {
144
144
  const patchInfo = data.patchedDependencies[specifier];
145
- if (patchInfo && typeof patchInfo === 'object' && 'hash' in patchInfo) {
145
+ const patchHash = typeof patchInfo === 'string'
146
+ ? patchInfo
147
+ : patchInfo && typeof patchInfo === 'object' && 'hash' in patchInfo
148
+ ? patchInfo.hash
149
+ : undefined;
150
+ if (patchHash) {
146
151
  const packageName = extractNameFromKey(specifier, false);
147
152
  const versionSpecifier = getVersion(specifier, packageName) || null;
148
153
  if (!patchEntriesByPackage.has(packageName)) {
@@ -150,7 +155,7 @@ function getNodes(data, isV5) {
150
155
  }
151
156
  patchEntriesByPackage.get(packageName).push({
152
157
  versionSpecifier,
153
- hash: patchInfo.hash,
158
+ hash: patchHash,
154
159
  });
155
160
  }
156
161
  }
@@ -391,9 +396,12 @@ function parseBaseVersion(rawVersion, isV5) {
391
396
  }
392
397
  function stringifyPnpmLockfile(graph, rootLockFileContent, packageJson, workspaceRoot) {
393
398
  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);
399
+ const { lockfileVersion, importers } = data;
400
+ // pnpm omits the packages block for workspace-only lockfiles (no external deps)
401
+ const packages = data.packages ?? {};
402
+ const packageIndex = indexPackagesByName(packages, +lockfileVersion);
403
+ const { snapshot: rootSnapshot, importers: requiredImporters } = mapRootSnapshot(packageJson, importers, packages, packageIndex, graph, +lockfileVersion, workspaceRoot);
404
+ const snapshots = mapSnapshots(packages, packageIndex, graph.externalNodes, +lockfileVersion);
397
405
  const workspaceModules = (0, get_workspace_packages_from_graph_1.getWorkspacePackagesFromGraph)(graph);
398
406
  // Walk transitive workspace deps so every package copy-workspace-modules
399
407
  // writes to disk has a matching importer block. Without this, pnpm errors
@@ -455,12 +463,10 @@ function stringifyPnpmLockfile(graph, rootLockFileContent, packageJson, workspac
455
463
  };
456
464
  return (0, pnpm_normalizer_1.stringifyToPnpmYaml)(output);
457
465
  }
458
- function mapSnapshots(packages, nodes, lockfileVersion) {
466
+ function mapSnapshots(packages, packageIndex, nodes, lockfileVersion) {
459
467
  const result = {};
460
468
  Object.values(nodes).forEach((node) => {
461
- const matchedKeys = findOriginalKeys(packages, node, lockfileVersion, {
462
- returnFullKey: true,
463
- });
469
+ const matchedKeys = findOriginalKeys(packages, packageIndex, node, lockfileVersion, { returnFullKey: true });
464
470
  // the package manager doesn't check for types of dependencies
465
471
  // so we can safely set all to prod
466
472
  matchedKeys.forEach(([key, snapshot]) => {
@@ -500,10 +506,35 @@ function remapDependencies(snapshot) {
500
506
  }
501
507
  });
502
508
  }
503
- function findOriginalKeys(packages, { data: { packageName, version } }, lockfileVersion, { returnFullKey } = {}) {
504
- const matchedKeys = [];
509
+ // Bucket package keys by their package name so a node only scans its own name's
510
+ // versions instead of every key (was O(nodes * allPackages)). v5 is excluded
511
+ // below and keeps the full scan: its standard keys use a "/" separator while
512
+ // tarball keys use "@", so a single name index would misfile v5 tarballs.
513
+ function indexPackagesByName(packages, lockfileVersion) {
514
+ const isV5 = lockfileVersion < 6;
515
+ const index = new Map();
505
516
  for (const key of Object.keys(packages)) {
506
- const snapshot = packages[key];
517
+ const name = extractNameFromKey(key, isV5);
518
+ let bucket = index.get(name);
519
+ if (!bucket)
520
+ index.set(name, (bucket = []));
521
+ bucket.push([key, packages[key]]);
522
+ }
523
+ return index;
524
+ }
525
+ // npm alias version is "npm:<name>@<ver>"; extract <name> the same way
526
+ // versionIsAlias does so the index lookup matches the alias branch below.
527
+ function aliasTargetName(version) {
528
+ return version.slice('npm:'.length, version.indexOf('@', 'npm:'.length + 1));
529
+ }
530
+ const NO_CANDIDATES = [];
531
+ function findOriginalKeys(packages, packageIndex, node, lockfileVersion, { returnFullKey } = {}) {
532
+ const { data: { packageName, version }, } = node;
533
+ const candidates = lockfileVersion >= 6
534
+ ? (packageIndex.get(version.startsWith('npm:') ? aliasTargetName(version) : packageName) ?? NO_CANDIDATES)
535
+ : Object.entries(packages);
536
+ const matchedKeys = [];
537
+ for (const [key, snapshot] of candidates) {
507
538
  // tarball package
508
539
  if (key.startsWith(`${packageName}@${version}`) &&
509
540
  snapshot.resolution?.['tarball']) {
@@ -562,10 +593,11 @@ function versionIsAlias(key, versionExpr, lockfileVersion) {
562
593
  ? key.startsWith(`${packageName}/${version}`)
563
594
  : key.startsWith(`${packageName}@${version}`);
564
595
  }
565
- function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVersion, workspaceRoot) {
596
+ function mapRootSnapshot(packageJson, rootImporters, packages, packageIndex, graph, lockfileVersion, workspaceRoot) {
566
597
  const workspaceModules = (0, get_workspace_packages_from_graph_1.getWorkspacePackagesFromGraph)(graph);
567
598
  const snapshot = { specifiers: {} };
568
599
  const importers = {};
600
+ const manager = (0, catalog_1.getCatalogManager)(workspaceRoot);
569
601
  [
570
602
  'dependencies',
571
603
  'optionalDependencies',
@@ -575,7 +607,6 @@ function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVe
575
607
  if (packageJson[depType]) {
576
608
  Object.keys(packageJson[depType]).forEach((packageName) => {
577
609
  let version = packageJson[depType][packageName];
578
- const manager = (0, catalog_1.getCatalogManager)(workspaceRoot);
579
610
  if (manager?.isCatalogReference(version)) {
580
611
  version = manager.resolveCatalogReference(workspaceRoot, packageName, version);
581
612
  if (!version) {
@@ -612,7 +643,7 @@ function mapRootSnapshot(packageJson, rootImporters, packages, graph, lockfileVe
612
643
  // peer dependencies are mapped to dependencies
613
644
  let section = depType === 'peerDependencies' ? 'dependencies' : depType;
614
645
  snapshot[section] = snapshot[section] || {};
615
- snapshot[section][packageName] = findOriginalKeys(packages, node, lockfileVersion)[0][0];
646
+ snapshot[section][packageName] = findOriginalKeys(packages, packageIndex, node, lockfileVersion)[0][0];
616
647
  }
617
648
  });
618
649
  }
@@ -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) {
@@ -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:`)) {
@@ -96,6 +96,24 @@ export declare function ensureNodeNextEsmResolverRegistered(): void;
96
96
  * unaffected when both files exist. Idempotent on repeat calls.
97
97
  */
98
98
  export declare function ensureCjsResolverPatched(): void;
99
+ /**
100
+ * Make Node's module resolver honor the given export conditions for the rest of
101
+ * the process, via `module.registerHooks()`. Used when a local plugin is loaded
102
+ * from source in-process (no child process to pass `--conditions` to at spawn):
103
+ * the hook appends the conditions to every resolution so the plugin's transitive
104
+ * workspace imports resolve to source the same way the plugin entry did.
105
+ *
106
+ * No-op when:
107
+ * - `conditions` is empty (nothing to inject);
108
+ * - every target condition is already active at startup (a spawned plugin
109
+ * worker or daemon was launched with the full `--conditions` set, so Node's
110
+ * resolver already honors them and the per-resolve hook would be redundant);
111
+ * - `module.registerHooks` is unavailable (Node < 22.15 / < 23.5). Those
112
+ * runtimes keep the `NODE_OPTIONS=--conditions` escape hatch.
113
+ *
114
+ * Idempotent and best-effort.
115
+ */
116
+ export declare function ensureResolveConditionsInjected(conditions: string[]): void;
99
117
  /**
100
118
  * Whether Nx will defer to Node's native TypeScript stripping for the next
101
119
  * `.ts` load. Mirrors the gate used by `loadTsFile`/`registerTsProject` so