nx 23.1.0 → 23.1.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 (126) hide show
  1. package/dist/bin/nx.js +2 -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/analytics/analytics.d.ts +6 -0
  5. package/dist/src/analytics/analytics.js +16 -8
  6. package/dist/src/analytics/index.d.ts +1 -1
  7. package/dist/src/analytics/index.js +2 -1
  8. package/dist/src/command-line/add/add.js +8 -1
  9. package/dist/src/command-line/graph/graph.d.ts +1 -0
  10. package/dist/src/command-line/graph/graph.js +15 -6
  11. package/dist/src/command-line/import/import.js +1 -1
  12. package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
  13. package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
  14. package/dist/src/command-line/init/command-object.js +10 -2
  15. package/dist/src/command-line/init/configure-plugins.js +3 -2
  16. package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
  17. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
  18. package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
  19. package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
  20. package/dist/src/command-line/init/implementation/angular/index.js +4 -2
  21. package/dist/src/command-line/init/implementation/angular/integrated-workspace.js +2 -5
  22. package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +3 -0
  23. package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +4 -1
  24. package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
  25. package/dist/src/command-line/init/implementation/utils.js +22 -3
  26. package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
  27. package/dist/src/command-line/migrate/execute-migration.js +389 -0
  28. package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
  29. package/dist/src/command-line/migrate/migrate.d.ts +1 -45
  30. package/dist/src/command-line/migrate/migrate.js +33 -376
  31. package/dist/src/command-line/nx-cloud/connect/connect-to-nx-cloud.js +1 -1
  32. package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
  33. package/dist/src/command-line/release/utils/git.js +6 -2
  34. package/dist/src/command-line/release/utils/release-graph.d.ts +2 -2
  35. package/dist/src/command-line/release/utils/remote-release-clients/github.js +1 -1
  36. package/dist/src/command-line/release/utils/remote-release-clients/gitlab.js +1 -1
  37. package/dist/src/command-line/release/utils/shared.d.ts +2 -2
  38. package/dist/src/command-line/show/show-target/info.js +11 -2
  39. package/dist/src/command-line/show/show-target/inputs.js +50 -153
  40. package/dist/src/command-line/show/show-target/outputs.js +48 -173
  41. package/dist/src/command-line/show/show-target/utils.d.ts +28 -0
  42. package/dist/src/command-line/show/show-target/utils.js +83 -3
  43. package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
  44. package/dist/src/commands-runner/create-command-graph.js +5 -0
  45. package/dist/src/config/nx-json.d.ts +5 -3
  46. package/dist/src/core/graph/main.js +1 -1
  47. package/dist/src/daemon/client/client.d.ts +0 -2
  48. package/dist/src/daemon/client/client.js +17 -19
  49. package/dist/src/daemon/server/handle-tasks-execution-hooks.d.ts +1 -1
  50. package/dist/src/daemon/server/server.js +7 -4
  51. package/dist/src/devkit-internals.d.ts +3 -0
  52. package/dist/src/devkit-internals.js +10 -1
  53. package/dist/src/generators/utils/nx-json.js +13 -3
  54. package/dist/src/hasher/check-task-files.d.ts +86 -0
  55. package/dist/src/hasher/check-task-files.js +378 -0
  56. package/dist/src/native/index.d.ts +21 -2
  57. package/dist/src/native/native-bindings.js +2 -0
  58. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  59. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  60. package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
  61. package/dist/src/plugins/js/lock-file/pnpm-parser.js +50 -17
  62. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +1 -1
  63. package/dist/src/plugins/js/lock-file/utils/pnpm-normalizer.js +3 -0
  64. package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
  65. package/dist/src/plugins/js/project-graph/build-dependencies/target-project-locator.js +12 -4
  66. package/dist/src/plugins/js/utils/register.d.ts +18 -0
  67. package/dist/src/plugins/js/utils/register.js +81 -0
  68. package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
  69. package/dist/src/plugins/js/utils/typescript.js +15 -0
  70. package/dist/src/project-graph/build-project-graph.js +7 -1
  71. package/dist/src/project-graph/error-types.js +41 -1
  72. package/dist/src/project-graph/file-utils.js +6 -1
  73. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +4 -0
  74. package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
  75. package/dist/src/project-graph/plugins/transpiler.js +4 -0
  76. package/dist/src/project-graph/project-graph.js +3 -0
  77. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +10 -12
  78. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +93 -63
  79. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +5 -8
  80. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +11 -14
  81. package/dist/src/project-graph/utils/project-configuration/source-maps.d.ts +12 -5
  82. package/dist/src/project-graph/utils/project-configuration/source-maps.js +21 -7
  83. package/dist/src/project-graph/utils/project-configuration/target-defaults.d.ts +1 -1
  84. package/dist/src/project-graph/utils/project-configuration/target-defaults.js +26 -27
  85. package/dist/src/project-graph/utils/project-configuration/target-merging.js +50 -8
  86. package/dist/src/project-graph/utils/project-configuration/target-normalization.js +16 -3
  87. package/dist/src/project-graph/utils/project-configuration/utils.d.ts +15 -0
  88. package/dist/src/project-graph/utils/project-configuration/utils.js +42 -6
  89. package/dist/src/project-graph/utils/project-configuration-utils.d.ts +15 -12
  90. package/dist/src/project-graph/utils/project-configuration-utils.js +79 -101
  91. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +11 -4
  92. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
  93. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
  94. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
  95. package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
  96. package/dist/src/tasks-runner/run-command.js +4 -2
  97. package/dist/src/tasks-runner/task-env.d.ts +9 -0
  98. package/dist/src/tasks-runner/task-env.js +26 -2
  99. package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
  100. package/dist/src/tasks-runner/task-orchestrator.js +23 -10
  101. package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
  102. package/dist/src/utils/acknowledge-build-scripts.js +102 -0
  103. package/dist/src/utils/analytics-prompt.d.ts +0 -6
  104. package/dist/src/utils/analytics-prompt.js +0 -51
  105. package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
  106. package/dist/src/utils/catalog/manager-utils.js +22 -5
  107. package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
  108. package/dist/src/utils/catalog/pnpm-manager.js +3 -15
  109. package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
  110. package/dist/src/utils/catalog/yarn-manager.js +3 -15
  111. package/dist/src/utils/command-line-utils.js +30 -20
  112. package/dist/src/utils/git-revision.d.ts +12 -0
  113. package/dist/src/utils/git-revision.js +25 -0
  114. package/dist/src/utils/git-utils.d.ts +16 -3
  115. package/dist/src/utils/git-utils.js +146 -52
  116. package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
  117. package/dist/src/utils/package-json.js +11 -7
  118. package/dist/src/utils/package-manager.d.ts +3 -1
  119. package/dist/src/utils/package-manager.js +28 -4
  120. package/dist/src/utils/provenance.js +12 -2
  121. package/dist/src/utils/workspace-id.d.ts +20 -0
  122. package/dist/src/utils/workspace-id.js +54 -0
  123. package/migrations.json +7 -1
  124. package/package.json +22 -17
  125. package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
  126. package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
@@ -4,6 +4,10 @@ exports.GitRepository = void 0;
4
4
  exports.cloneFromUpstream = cloneFromUpstream;
5
5
  exports.parseVcsRemoteUrl = parseVcsRemoteUrl;
6
6
  exports.getVcsRemoteInfo = getVcsRemoteInfo;
7
+ exports.getGitRootPath = getGitRootPath;
8
+ exports.getGitRootRelativePath = getGitRootRelativePath;
9
+ exports.isShallowRepository = isShallowRepository;
10
+ exports.getFirstCommitSha = getFirstCommitSha;
7
11
  exports.isGitRepository = isGitRepository;
8
12
  exports.hasUncommittedChanges = hasUncommittedChanges;
9
13
  exports.getUncommittedChangesSnapshot = getUncommittedChangesSnapshot;
@@ -16,9 +20,9 @@ const crypto = tslib_1.__importStar(require("crypto"));
16
20
  const fs = tslib_1.__importStar(require("fs"));
17
21
  const path_1 = require("path");
18
22
  const logger_1 = require("./logger");
19
- function execAsync(command, execOptions) {
23
+ function execFileAsync(file, args, execOptions) {
20
24
  return new Promise((res, rej) => {
21
- (0, child_process_1.exec)(command, { ...execOptions, windowsHide: true }, (err, stdout, stderr) => {
25
+ (0, child_process_1.execFile)(file, args, { ...execOptions, windowsHide: true }, (err, stdout) => {
22
26
  if (err) {
23
27
  return rej(err);
24
28
  }
@@ -29,7 +33,14 @@ function execAsync(command, execOptions) {
29
33
  async function cloneFromUpstream(url, destination, { originName, depth } = {
30
34
  originName: 'origin',
31
35
  }) {
32
- await execAsync(`git clone ${url} ${destination} ${depth ? `--depth ${depth}` : ''} --origin ${originName}`, {
36
+ await execFileAsync('git', [
37
+ 'clone',
38
+ url,
39
+ destination,
40
+ ...(depth ? ['--depth', `${depth}`] : []),
41
+ '--origin',
42
+ originName,
43
+ ], {
33
44
  cwd: (0, path_1.dirname)(destination),
34
45
  maxBuffer: 10 * 1024 * 1024,
35
46
  });
@@ -41,25 +52,25 @@ class GitRepository {
41
52
  this.root = this.getGitRootPath(this.directory);
42
53
  }
43
54
  getGitRootPath(cwd) {
44
- return (0, child_process_1.execSync)('git rev-parse --show-toplevel', {
45
- cwd,
46
- windowsHide: true,
47
- })
48
- .toString()
49
- .trim();
55
+ return getGitRootPath(cwd);
50
56
  }
51
57
  async hasUncommittedChanges() {
52
- const data = await this.execAsync(`git status --porcelain`);
58
+ const data = await this.execGit(['status', '--porcelain']);
53
59
  return data.trim() !== '';
54
60
  }
55
61
  async addFetchRemote(remoteName, branch) {
56
- return await this.execAsync(`git config --add remote.${remoteName}.fetch "+refs/heads/${branch}:refs/remotes/${remoteName}/${branch}"`);
62
+ return await this.execGit([
63
+ 'config',
64
+ '--add',
65
+ `remote.${remoteName}.fetch`,
66
+ `+refs/heads/${branch}:refs/remotes/${remoteName}/${branch}`,
67
+ ]);
57
68
  }
58
69
  async showStat() {
59
- return await this.execAsync(`git show --stat`);
70
+ return await this.execGit(['show', '--stat']);
60
71
  }
61
72
  async listBranches() {
62
- return (await this.execAsync(`git ls-remote --heads --quiet`))
73
+ return (await this.execGit(['ls-remote', '--heads', '--quiet']))
63
74
  .trim()
64
75
  .split('\n')
65
76
  .map((s) => s
@@ -70,45 +81,58 @@ class GitRepository {
70
81
  async getGitFiles(path) {
71
82
  // Use -z to return file names exactly as they are stored in git, separated by NULL (\x00) character.
72
83
  // This avoids problems with special characters in file names.
73
- return (await this.execAsync(`git ls-files -z ${path}`))
84
+ return (await this.execGit(['ls-files', '-z', '--', path]))
74
85
  .trim()
75
86
  .split('\x00')
76
87
  .map((s) => s.trim())
77
88
  .filter(Boolean);
78
89
  }
79
90
  async reset(ref) {
80
- return await this.execAsync(`git reset ${ref} --hard`);
91
+ return await this.execGit(['reset', '--hard', ref]);
81
92
  }
82
93
  async mergeUnrelatedHistories(ref, message) {
83
- return await this.execAsync(`git merge ${ref} -X ours --allow-unrelated-histories -m "${message}"`);
94
+ return await this.execGit([
95
+ 'merge',
96
+ ref,
97
+ '-X',
98
+ 'ours',
99
+ '--allow-unrelated-histories',
100
+ '-m',
101
+ message,
102
+ ]);
84
103
  }
85
104
  async fetch(remote, ref) {
86
- return await this.execAsync(`git fetch ${remote}${ref ? ` ${ref}` : ''}`);
105
+ return await this.execGit(['fetch', remote, ...(ref ? [ref] : [])]);
87
106
  }
88
107
  async checkout(branch, opts) {
89
- return await this.execAsync(`git checkout ${opts.new ? '-b ' : ' '}${branch}${opts.base ? ' ' + opts.base : ''}`);
108
+ return await this.execGit([
109
+ 'checkout',
110
+ ...(opts.new ? ['-b'] : []),
111
+ branch,
112
+ ...(opts.base ? [opts.base] : []),
113
+ ]);
90
114
  }
91
115
  async move(path, destination) {
92
- return await this.execAsync(`git mv ${this.quotePath(path)} ${this.quotePath(destination)}`);
116
+ return await this.execGit(['mv', '--', path, destination]);
93
117
  }
94
118
  async push(ref, remoteName) {
95
- return await this.execAsync(`git push -u -f ${remoteName} ${ref}`);
119
+ return await this.execGit(['push', '-u', '-f', remoteName, ref]);
96
120
  }
97
121
  async commit(message) {
98
- return await this.execAsync(`git commit -am "${message}"`);
122
+ return await this.execGit(['commit', '-am', message]);
99
123
  }
100
124
  async amendCommit() {
101
- return await this.execAsync(`git commit --amend -a --no-edit`);
125
+ return await this.execGit(['commit', '--amend', '-a', '--no-edit']);
102
126
  }
103
127
  async deleteGitRemote(name) {
104
- return await this.execAsync(`git remote rm ${name}`);
128
+ return await this.execGit(['remote', 'rm', name]);
105
129
  }
106
130
  async addGitRemote(name, url) {
107
- return await this.execAsync(`git remote add ${name} ${url}`);
131
+ return await this.execGit(['remote', 'add', name, url]);
108
132
  }
109
133
  async hasFilterRepoInstalled() {
110
134
  try {
111
- await this.execAsync(`git filter-repo --help`);
135
+ await this.execGit(['filter-repo', '--help']);
112
136
  return true;
113
137
  }
114
138
  catch {
@@ -121,35 +145,57 @@ class GitRepository {
121
145
  // NOTE: filter-repo requires POSIX path to work
122
146
  const sourcePosixPath = source.split(path_1.sep).join(path_1.posix.sep);
123
147
  const destinationPosixPath = destination.split(path_1.sep).join(path_1.posix.sep);
124
- await this.execAsync(`git filter-repo -f ${source !== '' ? `--path ${this.quotePath(sourcePosixPath)}` : ''} ${source !== destination
125
- ? `--path-rename ${this.quotePath(sourcePosixPath, true)}:${this.quotePath(destinationPosixPath, true)}`
126
- : ''}`);
148
+ const sourcePath = ensureTrailingSlash(sourcePosixPath);
149
+ const destinationPath = ensureTrailingSlash(destinationPosixPath);
150
+ await this.execGit([
151
+ 'filter-repo',
152
+ '-f',
153
+ ...(source !== '' ? ['--path', sourcePosixPath] : []),
154
+ ...(source !== destination
155
+ ? ['--path-rename', `${sourcePath}:${destinationPath}`]
156
+ : []),
157
+ ]);
127
158
  }
128
159
  async filterBranch(source, destination, branchName) {
129
160
  // We need non-ASCII file names to not be quoted, or else filter-branch will exclude them.
130
- await this.execAsync(`git config core.quotepath false`);
161
+ await this.execGit(['config', 'core.quotepath', 'false']);
131
162
  // NOTE: filter-repo requires POSIX path to work
132
163
  const sourcePosixPath = source.split(path_1.sep).join(path_1.posix.sep);
133
164
  const destinationPosixPath = destination.split(path_1.sep).join(path_1.posix.sep);
134
165
  // First, if the source is not a root project, then only include commits relevant to the subdirectory.
135
166
  if (source !== '') {
136
- const indexFilterCommand = this.quoteArg(`node ${(0, path_1.join)(__dirname, 'git-utils.index-filter.js')}`);
137
- await this.execAsync(`git filter-branch -f --index-filter ${indexFilterCommand} --prune-empty -- ${branchName}`, {
167
+ const indexFilterCommand = `node ${quoteForShell((0, path_1.join)(__dirname, 'git-utils.index-filter.js'))}`;
168
+ await this.execGit([
169
+ 'filter-branch',
170
+ '-f',
171
+ '--index-filter',
172
+ indexFilterCommand,
173
+ '--prune-empty',
174
+ '--',
175
+ branchName,
176
+ ], {
138
177
  NX_IMPORT_SOURCE: sourcePosixPath,
139
178
  NX_IMPORT_DESTINATION: destinationPosixPath,
140
179
  });
141
180
  }
142
181
  // Then, move files to their new location if necessary.
143
182
  if (source === '' || source !== destination) {
144
- const treeFilterCommand = this.quoteArg(`node ${(0, path_1.join)(__dirname, 'git-utils.tree-filter.js')}`);
145
- await this.execAsync(`git filter-branch -f --tree-filter ${treeFilterCommand} -- ${branchName}`, {
183
+ const treeFilterCommand = `node ${quoteForShell((0, path_1.join)(__dirname, 'git-utils.tree-filter.js'))}`;
184
+ await this.execGit([
185
+ 'filter-branch',
186
+ '-f',
187
+ '--tree-filter',
188
+ treeFilterCommand,
189
+ '--',
190
+ branchName,
191
+ ], {
146
192
  NX_IMPORT_SOURCE: sourcePosixPath,
147
193
  NX_IMPORT_DESTINATION: destinationPosixPath,
148
194
  });
149
195
  }
150
196
  }
151
- execAsync(command, env) {
152
- return execAsync(command, {
197
+ execGit(args, env) {
198
+ return execFileAsync('git', args, {
153
199
  cwd: this.root,
154
200
  maxBuffer: 10 * 1024 * 1024,
155
201
  env: {
@@ -158,25 +204,14 @@ class GitRepository {
158
204
  },
159
205
  });
160
206
  }
161
- quotePath(path, ensureTrailingSlash) {
162
- return this.quoteArg(ensureTrailingSlash && path !== '' && !path.endsWith('/')
163
- ? `${path}/`
164
- : path);
165
- }
166
- quoteArg(arg) {
167
- return process.platform === 'win32'
168
- ? // Windows/CMD only understands double-quotes, single-quotes are treated as part of the file name
169
- // Bash and other shells will substitute `$` in file names with a variable value.
170
- `"${arg
171
- // Need to keep two slashes for Windows or else the path will be invalid.
172
- // e.g. 'C:\Users\bob\projects\repo' is invalid, but 'C:\\Users\\bob\\projects\\repo' is valid
173
- .replaceAll('\\', '\\\\')}"`
174
- : // e.g. `git mv "$$file.txt" "libs/a/$$file.txt"` will not work since `$$` is swapped with the PID of the last process.
175
- // Using single-quotes prevents this substitution.
176
- `'${arg}'`;
177
- }
178
207
  }
179
208
  exports.GitRepository = GitRepository;
209
+ function ensureTrailingSlash(path) {
210
+ return path !== '' && !path.endsWith('/') ? `${path}/` : path;
211
+ }
212
+ function quoteForShell(arg) {
213
+ return `'${arg.replaceAll("'", "'\"'\"'")}'`;
214
+ }
180
215
  function parseVcsRemoteUrl(url) {
181
216
  // Remove whitespace and handle common URL formats
182
217
  const cleanUrl = url.trim();
@@ -260,6 +295,65 @@ function getVcsRemoteInfo(directory) {
260
295
  return null;
261
296
  }
262
297
  }
298
+ function getGitRootPath(cwd) {
299
+ return (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], {
300
+ cwd,
301
+ windowsHide: true,
302
+ })
303
+ .toString()
304
+ .trim();
305
+ }
306
+ /**
307
+ * Path of `directory` relative to its git root, posix-separated so it is
308
+ * identical on every OS, and '' when the directory is the git root itself.
309
+ * Null outside a git repository.
310
+ */
311
+ function getGitRootRelativePath(directory) {
312
+ try {
313
+ return (0, path_1.relative)(getGitRootPath(directory), directory)
314
+ .split(path_1.sep)
315
+ .join(path_1.posix.sep);
316
+ }
317
+ catch {
318
+ return null;
319
+ }
320
+ }
321
+ /** A shallow clone's truncated history has no stable root commit. */
322
+ function isShallowRepository(directory) {
323
+ try {
324
+ return ((0, child_process_1.execFileSync)('git', ['rev-parse', '--is-shallow-repository'], {
325
+ encoding: 'utf8',
326
+ stdio: 'pipe',
327
+ cwd: directory,
328
+ windowsHide: true,
329
+ }).trim() === 'true');
330
+ }
331
+ catch {
332
+ return false;
333
+ }
334
+ }
335
+ /**
336
+ * SHA of the repository's first commit. Merged unrelated histories leave
337
+ * several root commits — the sorted-first one is picked so every clone
338
+ * agrees. Null when there are no commits, or outside a git repository.
339
+ */
340
+ function getFirstCommitSha(directory) {
341
+ try {
342
+ const roots = (0, child_process_1.execFileSync)('git', ['rev-list', '--max-parents=0', 'HEAD'], {
343
+ encoding: 'utf8',
344
+ stdio: 'pipe',
345
+ cwd: directory,
346
+ windowsHide: true,
347
+ })
348
+ .trim()
349
+ .split(/\r?\n/)
350
+ .filter(Boolean);
351
+ return roots.sort()[0] ?? null;
352
+ }
353
+ catch {
354
+ return null;
355
+ }
356
+ }
263
357
  function isGitRepository(directory) {
264
358
  try {
265
359
  (0, child_process_1.execSync)('git rev-parse --is-inside-work-tree', {
@@ -88,15 +88,17 @@ function readPnpmConfigList(root) {
88
88
  return null;
89
89
  }
90
90
  }
91
- // Extracts the cooldown keys from pnpm's reported config. pnpm reports the
92
- // kebab-case keys; the exclude list comes back as a JSON array (set in a yaml
93
- // surface) or a comma-joined string (set via .npmrc / env).
91
+ // Extracts the cooldown keys from pnpm's reported config. pnpm 11 reports them
92
+ // camelCase; pnpm 10 reported them kebab-case, so read both forms. The exclude
93
+ // list comes back as a JSON array (set in a yaml surface) or a comma-joined
94
+ // string (set via .npmrc / env).
94
95
  function parseCooldownConfig(config) {
96
+ const read = (camelKey, kebabKey) => config[camelKey] ?? config[kebabKey];
95
97
  return {
96
- windowMinutes: toNumber(config['minimum-release-age']) ?? undefined,
97
- excludes: parseExcludeValue(config['minimum-release-age-exclude']),
98
- strictExplicit: toBoolean(config['minimum-release-age-strict']),
99
- ignoreMissingTimeExplicit: toBoolean(config['minimum-release-age-ignore-missing-time']),
98
+ windowMinutes: toNumber(read('minimumReleaseAge', 'minimum-release-age')) ?? undefined,
99
+ excludes: parseExcludeValue(read('minimumReleaseAgeExclude', 'minimum-release-age-exclude')),
100
+ strictExplicit: toBoolean(read('minimumReleaseAgeStrict', 'minimum-release-age-strict')),
101
+ ignoreMissingTimeExplicit: toBoolean(read('minimumReleaseAgeIgnoreMissingTime', 'minimum-release-age-ignore-missing-time')),
100
102
  };
101
103
  }
102
104
  function parseExcludeValue(value) {
@@ -225,19 +225,23 @@ function preparePackageInstallation(pkg, requiredVersion, packageManager) {
225
225
  // it into the temp dir, so the `-w` here resolves to the temp dir.
226
226
  const pmCommands = (0, package_manager_1.getPackageManagerCommand)(packageManager);
227
227
  const preInstallCommand = pmCommands.preInstall;
228
- // Omit peer dependencies from the temp install. `ensurePackage` puts the
228
+ // Keep peer dependencies out of the temp install. `ensurePackage` puts the
229
229
  // workspace's `node_modules` on `NODE_PATH`, so a loaded package resolves its
230
230
  // peers from the workspace instead of pulling its own (possibly incompatible)
231
231
  // copies into the temp dir.
232
- const omitPeerDependenciesFlag = packageManager === 'npm' || packageManager === 'bun'
233
- ? '--omit=peer'
234
- : packageManager === 'pnpm'
235
- ? '--config.auto-install-peers=false'
236
- : '';
232
+ //
233
+ // npm needs `--legacy-peer-deps` rather than `--omit=peer`: npm marks a package
234
+ // as a peer if anything in the tree peer-depends on it, so `--omit=peer` also
235
+ // prunes packages that are real dependencies. Bun's `--omit=peer` does not.
236
+ const skipPeerDependenciesFlags = {
237
+ npm: '--legacy-peer-deps',
238
+ bun: '--omit=peer',
239
+ pnpm: '--config.auto-install-peers=false',
240
+ };
237
241
  const installCommand = [
238
242
  pmCommands.addDev,
239
243
  `${pkg}@${requiredVersion}`,
240
- omitPeerDependenciesFlag,
244
+ skipPeerDependenciesFlags[packageManager],
241
245
  pmCommands.ignoreScriptsFlag,
242
246
  ]
243
247
  .filter(Boolean)
@@ -121,7 +121,9 @@ export declare function resolvePackageVersionUsingInstallation(packageName: stri
121
121
  export declare function packageRegistryView(pkg: string, version: string, args: string, options?: {
122
122
  forceNpm?: boolean;
123
123
  }): Promise<string>;
124
- export declare function packageRegistryPack(cwd: string, pkg: string, version: string): Promise<{
124
+ export declare function packageRegistryPack(cwd: string, pkg: string, version: string, options?: {
125
+ bypassMinReleaseAge?: boolean;
126
+ }): Promise<{
125
127
  tarballPath: string;
126
128
  }>;
127
129
  /**
@@ -165,8 +165,12 @@ function getPackageManagerCommand(packageManager = detectPackageManager(), root
165
165
  install: 'pnpm install --no-frozen-lockfile', // explicitly disable in case of CI
166
166
  ciInstall: 'pnpm install --frozen-lockfile',
167
167
  updateLockFile: 'pnpm install --lockfile-only',
168
- add: isPnpmWorkspace ? 'pnpm add -w' : 'pnpm add',
169
- addDev: isPnpmWorkspace ? 'pnpm add -Dw' : 'pnpm add -D',
168
+ add: isPnpmWorkspace
169
+ ? 'pnpm add -w --config.frozen-lockfile=false'
170
+ : 'pnpm add --config.frozen-lockfile=false',
171
+ addDev: isPnpmWorkspace
172
+ ? 'pnpm add -Dw --config.frozen-lockfile=false'
173
+ : 'pnpm add -D --config.frozen-lockfile=false',
170
174
  rm: 'pnpm rm',
171
175
  exec: modernPnpm ? 'pnpm exec' : 'pnpx',
172
176
  dlx: modernPnpm ? 'pnpm dlx' : 'pnpx',
@@ -345,6 +349,20 @@ function modifyPnpmWorkspaceYamlToFitNewDirectory(contents) {
345
349
  doc.set('packages', ['.']);
346
350
  // Relative patch paths don't resolve in the temp dir.
347
351
  doc.delete('patchedDependencies');
352
+ // link:/file: overrides (e.g. written by `pnpm link`) point at paths that
353
+ // don't exist in the temp dir, and an override would hijack an exact-version
354
+ // add (`pnpm add pkg@x.y.z` would install the linked dir instead).
355
+ const overrides = doc.toJS()?.overrides;
356
+ if (overrides && typeof overrides === 'object') {
357
+ for (const [name, spec] of Object.entries(overrides)) {
358
+ if (typeof spec === 'string' && /^(link|file):/.test(spec)) {
359
+ doc.deleteIn(['overrides', name]);
360
+ }
361
+ }
362
+ if (Object.keys(doc.toJS()?.overrides ?? {}).length === 0) {
363
+ doc.delete('overrides');
364
+ }
365
+ }
348
366
  return doc.toString();
349
367
  }
350
368
  function copyPackageManagerConfigurationFiles(root, destination) {
@@ -515,7 +533,7 @@ options) {
515
533
  });
516
534
  return stdout.toString().trim();
517
535
  }
518
- async function packageRegistryPack(cwd, pkg, version) {
536
+ async function packageRegistryPack(cwd, pkg, version, options) {
519
537
  /**
520
538
  * Only `npm pack` supports downloading a tarball of a specified remote
521
539
  * package. `yarn` packs the active workspace, `pnpm pack` only packs
@@ -529,7 +547,13 @@ async function packageRegistryPack(cwd, pkg, version) {
529
547
  windowsHide: true,
530
548
  // npm enforces `devEngines.packageManager` even on `pack`; force keeps the
531
549
  // download working in workspaces that pin a non-npm manager (onFail: error).
532
- env: { ...process.env, npm_config_force: 'true' },
550
+ env: {
551
+ ...process.env,
552
+ npm_config_force: 'true',
553
+ ...(options?.bypassMinReleaseAge
554
+ ? { npm_config_min_release_age: '0' }
555
+ : {}),
556
+ },
533
557
  });
534
558
  const tarballPath = stdout.trim();
535
559
  return { tarballPath };
@@ -22,8 +22,18 @@ async function ensurePackageHasProvenance(packageName, packageVersion) {
22
22
  }
23
23
  try {
24
24
  const result = await (0, package_manager_1.packageRegistryView)(packageName, packageVersion, '--json --silent');
25
- const npmViewResult = JSON.parse(result);
26
- const attURL = npmViewResult.dist?.attestations?.url;
25
+ const parsed = JSON.parse(result);
26
+ // `npm view <pkg>@<spec> --json` returns a bare object on npm <= 11 but an
27
+ // array on npm 12 and pnpm, even for a single resolved version. A version
28
+ // range matches several versions and the registry lists all of them
29
+ // (including deprecated ones the installer skips), so we cannot tell which
30
+ // one will actually be installed; refuse rather than verify the wrong
31
+ // artifact.
32
+ if (Array.isArray(parsed) && parsed.length > 1) {
33
+ throw new ProvenanceError(packageName, packageVersion, 'Provenance can only be verified for a single version, but this version resolved to multiple candidates. Specify an exact version.');
34
+ }
35
+ const npmViewResult = Array.isArray(parsed) ? parsed[0] : parsed;
36
+ const attURL = npmViewResult?.dist?.attestations?.url;
27
37
  if (!attURL)
28
38
  throw new ProvenanceError(packageName, packageVersion, 'No attestation URL found');
29
39
  const response = await fetch(attURL);
@@ -0,0 +1,20 @@
1
+ import type { NxJsonConfiguration } from '../config/nx-json';
2
+ /**
3
+ * The workspace's analytics identity: the Nx Cloud id when the workspace has
4
+ * one (most stable — it survives repo moves and renames), else the repo key.
5
+ * Null when neither is available, in which case nothing is reported.
6
+ */
7
+ export declare function generateWorkspaceId(root: string, nxJson: NxJsonConfiguration | null): string | null;
8
+ /**
9
+ * Derive the stable, unsalted key identifying this workspace in the
10
+ * repoTelemetry registry: `sha256(<repo identity> + '#' + <workspace path
11
+ * relative to the git root>)`.
12
+ *
13
+ * The repo identity is the normalized `domain/slug` from the git remote
14
+ * (protocol-independent: ssh, https, and token-authenticated URLs of the
15
+ * same repo produce the same key), falling back to the first-commit SHA
16
+ * when no remote exists. Returns null when no identity is derivable — not
17
+ * a git repository, or a shallow clone without a remote.
18
+ */
19
+ export declare function deriveRepoKey(directory: string): string | null;
20
+ export declare function computeRepoKey(identity: string, relativePath: string): string;
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.generateWorkspaceId = generateWorkspaceId;
4
+ exports.deriveRepoKey = deriveRepoKey;
5
+ exports.computeRepoKey = computeRepoKey;
6
+ const crypto_1 = require("crypto");
7
+ const git_utils_1 = require("./git-utils");
8
+ /**
9
+ * The workspace's analytics identity: the Nx Cloud id when the workspace has
10
+ * one (most stable — it survives repo moves and renames), else the repo key.
11
+ * Null when neither is available, in which case nothing is reported.
12
+ */
13
+ function generateWorkspaceId(root, nxJson) {
14
+ const nxCloudId = nxJson?.nxCloudId ?? nxJson?.nxCloudAccessToken;
15
+ if (nxCloudId) {
16
+ return nxCloudId;
17
+ }
18
+ return deriveRepoKey(root);
19
+ }
20
+ /**
21
+ * Derive the stable, unsalted key identifying this workspace in the
22
+ * repoTelemetry registry: `sha256(<repo identity> + '#' + <workspace path
23
+ * relative to the git root>)`.
24
+ *
25
+ * The repo identity is the normalized `domain/slug` from the git remote
26
+ * (protocol-independent: ssh, https, and token-authenticated URLs of the
27
+ * same repo produce the same key), falling back to the first-commit SHA
28
+ * when no remote exists. Returns null when no identity is derivable — not
29
+ * a git repository, or a shallow clone without a remote.
30
+ */
31
+ function deriveRepoKey(directory) {
32
+ const identity = getRepoIdentity(directory);
33
+ if (!identity) {
34
+ return null;
35
+ }
36
+ return computeRepoKey(identity, (0, git_utils_1.getGitRootRelativePath)(directory) ?? '');
37
+ }
38
+ function computeRepoKey(identity, relativePath) {
39
+ return (0, crypto_1.createHash)('sha256')
40
+ .update(`${identity}#${relativePath}`)
41
+ .digest('hex');
42
+ }
43
+ function getRepoIdentity(directory) {
44
+ const remote = (0, git_utils_1.getVcsRemoteInfo)(directory);
45
+ if (remote) {
46
+ // Hosts route case-insensitively and hold one canonical casing, so a
47
+ // hand-typed remote must key the same as the canonical one the claim
48
+ // flow derives from the host's API.
49
+ return `${remote.domain}/${remote.slug}`.toLowerCase().replace(/\/+$/, '');
50
+ }
51
+ // Without a remote the first commit is the only stable identity, and a
52
+ // shallow clone doesn't have a real one.
53
+ return (0, git_utils_1.isShallowRepository)(directory) ? null : (0, git_utils_1.getFirstCommitSha)(directory);
54
+ }
package/migrations.json CHANGED
@@ -160,7 +160,13 @@
160
160
  "cli": "nx",
161
161
  "version": "22.6.0-rc.0",
162
162
  "description": "Adds .claude/settings.local.json to .gitignore",
163
- "implementation": "./dist/src/migrations/update-17-3-0/update-nxw"
163
+ "implementation": "./dist/src/migrations/update-22-6-0/add-claude-settings-local-to-git-ignore"
164
+ },
165
+ "22-6-0-enable-analytics-prompt": {
166
+ "cli": "nx",
167
+ "version": "22.6.0-beta.11",
168
+ "description": "Prompts to enable usage analytics",
169
+ "implementation": "./dist/src/migrations/update-22-6-0/enable-analytics-prompt"
164
170
  },
165
171
  "22-7-0-add-self-healing-to-gitignore": {
166
172
  "cli": "nx",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx",
3
- "version": "23.1.0",
3
+ "version": "23.1.1",
4
4
  "private": false,
5
5
  "type": "commonjs",
6
6
  "description": "The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.",
@@ -61,12 +61,13 @@
61
61
  "ansi-styles": "4.3.0",
62
62
  "argparse": "2.0.1",
63
63
  "asynckit": "0.4.0",
64
- "axios": "1.16.1",
64
+ "axios": "1.18.1",
65
65
  "balanced-match": "4.0.3",
66
66
  "base64-js": "1.5.1",
67
67
  "bl": "4.1.0",
68
- "brace-expansion": "5.0.6",
68
+ "brace-expansion": "5.0.8",
69
69
  "buffer": "5.7.1",
70
+ "bundle-name": "4.1.0",
70
71
  "call-bind-apply-helpers": "1.0.2",
71
72
  "chalk": "4.1.2",
72
73
  "cli-cursor": "3.1.0",
@@ -77,8 +78,10 @@
77
78
  "color-name": "1.1.4",
78
79
  "combined-stream": "1.0.8",
79
80
  "debug": "4.4.3",
81
+ "default-browser": "5.2.1",
82
+ "default-browser-id": "5.0.0",
80
83
  "defaults": "1.0.4",
81
- "define-lazy-prop": "2.0.0",
84
+ "define-lazy-prop": "3.0.0",
82
85
  "delayed-stream": "1.0.0",
83
86
  "dotenv": "16.4.7",
84
87
  "dotenv-expand": "12.0.3",
@@ -111,11 +114,12 @@
111
114
  "ieee754": "1.2.1",
112
115
  "ignore": "7.0.5",
113
116
  "inherits": "2.0.4",
114
- "is-docker": "2.2.1",
117
+ "is-docker": "3.0.0",
115
118
  "is-fullwidth-code-point": "3.0.0",
119
+ "is-inside-container": "1.0.0",
116
120
  "is-interactive": "1.0.0",
117
121
  "is-unicode-supported": "0.1.0",
118
- "is-wsl": "2.2.0",
122
+ "is-wsl": "3.1.0",
119
123
  "isexe": "2.0.0",
120
124
  "json5": "2.2.3",
121
125
  "jsonc-parser": "3.3.1",
@@ -131,7 +135,7 @@
131
135
  "npm-run-path": "4.0.1",
132
136
  "once": "1.4.0",
133
137
  "onetime": "5.1.2",
134
- "open": "8.4.2",
138
+ "open": "10.1.0",
135
139
  "ora": "5.4.1",
136
140
  "path-key": "3.1.1",
137
141
  "picocolors": "1.1.1",
@@ -140,6 +144,7 @@
140
144
  "require-directory": "2.1.1",
141
145
  "resolve.exports": "2.0.3",
142
146
  "restore-cursor": "3.1.0",
147
+ "run-applescript": "7.0.0",
143
148
  "safe-buffer": "5.2.1",
144
149
  "semver": "7.8.4",
145
150
  "signal-exit": "3.0.7",
@@ -176,16 +181,16 @@
176
181
  }
177
182
  },
178
183
  "optionalDependencies": {
179
- "@nx/nx-darwin-arm64": "23.1.0",
180
- "@nx/nx-darwin-x64": "23.1.0",
181
- "@nx/nx-freebsd-x64": "23.1.0",
182
- "@nx/nx-linux-arm-gnueabihf": "23.1.0",
183
- "@nx/nx-linux-arm64-gnu": "23.1.0",
184
- "@nx/nx-linux-arm64-musl": "23.1.0",
185
- "@nx/nx-linux-x64-gnu": "23.1.0",
186
- "@nx/nx-linux-x64-musl": "23.1.0",
187
- "@nx/nx-win32-arm64-msvc": "23.1.0",
188
- "@nx/nx-win32-x64-msvc": "23.1.0"
184
+ "@nx/nx-darwin-arm64": "23.1.1",
185
+ "@nx/nx-darwin-x64": "23.1.1",
186
+ "@nx/nx-freebsd-x64": "23.1.1",
187
+ "@nx/nx-linux-arm-gnueabihf": "23.1.1",
188
+ "@nx/nx-linux-arm64-gnu": "23.1.1",
189
+ "@nx/nx-linux-arm64-musl": "23.1.1",
190
+ "@nx/nx-linux-x64-gnu": "23.1.1",
191
+ "@nx/nx-linux-x64-musl": "23.1.1",
192
+ "@nx/nx-win32-arm64-msvc": "23.1.1",
193
+ "@nx/nx-win32-x64-msvc": "23.1.1"
189
194
  },
190
195
  "nx-migrations": {
191
196
  "migrations": "./migrations.json",
@@ -1,3 +0,0 @@
1
- import { Tree } from '../../generators/tree';
2
- export default function nxReleasePath(tree: Tree): void;
3
- export declare function visitNotIgnoredFiles(tree: Tree, dirPath: string, visitor: (path: string) => void): void;