nx 23.1.0 → 23.2.0-beta.0

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 (60) hide show
  1. package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
  2. package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
  3. package/dist/src/command-line/add/add.js +8 -1
  4. package/dist/src/command-line/import/import.js +1 -1
  5. package/dist/src/command-line/init/configure-plugins.js +3 -2
  6. package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
  7. package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
  8. package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
  9. package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
  10. package/dist/src/command-line/init/implementation/angular/index.js +4 -2
  11. package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
  12. package/dist/src/command-line/init/implementation/utils.js +22 -3
  13. package/dist/src/command-line/migrate/migrate.d.ts +13 -0
  14. package/dist/src/command-line/migrate/migrate.js +45 -0
  15. package/dist/src/command-line/release/utils/git.js +6 -2
  16. package/dist/src/command-line/show/show-target/info.js +11 -2
  17. package/dist/src/daemon/client/client.d.ts +0 -2
  18. package/dist/src/daemon/client/client.js +17 -19
  19. package/dist/src/devkit-internals.d.ts +2 -0
  20. package/dist/src/devkit-internals.js +7 -1
  21. package/dist/src/native/index.d.ts +1 -1
  22. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  23. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  24. package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
  25. package/dist/src/plugins/js/lock-file/pnpm-parser.js +46 -15
  26. package/dist/src/plugins/js/lock-file/project-graph-pruning.js +1 -1
  27. package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
  28. package/dist/src/plugins/js/utils/register.d.ts +18 -0
  29. package/dist/src/plugins/js/utils/register.js +81 -0
  30. package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
  31. package/dist/src/plugins/js/utils/typescript.js +15 -0
  32. package/dist/src/project-graph/error-types.js +41 -1
  33. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +4 -0
  34. package/dist/src/project-graph/plugins/transpiler.js +4 -0
  35. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +9 -12
  36. package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +77 -61
  37. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +1 -1
  38. package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +1 -1
  39. package/dist/src/project-graph/utils/project-configuration-utils.js +4 -6
  40. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +2 -0
  41. package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
  42. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +2 -0
  43. package/dist/src/tasks-runner/life-cycles/performance-report.js +11 -2
  44. package/dist/src/tasks-runner/run-command.js +5 -3
  45. package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
  46. package/dist/src/utils/acknowledge-build-scripts.js +100 -0
  47. package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
  48. package/dist/src/utils/catalog/manager-utils.js +22 -5
  49. package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
  50. package/dist/src/utils/catalog/pnpm-manager.js +3 -15
  51. package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
  52. package/dist/src/utils/catalog/yarn-manager.js +3 -15
  53. package/dist/src/utils/git-utils.d.ts +2 -3
  54. package/dist/src/utils/git-utils.js +101 -47
  55. package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
  56. package/dist/src/utils/provenance.js +12 -2
  57. package/migrations.json +7 -1
  58. package/package.json +11 -11
  59. package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
  60. package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
@@ -1,11 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.readCatalogConfigFromFs = readCatalogConfigFromFs;
4
- exports.readCatalogConfigFromTree = readCatalogConfigFromTree;
3
+ exports.readCatalogDefinitions = readCatalogDefinitions;
5
4
  exports.updateCatalogVersionsInFile = updateCatalogVersionsInFile;
6
- // Keep in sync with packages/devkit/src/utils/catalog/manager-utils.ts; the body
7
- // below the imports is duplicated because @nx/devkit supports a range of nx majors
8
- // and this logic isn't part of the nx surface it can import across that range.
9
5
  const js_yaml_1 = require("@zkochan/js-yaml");
10
6
  const node_fs_1 = require("node:fs");
11
7
  const node_path_1 = require("node:path");
@@ -119,6 +115,27 @@ function readCatalogConfigFromTree(filename, tree) {
119
115
  return null;
120
116
  }
121
117
  }
118
+ // Managers are created per operation (getCatalogManager news one up), so the
119
+ // fs (string-root) branch is cached to read the file once per pass instead of
120
+ // once per catalog reference. The Tree branch stays live since the tree is
121
+ // mutable within a generator.
122
+ function readCatalogDefinitions(filename, treeOrRoot, cache) {
123
+ if (typeof treeOrRoot === 'string') {
124
+ if (cache.has(treeOrRoot)) {
125
+ return cache.get(treeOrRoot);
126
+ }
127
+ const configPath = (0, node_path_1.join)(treeOrRoot, filename);
128
+ const defs = (0, node_fs_1.existsSync)(configPath)
129
+ ? readCatalogConfigFromFs(filename, configPath)
130
+ : null;
131
+ cache.set(treeOrRoot, defs);
132
+ return defs;
133
+ }
134
+ if (!treeOrRoot.exists(filename)) {
135
+ return null;
136
+ }
137
+ return readCatalogConfigFromTree(filename, treeOrRoot);
138
+ }
122
139
  function updateCatalogVersionsInFile(filename, treeOrRoot, updates) {
123
140
  let checkExists;
124
141
  let readYaml;
@@ -7,6 +7,7 @@ import type { CatalogDefinitions, CatalogReference } from './types';
7
7
  export declare class PnpmCatalogManager implements CatalogManager {
8
8
  readonly name = "pnpm";
9
9
  readonly catalogProtocol = "catalog:";
10
+ private definitionsByRoot;
10
11
  isCatalogReference(version: string): boolean;
11
12
  parseCatalogReference(version: string): CatalogReference | null;
12
13
  getCatalogDefinitionFilePaths(): string[];
@@ -1,8 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.PnpmCatalogManager = void 0;
4
- const node_fs_1 = require("node:fs");
5
- const node_path_1 = require("node:path");
6
4
  const manager_1 = require("./manager");
7
5
  const manager_utils_1 = require("./manager-utils");
8
6
  const PNPM_WORKSPACE_FILENAME = 'pnpm-workspace.yaml';
@@ -13,6 +11,8 @@ class PnpmCatalogManager {
13
11
  constructor() {
14
12
  this.name = 'pnpm';
15
13
  this.catalogProtocol = 'catalog:';
14
+ // Parsed fs-root definitions, cached per pass. See readCatalogDefinitions.
15
+ this.definitionsByRoot = new Map();
16
16
  }
17
17
  isCatalogReference(version) {
18
18
  return version.startsWith(this.catalogProtocol);
@@ -33,19 +33,7 @@ class PnpmCatalogManager {
33
33
  return [PNPM_WORKSPACE_FILENAME];
34
34
  }
35
35
  getCatalogDefinitions(treeOrRoot) {
36
- if (typeof treeOrRoot === 'string') {
37
- const configPath = (0, node_path_1.join)(treeOrRoot, PNPM_WORKSPACE_FILENAME);
38
- if (!(0, node_fs_1.existsSync)(configPath)) {
39
- return null;
40
- }
41
- return (0, manager_utils_1.readCatalogConfigFromFs)(PNPM_WORKSPACE_FILENAME, configPath);
42
- }
43
- else {
44
- if (!treeOrRoot.exists(PNPM_WORKSPACE_FILENAME)) {
45
- return null;
46
- }
47
- return (0, manager_utils_1.readCatalogConfigFromTree)(PNPM_WORKSPACE_FILENAME, treeOrRoot);
48
- }
36
+ return (0, manager_utils_1.readCatalogDefinitions)(PNPM_WORKSPACE_FILENAME, treeOrRoot, this.definitionsByRoot);
49
37
  }
50
38
  resolveCatalogReference(treeOrRoot, packageName, version) {
51
39
  const catalogRef = this.parseCatalogReference(version);
@@ -7,6 +7,7 @@ import type { CatalogDefinitions, CatalogReference } from './types';
7
7
  export declare class YarnCatalogManager implements CatalogManager {
8
8
  readonly name = "yarn";
9
9
  readonly catalogProtocol = "catalog:";
10
+ private definitionsByRoot;
10
11
  isCatalogReference(version: string): boolean;
11
12
  parseCatalogReference(version: string): CatalogReference | null;
12
13
  getCatalogDefinitionFilePaths(): string[];
@@ -1,8 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.YarnCatalogManager = void 0;
4
- const node_fs_1 = require("node:fs");
5
- const node_path_1 = require("node:path");
6
4
  const manager_1 = require("./manager");
7
5
  const manager_utils_1 = require("./manager-utils");
8
6
  const YARNRC_FILENAME = '.yarnrc.yml';
@@ -13,6 +11,8 @@ class YarnCatalogManager {
13
11
  constructor() {
14
12
  this.name = 'yarn';
15
13
  this.catalogProtocol = 'catalog:';
14
+ // Parsed fs-root definitions, cached per pass. See readCatalogDefinitions.
15
+ this.definitionsByRoot = new Map();
16
16
  }
17
17
  isCatalogReference(version) {
18
18
  return version.startsWith(this.catalogProtocol);
@@ -33,19 +33,7 @@ class YarnCatalogManager {
33
33
  return [YARNRC_FILENAME];
34
34
  }
35
35
  getCatalogDefinitions(treeOrRoot) {
36
- if (typeof treeOrRoot === 'string') {
37
- const configPath = (0, node_path_1.join)(treeOrRoot, YARNRC_FILENAME);
38
- if (!(0, node_fs_1.existsSync)(configPath)) {
39
- return null;
40
- }
41
- return (0, manager_utils_1.readCatalogConfigFromFs)(YARNRC_FILENAME, configPath);
42
- }
43
- else {
44
- if (!treeOrRoot.exists(YARNRC_FILENAME)) {
45
- return null;
46
- }
47
- return (0, manager_utils_1.readCatalogConfigFromTree)(YARNRC_FILENAME, treeOrRoot);
48
- }
36
+ return (0, manager_utils_1.readCatalogDefinitions)(YARNRC_FILENAME, treeOrRoot, this.definitionsByRoot);
49
37
  }
50
38
  resolveCatalogReference(treeOrRoot, packageName, version) {
51
39
  const catalogRef = this.parseCatalogReference(version);
@@ -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) {
@@ -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.0",
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.0",
180
+ "@nx/nx-darwin-x64": "23.2.0-beta.0",
181
+ "@nx/nx-freebsd-x64": "23.2.0-beta.0",
182
+ "@nx/nx-linux-arm-gnueabihf": "23.2.0-beta.0",
183
+ "@nx/nx-linux-arm64-gnu": "23.2.0-beta.0",
184
+ "@nx/nx-linux-arm64-musl": "23.2.0-beta.0",
185
+ "@nx/nx-linux-x64-gnu": "23.2.0-beta.0",
186
+ "@nx/nx-linux-x64-musl": "23.2.0-beta.0",
187
+ "@nx/nx-win32-arm64-msvc": "23.2.0-beta.0",
188
+ "@nx/nx-win32-x64-msvc": "23.2.0-beta.0"
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
- }