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
@@ -11,6 +11,7 @@ const yargs_parser_1 = tslib_1.__importDefault(require("yargs-parser"));
11
11
  const file_utils_1 = require("../project-graph/file-utils");
12
12
  const output_1 = require("./output");
13
13
  const child_process_1 = require("child_process");
14
+ const git_revision_1 = require("./git-revision");
14
15
  const workspace_root_1 = require("./workspace-root");
15
16
  const shared_options_1 = require("../command-line/yargs-utils/shared-options");
16
17
  function createOverrides(__overrides_unparsed__ = []) {
@@ -192,30 +193,27 @@ function parseFiles(options) {
192
193
  }
193
194
  }
194
195
  function getUncommittedFiles() {
195
- return parseGitOutput(`git diff --name-only --no-renames --relative HEAD .`);
196
+ return parseGitOutput([
197
+ 'diff',
198
+ '--name-only',
199
+ '--no-renames',
200
+ '--relative',
201
+ 'HEAD',
202
+ '.',
203
+ ]);
196
204
  }
197
205
  function getUntrackedFiles() {
198
- return parseGitOutput(`git ls-files --others --exclude-standard`);
206
+ return parseGitOutput(['ls-files', '--others', '--exclude-standard']);
199
207
  }
200
208
  function getMergeBase(base, head = 'HEAD') {
209
+ (0, git_revision_1.assertValidGitRevision)(base);
210
+ (0, git_revision_1.assertValidGitRevision)(head);
201
211
  try {
202
- return (0, child_process_1.execSync)(`git merge-base "${base}" "${head}"`, {
203
- maxBuffer: file_utils_1.TEN_MEGABYTES,
204
- cwd: workspace_root_1.workspaceRoot,
205
- stdio: 'pipe',
206
- windowsHide: true,
207
- })
208
- .toString()
209
- .trim();
212
+ return runGit(['merge-base', base, head]).toString().trim();
210
213
  }
211
214
  catch {
212
215
  try {
213
- return (0, child_process_1.execSync)(`git merge-base --fork-point "${base}" "${head}"`, {
214
- maxBuffer: file_utils_1.TEN_MEGABYTES,
215
- cwd: workspace_root_1.workspaceRoot,
216
- stdio: 'pipe',
217
- windowsHide: true,
218
- })
216
+ return runGit(['merge-base', '--fork-point', base, head])
219
217
  .toString()
220
218
  .trim();
221
219
  }
@@ -225,15 +223,27 @@ function getMergeBase(base, head = 'HEAD') {
225
223
  }
226
224
  }
227
225
  function getFilesUsingBaseAndHead(base, head) {
228
- return parseGitOutput(`git diff --name-only --no-renames --relative "${base}" "${head}"`);
226
+ (0, git_revision_1.assertValidGitRevision)(base);
227
+ (0, git_revision_1.assertValidGitRevision)(head);
228
+ return parseGitOutput([
229
+ 'diff',
230
+ '--name-only',
231
+ '--no-renames',
232
+ '--relative',
233
+ base,
234
+ head,
235
+ ]);
229
236
  }
230
- function parseGitOutput(command) {
231
- return (0, child_process_1.execSync)(command, {
237
+ function runGit(args) {
238
+ return (0, child_process_1.execFileSync)('git', args, {
232
239
  maxBuffer: file_utils_1.TEN_MEGABYTES,
233
240
  cwd: workspace_root_1.workspaceRoot,
234
241
  stdio: 'pipe',
235
242
  windowsHide: true,
236
- })
243
+ });
244
+ }
245
+ function parseGitOutput(args) {
246
+ return runGit(args)
237
247
  .toString('utf-8')
238
248
  .split('\n')
239
249
  .map((a) => a.trim())
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Git parses any argument beginning with `-` as an option rather than a
3
+ * revision, so a flag-shaped revision could smuggle options such as
4
+ * `--upload-pack` into the git commands Nx runs.
5
+ */
6
+ export declare function assertValidGitRevision(revision: string): void;
7
+ /**
8
+ * For refs Nx itself recorded from `git rev-parse` and later read back off
9
+ * disk, where anything other than a commit sha means the value was tampered
10
+ * with rather than that the user picked an unusual revision.
11
+ */
12
+ export declare function assertValidGitSha(sha: string): void;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.assertValidGitRevision = assertValidGitRevision;
4
+ exports.assertValidGitSha = assertValidGitSha;
5
+ /**
6
+ * Git parses any argument beginning with `-` as an option rather than a
7
+ * revision, so a flag-shaped revision could smuggle options such as
8
+ * `--upload-pack` into the git commands Nx runs.
9
+ */
10
+ function assertValidGitRevision(revision) {
11
+ if (revision.startsWith('-')) {
12
+ throw new Error(`Invalid git revision: "${revision}". Git revisions cannot start with "-".`);
13
+ }
14
+ }
15
+ const COMMIT_SHA = /^[0-9a-f]{7,40}$/i;
16
+ /**
17
+ * For refs Nx itself recorded from `git rev-parse` and later read back off
18
+ * disk, where anything other than a commit sha means the value was tampered
19
+ * with rather than that the user picked an unusual revision.
20
+ */
21
+ function assertValidGitSha(sha) {
22
+ if (!COMMIT_SHA.test(sha)) {
23
+ throw new Error(`Invalid git commit sha: "${sha}". Expected a hexadecimal commit sha.`);
24
+ }
25
+ }
@@ -28,9 +28,7 @@ export declare class GitRepository {
28
28
  hasFilterRepoInstalled(): Promise<boolean>;
29
29
  filterRepo(source: string, destination: string): Promise<void>;
30
30
  filterBranch(source: string, destination: string, branchName: string): Promise<void>;
31
- private execAsync;
32
- private quotePath;
33
- private quoteArg;
31
+ private execGit;
34
32
  }
35
33
  export interface VcsRemoteInfo {
36
34
  domain: string;
@@ -39,6 +37,7 @@ export interface VcsRemoteInfo {
39
37
  export declare function parseVcsRemoteUrl(url: string): VcsRemoteInfo | null;
40
38
  export declare function getVcsRemoteInfo(directory?: string): VcsRemoteInfo | null;
41
39
  export declare function isGitRepository(directory?: string): boolean;
40
+ export declare function getGitCurrentBranch(directory?: string): string | null;
42
41
  export declare function hasUncommittedChanges(directory?: string): boolean;
43
42
  export declare function getUncommittedChangesSnapshot(directory?: string): string;
44
43
  export declare function commitChanges(commitMessage: string, directory?: string): string | null;
@@ -5,6 +5,7 @@ exports.cloneFromUpstream = cloneFromUpstream;
5
5
  exports.parseVcsRemoteUrl = parseVcsRemoteUrl;
6
6
  exports.getVcsRemoteInfo = getVcsRemoteInfo;
7
7
  exports.isGitRepository = isGitRepository;
8
+ exports.getGitCurrentBranch = getGitCurrentBranch;
8
9
  exports.hasUncommittedChanges = hasUncommittedChanges;
9
10
  exports.getUncommittedChangesSnapshot = getUncommittedChangesSnapshot;
10
11
  exports.commitChanges = commitChanges;
@@ -16,9 +17,9 @@ const crypto = tslib_1.__importStar(require("crypto"));
16
17
  const fs = tslib_1.__importStar(require("fs"));
17
18
  const path_1 = require("path");
18
19
  const logger_1 = require("./logger");
19
- function execAsync(command, execOptions) {
20
+ function execFileAsync(file, args, execOptions) {
20
21
  return new Promise((res, rej) => {
21
- (0, child_process_1.exec)(command, { ...execOptions, windowsHide: true }, (err, stdout, stderr) => {
22
+ (0, child_process_1.execFile)(file, args, { ...execOptions, windowsHide: true }, (err, stdout) => {
22
23
  if (err) {
23
24
  return rej(err);
24
25
  }
@@ -29,7 +30,14 @@ function execAsync(command, execOptions) {
29
30
  async function cloneFromUpstream(url, destination, { originName, depth } = {
30
31
  originName: 'origin',
31
32
  }) {
32
- await execAsync(`git clone ${url} ${destination} ${depth ? `--depth ${depth}` : ''} --origin ${originName}`, {
33
+ await execFileAsync('git', [
34
+ 'clone',
35
+ url,
36
+ destination,
37
+ ...(depth ? ['--depth', `${depth}`] : []),
38
+ '--origin',
39
+ originName,
40
+ ], {
33
41
  cwd: (0, path_1.dirname)(destination),
34
42
  maxBuffer: 10 * 1024 * 1024,
35
43
  });
@@ -41,7 +49,7 @@ class GitRepository {
41
49
  this.root = this.getGitRootPath(this.directory);
42
50
  }
43
51
  getGitRootPath(cwd) {
44
- return (0, child_process_1.execSync)('git rev-parse --show-toplevel', {
52
+ return (0, child_process_1.execFileSync)('git', ['rev-parse', '--show-toplevel'], {
45
53
  cwd,
46
54
  windowsHide: true,
47
55
  })
@@ -49,17 +57,22 @@ class GitRepository {
49
57
  .trim();
50
58
  }
51
59
  async hasUncommittedChanges() {
52
- const data = await this.execAsync(`git status --porcelain`);
60
+ const data = await this.execGit(['status', '--porcelain']);
53
61
  return data.trim() !== '';
54
62
  }
55
63
  async addFetchRemote(remoteName, branch) {
56
- return await this.execAsync(`git config --add remote.${remoteName}.fetch "+refs/heads/${branch}:refs/remotes/${remoteName}/${branch}"`);
64
+ return await this.execGit([
65
+ 'config',
66
+ '--add',
67
+ `remote.${remoteName}.fetch`,
68
+ `+refs/heads/${branch}:refs/remotes/${remoteName}/${branch}`,
69
+ ]);
57
70
  }
58
71
  async showStat() {
59
- return await this.execAsync(`git show --stat`);
72
+ return await this.execGit(['show', '--stat']);
60
73
  }
61
74
  async listBranches() {
62
- return (await this.execAsync(`git ls-remote --heads --quiet`))
75
+ return (await this.execGit(['ls-remote', '--heads', '--quiet']))
63
76
  .trim()
64
77
  .split('\n')
65
78
  .map((s) => s
@@ -70,45 +83,58 @@ class GitRepository {
70
83
  async getGitFiles(path) {
71
84
  // Use -z to return file names exactly as they are stored in git, separated by NULL (\x00) character.
72
85
  // This avoids problems with special characters in file names.
73
- return (await this.execAsync(`git ls-files -z ${path}`))
86
+ return (await this.execGit(['ls-files', '-z', '--', path]))
74
87
  .trim()
75
88
  .split('\x00')
76
89
  .map((s) => s.trim())
77
90
  .filter(Boolean);
78
91
  }
79
92
  async reset(ref) {
80
- return await this.execAsync(`git reset ${ref} --hard`);
93
+ return await this.execGit(['reset', '--hard', ref]);
81
94
  }
82
95
  async mergeUnrelatedHistories(ref, message) {
83
- return await this.execAsync(`git merge ${ref} -X ours --allow-unrelated-histories -m "${message}"`);
96
+ return await this.execGit([
97
+ 'merge',
98
+ ref,
99
+ '-X',
100
+ 'ours',
101
+ '--allow-unrelated-histories',
102
+ '-m',
103
+ message,
104
+ ]);
84
105
  }
85
106
  async fetch(remote, ref) {
86
- return await this.execAsync(`git fetch ${remote}${ref ? ` ${ref}` : ''}`);
107
+ return await this.execGit(['fetch', remote, ...(ref ? [ref] : [])]);
87
108
  }
88
109
  async checkout(branch, opts) {
89
- return await this.execAsync(`git checkout ${opts.new ? '-b ' : ' '}${branch}${opts.base ? ' ' + opts.base : ''}`);
110
+ return await this.execGit([
111
+ 'checkout',
112
+ ...(opts.new ? ['-b'] : []),
113
+ branch,
114
+ ...(opts.base ? [opts.base] : []),
115
+ ]);
90
116
  }
91
117
  async move(path, destination) {
92
- return await this.execAsync(`git mv ${this.quotePath(path)} ${this.quotePath(destination)}`);
118
+ return await this.execGit(['mv', '--', path, destination]);
93
119
  }
94
120
  async push(ref, remoteName) {
95
- return await this.execAsync(`git push -u -f ${remoteName} ${ref}`);
121
+ return await this.execGit(['push', '-u', '-f', remoteName, ref]);
96
122
  }
97
123
  async commit(message) {
98
- return await this.execAsync(`git commit -am "${message}"`);
124
+ return await this.execGit(['commit', '-am', message]);
99
125
  }
100
126
  async amendCommit() {
101
- return await this.execAsync(`git commit --amend -a --no-edit`);
127
+ return await this.execGit(['commit', '--amend', '-a', '--no-edit']);
102
128
  }
103
129
  async deleteGitRemote(name) {
104
- return await this.execAsync(`git remote rm ${name}`);
130
+ return await this.execGit(['remote', 'rm', name]);
105
131
  }
106
132
  async addGitRemote(name, url) {
107
- return await this.execAsync(`git remote add ${name} ${url}`);
133
+ return await this.execGit(['remote', 'add', name, url]);
108
134
  }
109
135
  async hasFilterRepoInstalled() {
110
136
  try {
111
- await this.execAsync(`git filter-repo --help`);
137
+ await this.execGit(['filter-repo', '--help']);
112
138
  return true;
113
139
  }
114
140
  catch {
@@ -121,35 +147,57 @@ class GitRepository {
121
147
  // NOTE: filter-repo requires POSIX path to work
122
148
  const sourcePosixPath = source.split(path_1.sep).join(path_1.posix.sep);
123
149
  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
- : ''}`);
150
+ const sourcePath = ensureTrailingSlash(sourcePosixPath);
151
+ const destinationPath = ensureTrailingSlash(destinationPosixPath);
152
+ await this.execGit([
153
+ 'filter-repo',
154
+ '-f',
155
+ ...(source !== '' ? ['--path', sourcePosixPath] : []),
156
+ ...(source !== destination
157
+ ? ['--path-rename', `${sourcePath}:${destinationPath}`]
158
+ : []),
159
+ ]);
127
160
  }
128
161
  async filterBranch(source, destination, branchName) {
129
162
  // 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`);
163
+ await this.execGit(['config', 'core.quotepath', 'false']);
131
164
  // NOTE: filter-repo requires POSIX path to work
132
165
  const sourcePosixPath = source.split(path_1.sep).join(path_1.posix.sep);
133
166
  const destinationPosixPath = destination.split(path_1.sep).join(path_1.posix.sep);
134
167
  // First, if the source is not a root project, then only include commits relevant to the subdirectory.
135
168
  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}`, {
169
+ const indexFilterCommand = `node ${quoteForShell((0, path_1.join)(__dirname, 'git-utils.index-filter.js'))}`;
170
+ await this.execGit([
171
+ 'filter-branch',
172
+ '-f',
173
+ '--index-filter',
174
+ indexFilterCommand,
175
+ '--prune-empty',
176
+ '--',
177
+ branchName,
178
+ ], {
138
179
  NX_IMPORT_SOURCE: sourcePosixPath,
139
180
  NX_IMPORT_DESTINATION: destinationPosixPath,
140
181
  });
141
182
  }
142
183
  // Then, move files to their new location if necessary.
143
184
  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}`, {
185
+ const treeFilterCommand = `node ${quoteForShell((0, path_1.join)(__dirname, 'git-utils.tree-filter.js'))}`;
186
+ await this.execGit([
187
+ 'filter-branch',
188
+ '-f',
189
+ '--tree-filter',
190
+ treeFilterCommand,
191
+ '--',
192
+ branchName,
193
+ ], {
146
194
  NX_IMPORT_SOURCE: sourcePosixPath,
147
195
  NX_IMPORT_DESTINATION: destinationPosixPath,
148
196
  });
149
197
  }
150
198
  }
151
- execAsync(command, env) {
152
- return execAsync(command, {
199
+ execGit(args, env) {
200
+ return execFileAsync('git', args, {
153
201
  cwd: this.root,
154
202
  maxBuffer: 10 * 1024 * 1024,
155
203
  env: {
@@ -158,25 +206,14 @@ class GitRepository {
158
206
  },
159
207
  });
160
208
  }
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
209
  }
179
210
  exports.GitRepository = GitRepository;
211
+ function ensureTrailingSlash(path) {
212
+ return path !== '' && !path.endsWith('/') ? `${path}/` : path;
213
+ }
214
+ function quoteForShell(arg) {
215
+ return `'${arg.replaceAll("'", "'\"'\"'")}'`;
216
+ }
180
217
  function parseVcsRemoteUrl(url) {
181
218
  // Remove whitespace and handle common URL formats
182
219
  const cleanUrl = url.trim();
@@ -273,6 +310,23 @@ function isGitRepository(directory) {
273
310
  return false;
274
311
  }
275
312
  }
313
+ // Checked-out branch name, or null when there isn't one to act on: a detached
314
+ // HEAD reports the literal "HEAD" (treated as no branch), and any git error
315
+ // (not a repo, no commits yet) also yields null.
316
+ function getGitCurrentBranch(directory) {
317
+ try {
318
+ const branch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
319
+ encoding: 'utf8',
320
+ cwd: directory,
321
+ stdio: ['ignore', 'pipe', 'ignore'],
322
+ windowsHide: true,
323
+ }).trim();
324
+ return branch && branch !== 'HEAD' ? branch : null;
325
+ }
326
+ catch {
327
+ return null;
328
+ }
329
+ }
276
330
  // Sync companion to `GitRepository.hasUncommittedChanges` for callers that
277
331
  // can't drop into the async class (e.g. the migrate orchestrator, which
278
332
  // branches on this before spawning subprocesses synchronously).
@@ -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) {
@@ -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',
@@ -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);
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.2.0-beta.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.",
@@ -176,16 +176,16 @@
176
176
  }
177
177
  },
178
178
  "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"
179
+ "@nx/nx-darwin-arm64": "23.2.0-beta.1",
180
+ "@nx/nx-darwin-x64": "23.2.0-beta.1",
181
+ "@nx/nx-freebsd-x64": "23.2.0-beta.1",
182
+ "@nx/nx-linux-arm-gnueabihf": "23.2.0-beta.1",
183
+ "@nx/nx-linux-arm64-gnu": "23.2.0-beta.1",
184
+ "@nx/nx-linux-arm64-musl": "23.2.0-beta.1",
185
+ "@nx/nx-linux-x64-gnu": "23.2.0-beta.1",
186
+ "@nx/nx-linux-x64-musl": "23.2.0-beta.1",
187
+ "@nx/nx-win32-arm64-msvc": "23.2.0-beta.1",
188
+ "@nx/nx-win32-x64-msvc": "23.2.0-beta.1"
189
189
  },
190
190
  "nx-migrations": {
191
191
  "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;
@@ -1,47 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.default = nxReleasePath;
4
- exports.visitNotIgnoredFiles = visitNotIgnoredFiles;
5
- const node_path_1 = require("node:path");
6
- const ignore_1 = require("../../utils/ignore");
7
- function nxReleasePath(tree) {
8
- visitNotIgnoredFiles(tree, '', (file) => {
9
- const contents = tree.read(file).toString('utf-8');
10
- if (
11
- // the deep import usage should be replaced by the new location
12
- contents.includes('nx/src/command-line/release') ||
13
- // changelog-renderer has moved into nx/release
14
- contents.includes('nx/changelog-renderer')) {
15
- const finalContents = contents
16
- // replace instances of old changelog renderer location
17
- .replace(/nx\/changelog-renderer/g, 'nx/release/changelog-renderer')
18
- // replace instances of deep import for programmatic API (only perform the replacement if an actual import by checking for trailing ' or ")
19
- .replace(/nx\/src\/command-line\/release(['"])/g, 'nx/release$1');
20
- tree.write(file, finalContents);
21
- }
22
- });
23
- }
24
- // Adapted from devkit
25
- function visitNotIgnoredFiles(tree, dirPath = tree.root, visitor) {
26
- const ig = (0, ignore_1.getIgnoreObject)();
27
- dirPath = normalizePathRelativeToRoot(dirPath, tree.root);
28
- if (dirPath !== '' && ig?.ignores(dirPath)) {
29
- return;
30
- }
31
- for (const child of tree.children(dirPath)) {
32
- const fullPath = (0, node_path_1.join)(dirPath, child);
33
- if (ig?.ignores(fullPath)) {
34
- continue;
35
- }
36
- if (tree.isFile(fullPath)) {
37
- visitor(fullPath);
38
- }
39
- else {
40
- visitNotIgnoredFiles(tree, fullPath, visitor);
41
- }
42
- }
43
- }
44
- // Copied from devkit
45
- function normalizePathRelativeToRoot(path, root) {
46
- return (0, node_path_1.relative)(root, (0, node_path_1.join)(root, path)).split(node_path_1.sep).join('/');
47
- }