nx 23.2.0-beta.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 (27) hide show
  1. package/dist/bin/nx.js +1 -1
  2. package/dist/src/command-line/init/ai-agent-prompts.d.ts +1 -1
  3. package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
  4. package/dist/src/command-line/init/command-object.js +10 -2
  5. package/dist/src/command-line/migrate/execute-migration.d.ts +72 -0
  6. package/dist/src/command-line/migrate/execute-migration.js +389 -0
  7. package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
  8. package/dist/src/command-line/migrate/migrate.d.ts +1 -45
  9. package/dist/src/command-line/migrate/migrate.js +13 -375
  10. package/dist/src/command-line/release/utils/release-graph.d.ts +2 -2
  11. package/dist/src/command-line/release/utils/shared.d.ts +2 -2
  12. package/dist/src/commands-runner/create-command-graph.js +5 -0
  13. package/dist/src/config/nx-json.d.ts +1 -1
  14. package/dist/src/core/graph/main.js +1 -1
  15. package/dist/src/generators/utils/nx-json.js +13 -3
  16. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  17. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  18. package/dist/src/project-graph/file-utils.js +6 -1
  19. package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +9 -4
  20. package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
  21. package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +16 -8
  22. package/dist/src/tasks-runner/life-cycles/performance-report.js +55 -27
  23. package/dist/src/utils/command-line-utils.js +30 -20
  24. package/dist/src/utils/git-revision.d.ts +12 -0
  25. package/dist/src/utils/git-revision.js +25 -0
  26. package/dist/src/utils/package-manager.js +6 -2
  27. package/package.json +11 -11
@@ -42,9 +42,19 @@ function updateNxJson(tree, nxJson) {
42
42
  }
43
43
  function readNxJsonExtends(tree, extendsPath) {
44
44
  try {
45
- return (0, json_1.readJson)(tree, (0, path_1.relative)(tree.root, require.resolve(extendsPath, {
46
- paths: [tree.root],
47
- })));
45
+ let resolvedExtendsPath;
46
+ try {
47
+ resolvedExtendsPath = require.resolve(extendsPath, {
48
+ paths: [tree.root],
49
+ });
50
+ }
51
+ catch {
52
+ // Tree roots without a node_modules folder (e.g. the in-memory trees
53
+ // used in tests) can't anchor module resolution; fall back to
54
+ // resolving from the running nx package.
55
+ resolvedExtendsPath = require.resolve(extendsPath);
56
+ }
57
+ return (0, json_1.readJson)(tree, (0, path_1.relative)(tree.root, resolvedExtendsPath));
48
58
  }
49
59
  catch (e) {
50
60
  throw new Error(`Unable to resolve nx.json extends. Error: ${e.message}`);
Binary file
@@ -12,6 +12,7 @@ const fs_1 = require("fs");
12
12
  const os_1 = require("os");
13
13
  const path_1 = require("path");
14
14
  const fileutils_1 = require("../utils/fileutils");
15
+ const git_revision_1 = require("../utils/git-revision");
15
16
  const ignore_1 = require("../utils/ignore");
16
17
  const json_diff_1 = require("../utils/json-diff");
17
18
  const workspace_root_1 = require("../utils/workspace-root");
@@ -116,11 +117,14 @@ function readLockFileAtRevision(file, lockFileName, revision, readFileAtRevision
116
117
  }
117
118
  exports.TEN_MEGABYTES = 1024 * 10000;
118
119
  function defaultReadFileAtRevision(file, revision) {
120
+ if (revision) {
121
+ (0, git_revision_1.assertValidGitRevision)(revision);
122
+ }
119
123
  try {
120
124
  const filePathInGitRepository = getFilePathInGitRepository(file);
121
125
  return !revision
122
126
  ? (0, fs_1.readFileSync)(file, 'utf-8')
123
- : (0, child_process_1.execSync)(`git show ${revision}:${filePathInGitRepository}`, {
127
+ : (0, child_process_1.execFileSync)('git', ['show', `${revision}:${filePathInGitRepository}`], {
124
128
  maxBuffer: exports.TEN_MEGABYTES,
125
129
  stdio: ['pipe', 'pipe', 'ignore'],
126
130
  windowsHide: true,
@@ -140,6 +144,7 @@ function defaultReadBunLockFileAtRevision(file, revision) {
140
144
  windowsHide: true,
141
145
  }).trim();
142
146
  }
147
+ (0, git_revision_1.assertValidGitRevision)(revision);
143
148
  const filePathInGitRepository = getFilePathInGitRepository(file);
144
149
  const tempDirectory = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'nx-bun-lock-'));
145
150
  const tempLockfilePath = (0, path_1.join)(tempDirectory, 'bun.lockb');
@@ -29,15 +29,20 @@ export interface Timespan {
29
29
  /** `parallelism: false` tasks running during the slice (each one holds every slot). */
30
30
  nonParallel: number;
31
31
  }
32
+ /**
33
+ * A task and how long it ran (ms). The unit the report's task lists are built from —
34
+ * shared with the renderers so the producer and the formatters can't drift.
35
+ */
36
+ export interface TaskDurationRow {
37
+ id: string;
38
+ duration: number;
39
+ }
32
40
  export interface PerformanceSummary {
33
41
  runDuration: number;
34
42
  criticalPathDuration: number;
35
43
  criticalPathTaskCount: number;
36
44
  /** Longest critical-path tasks that ran (desc, capped at a few), cache hits excluded; empty when the path was fully cached. */
37
- criticalPathTop: Array<{
38
- id: string;
39
- duration: number;
40
- }>;
45
+ criticalPathTop: TaskDurationRow[];
41
46
  /** Ids of tasks that failed (slowest first), for the GitHub Actions summary's failed-tasks list. Continuous tasks and tasks without a complete window are excluded. */
42
47
  failedTasks: string[];
43
48
  /** runDuration − criticalPathDuration. */
@@ -142,8 +142,9 @@ function flushPerformanceReport() {
142
142
  // restore_terminal cooks the terminal back post-TUI, so console.log's plain \n
143
143
  // renders fine; it also supplies the single trailing newline formatReport omits.
144
144
  console.log((0, performance_report_1.formatReport)(summary));
145
- // In GitHub Actions, also append the report (plus a per-task table) to the job
146
- // summary page. Independent of the console.log above so neither masks the other.
145
+ // In GitHub Actions, also append the report to the job summary page the same stats
146
+ // as above, led by the run's outcome (a failed-tasks list, or a success line).
147
+ // Independent of the console.log above so neither masks the other.
147
148
  writePerformanceReportToGitHubActions(summary);
148
149
  }
149
150
  catch (e) {
@@ -157,8 +158,8 @@ function flushPerformanceReport() {
157
158
  /**
158
159
  * Append the performance report to the GitHub Actions job summary page when running in
159
160
  * Actions (`$GITHUB_STEP_SUMMARY` is set there and nowhere else). No-op otherwise. The
160
- * per-task table is computed lazily so non-CI runs don't pay for it. Best-effort: a
161
- * write failure must never affect the run.
161
+ * Markdown is rendered below the guard, so non-CI runs don't pay to format a report
162
+ * nothing reads. Best-effort: a write failure must never affect the run.
162
163
  *
163
164
  * Skipped for a nested run (one nx command invoked by another nx task's command), so only
164
165
  * the outermost run writes to the summary. Nx sets `NX_TASK_TARGET_PROJECT` on every task's
@@ -1,12 +1,16 @@
1
1
  import type { PerformanceSummaryPayload } from '../../native';
2
- import type { PerformanceSummary } from './performance-analysis';
2
+ import type { PerformanceSummary, TaskDurationRow } from './performance-analysis';
3
3
  /**
4
- * A recommendation built from structured parts so the link text comes from the
5
- * link definition (not a substring scanned out of the assembled report). A part
6
- * is either literal text or a {@link RecLink}; the renderers below project the
7
- * same parts to the terminal string, the payload string, and the popup links.
4
+ * A recommendation built from structured parts so the link text comes from the link
5
+ * definition (not a substring scanned out of the assembled report). A part is literal
6
+ * text, a {@link RecLink}, or a {@link RecTaskRows}, projected to each output string by
7
+ * {@link renderRecommendation}.
8
+ *
9
+ * String parts must be single-line — multi-line content needs its own structured part (as
10
+ * {@link RecTaskRows} is). TS can't enforce this: `string` is already a handled member, so
11
+ * a multi-line one compiles and then breaks the Markdown nested list.
8
12
  */
9
- type RecPart = string | RecLink;
13
+ type RecPart = string | RecLink | RecTaskRows;
10
14
  export type Recommendation = RecPart[];
11
15
  /**
12
16
  * A docs link inside a recommendation: a sentence is the visible text and the whole of
@@ -15,11 +19,15 @@ export type Recommendation = RecPart[];
15
19
  * re-links the phrase from {@link PerformanceSummaryPayload.links}.
16
20
  */
17
21
  interface RecLink {
18
- /** Visible label: the sentence that links. */
19
22
  visible: string;
20
- /** OSC 8 click target / appended URL: the utm-tagged URL. */
21
23
  href: string;
22
24
  }
25
+ /**
26
+ * The critical path's longest tasks as data, so each renderer formats them natively:
27
+ * the terminal and payload as space-aligned columns, Markdown as a nested list
28
+ * (HTML collapses space runs, so aligned columns don't survive rendering there).
29
+ */
30
+ type RecTaskRows = TaskDurationRow[];
23
31
  /**
24
32
  * The recommendation string the napi payload ships and the Rust popup matches against.
25
33
  * Links are URL-less (the popup re-links them from {@link PerformanceSummaryPayload.links}).
@@ -25,15 +25,49 @@ const NX_DISTRIBUTE_CTA = 'Distribute across machines with Nx Agents';
25
25
  function phraseLink(phrase, taggedUrl) {
26
26
  return { visible: phrase, href: taggedUrl };
27
27
  }
28
+ // Discriminate positively — test for what each part *is*. A `!isRecTaskRows` catch-all
29
+ // would misclassify a future `RecPart` member as a link; TS can't catch that (it never
30
+ // checks a predicate body), so `recommendationLinks`' `.filter(isRecLink)` would ship
31
+ // `{text: undefined, href: undefined}` to the popup.
28
32
  function isRecLink(part) {
29
- return typeof part !== 'string';
33
+ return typeof part !== 'string' && 'href' in part;
34
+ }
35
+ function isRecTaskRows(part) {
36
+ return Array.isArray(part);
37
+ }
38
+ /**
39
+ * Project a recommendation to a string, formatting each non-text part with the caller's
40
+ * renderers. The three output targets (payload, terminal, Markdown) share this one dispatch.
41
+ * After the string and task-rows branches a part is a {@link RecLink}, so `render.link`
42
+ * takes it directly — and a new {@link RecPart} member that is neither would fail to satisfy
43
+ * that `RecLink` parameter, turning "forgot to handle it" into a compile error right here.
44
+ */
45
+ function renderRecommendation(rec, render) {
46
+ return rec
47
+ .map((part) => {
48
+ if (typeof part === 'string') {
49
+ return part;
50
+ }
51
+ if (isRecTaskRows(part)) {
52
+ return render.taskRows(part);
53
+ }
54
+ return render.link(part);
55
+ })
56
+ .join('');
30
57
  }
31
58
  /**
32
59
  * The recommendation string the napi payload ships and the Rust popup matches against.
33
60
  * Links are URL-less (the popup re-links them from {@link PerformanceSummaryPayload.links}).
34
61
  */
35
62
  function recommendationToPayloadString(rec) {
36
- return rec.map((part) => (!isRecLink(part) ? part : part.visible)).join('');
63
+ return renderRecommendation(rec, {
64
+ link: (link) => link.visible,
65
+ taskRows: taskRowsToText,
66
+ });
67
+ }
68
+ /** Task rows as the text block the terminal and payload embed: newline-led, space-aligned columns. */
69
+ function taskRowsToText(tasks) {
70
+ return ['', ...formatTopTaskRows(tasks)].join('\n');
37
71
  }
38
72
  /**
39
73
  * The recommendation as a terminal string. With OSC 8 the phrase becomes a hyperlink
@@ -42,16 +76,12 @@ function recommendationToPayloadString(rec) {
42
76
  * are off.
43
77
  */
44
78
  function recommendationToTerminalString(rec, hyperlinks) {
45
- return rec
46
- .map((part) => {
47
- if (!isRecLink(part)) {
48
- return part;
49
- }
50
- return hyperlinks
51
- ? (0, terminal_link_1.terminalLink)(part.visible, part.href)
52
- : `${part.visible} → ${part.href}`;
53
- })
54
- .join('');
79
+ return renderRecommendation(rec, {
80
+ link: (link) => hyperlinks
81
+ ? (0, terminal_link_1.terminalLink)(link.visible, link.href)
82
+ : `${link.visible} → ${link.href}`,
83
+ taskRows: taskRowsToText,
84
+ });
55
85
  }
56
86
  /** The popup links (phrase + href) for every link in a recommendation list, for OSC 8 re-linking. */
57
87
  function recommendationLinks(recommendations) {
@@ -77,7 +107,8 @@ function recoverableTime(s) {
77
107
  }
78
108
  /** Render the longest critical-path tasks as aligned columns: task (left), duration (right). */
79
109
  function formatTopTaskRows(tasks) {
80
- // The only caller returns early when empty, so `tasks` is non-empty here.
110
+ // Non-empty by construction: the only recommendation carrying task rows requires
111
+ // `criticalPathTop.length > 0` to apply, so no empty array reaches the widths below.
81
112
  const idWidth = Math.max(...tasks.map((t) => t.id.length));
82
113
  const durations = tasks.map((t) => (0, native_1.formatDuration)(t.duration));
83
114
  const durWidth = Math.max(...durations.map((d) => d.length));
@@ -172,10 +203,8 @@ const RECOMMENDATIONS = [
172
203
  // only multi-line rec). Nothing ran (fully cached) → it doesn't apply.
173
204
  isApplicable: (c) => criticalPathBound(c) && c.criticalPathTop.length > 0,
174
205
  build: (c) => [
175
- [
176
- `Speed up or split the longest tasks on the critical path:`,
177
- ...formatTopTaskRows(c.criticalPathTop),
178
- ].join('\n'),
206
+ `Speed up or split the longest tasks on the critical path:`,
207
+ c.criticalPathTop,
179
208
  ],
180
209
  },
181
210
  ];
@@ -261,18 +290,17 @@ function formatReport(s) {
261
290
  }
262
291
  /**
263
292
  * A recommendation as Markdown: every link becomes `[phrase](href)` (no OSC 8, unlike the
264
- * terminal renderer) — the whole sentence reads as prose and is the link text. The
265
- * critical-path rec embeds newline-separated, space-aligned task rows; collapse them to
266
- * `<br>`-joined lines so they render inside the list item.
293
+ * terminal renderer) — the whole sentence reads as prose and is the link text. Task rows
294
+ * become a nested list under the recommendation's bullet (space-aligned columns don't
295
+ * survive HTML's whitespace collapsing).
267
296
  */
268
297
  function recommendationToMarkdownString(rec) {
269
- return rec
270
- .map((part) => !isRecLink(part) ? part : `[${part.visible}](${part.href})`)
271
- .join('')
272
- .split('\n')
273
- .map((line) => line.trim())
274
- .filter(Boolean)
275
- .join('<br>');
298
+ return renderRecommendation(rec, {
299
+ link: (link) => `[${link.visible}](${link.href})`,
300
+ taskRows: (rows) => rows
301
+ .map((t) => `\n - \`${t.id}\` — ${(0, native_1.formatDuration)(t.duration)}`)
302
+ .join(''),
303
+ });
276
304
  }
277
305
  /**
278
306
  * The performance report as GitHub-flavored Markdown for the Actions job summary
@@ -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
+ }
@@ -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',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nx",
3
- "version": "23.2.0-beta.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.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"
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",