@nx/dotnet 23.2.0-pr.36527.2252709 → 23.2.0-pr.36838.ef8e258

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.
package/README.md CHANGED
@@ -7,9 +7,9 @@
7
7
 
8
8
  <div style="text-align: center;">
9
9
 
10
- [![License](https://img.shields.io/npm/l/@nx/workspace.svg?style=flat-square)]()
10
+ [![License](https://img.shields.io/npm/l/@nx/workspace.svg?style=flat-square)](<>)
11
11
  [![NPM Version](https://badge.fury.io/js/nx.svg)](https://www.npmjs.com/package/nx)
12
- [![Semantic Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)]()
12
+ [![Semantic Release](https://img.shields.io/badge/%20%20%F0%9F%93%A6%F0%9F%9A%80-semantic--release-e10079.svg?style=flat-square)](<>)
13
13
  [![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)
14
14
  [![Join us on the Official Nx Discord Server](https://img.shields.io/discord/1143497901675401286?label=discord)](https://go.nx.dev/community)
15
15
  [![Nx Sandboxing](https://staging.nx.app/workspaces/62d013ea0852fe0a2df74438/sandbox-badge.svg)](https://nx.dev/docs/features/ci-features/sandboxing)
@@ -1,26 +1,16 @@
1
1
  import { ProjectConfiguration } from '@nx/devkit';
2
+ export declare const ANALYZER_CANCELLED_MESSAGE = "Analyzer run was cancelled";
3
+ /**
4
+ * Cancel any in-flight analyzer process. Safe to call even if nothing is running.
5
+ */
6
+ export declare function cancelPendingAnalysis(): void;
7
+ export declare function getAnalysisTimeoutMs(): number;
2
8
  export interface AnalysisSuccessResult {
3
9
  nodesByFile: Record<string, ProjectConfiguration>;
4
10
  referencesByRoot: Record<string, {
5
11
  refs: string[];
6
12
  sourceConfigFile: string;
7
13
  }>;
8
- /**
9
- * Project roots whose targets were derived from C# sources rather than from
10
- * project files alone — i.e. projects that produced atomized test targets.
11
- *
12
- * Absent (not empty) when nothing atomized, both so that output from an older
13
- * analyzer still parses and so the common case is trivially detectable.
14
- */
15
- atomizedRoots?: string[];
16
- /**
17
- * Workspace-relative paths of sources that fed discovery but sit outside their
18
- * project's directory — linked or shared files.
19
- *
20
- * `atomizedRoots` is hashed as a per-root glob, which by construction cannot
21
- * reach these, so they are hashed alongside it.
22
- */
23
- atomizedExternalSources?: string[];
24
14
  }
25
15
  export interface AnalysisErrorResult {
26
16
  error: Error;
@@ -37,14 +27,6 @@ export interface DotNetAnalyzerOptions {
37
27
  restoreTargetName?: string;
38
28
  publishTargetName?: string;
39
29
  packTargetName?: string;
40
- /**
41
- * Name of the atomized parent target. Undefined disables test splitting
42
- * entirely, which is the default.
43
- */
44
- testCiTargetName?: string;
45
- /** Pre-derived group name; the analyzer does not compute one. */
46
- testCiGroupName?: string;
47
- testCiSplitBy?: 'class' | 'method';
48
30
  }
49
31
  /**
50
32
  * Get project analysis results for the given project files.
@@ -1 +1 @@
1
- {"version":3,"file":"analyzer-client.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyzer-client.ts"],"names":[],"mappings":"AAIA,OAAO,EAGL,oBAAoB,EAErB,MAAM,YAAY,CAAC;AASpB,MAAM,WAAW,qBAAqB;IAEpC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAElD,gBAAgB,EAAE,MAAM,CACtB,MAAM,EACN;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAC7C,CAAC;IACF;;;;;;OAMG;IACH,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;IACzB;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,MAAM,EAAE,CAAC;CACpC;AACD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,KAAK,CAAC;CACd;AACD,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;AAEzE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,aAAa,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CACpC;AAuOD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,cAAc,CAAC,CA0EzB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,cAAc,CASzD;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,GACrB,MAAM,IAAI,mBAAmB,CAE/B;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,GACrB,MAAM,IAAI,qBAAqB,CAEjC"}
1
+ {"version":3,"file":"analyzer-client.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyzer-client.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,oBAAoB,EACrB,MAAM,YAAY,CAAC;AAmBpB,eAAO,MAAM,0BAA0B,+BAA+B,CAAC;AAIvE;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,IAAI,CAK5C;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAS7C;AAED,MAAM,WAAW,qBAAqB;IAEpC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,oBAAoB,CAAC,CAAC;IAElD,gBAAgB,EAAE,MAAM,CACtB,MAAM,EACN;QAAE,IAAI,EAAE,MAAM,EAAE,CAAC;QAAC,gBAAgB,EAAE,MAAM,CAAA;KAAE,CAC7C,CAAC;CACH;AACD,MAAM,WAAW,mBAAmB;IAClC,KAAK,EAAE,KAAK,CAAC;CACd;AACD,MAAM,MAAM,cAAc,GAAG,qBAAqB,GAAG,mBAAmB,CAAC;AAEzE;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AA4MD;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EAAE,EACf,OAAO,CAAC,EAAE,qBAAqB,GAC9B,OAAO,CAAC,cAAc,CAAC,CA+DzB;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,IAAI,cAAc,CASzD;AAED;;GAEG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAEjC;AAED,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,cAAc,GACrB,MAAM,IAAI,mBAAmB,CAE/B;AAED,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,cAAc,GACrB,MAAM,IAAI,qBAAqB,CAEjC"}
@@ -1,15 +1,44 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ANALYZER_CANCELLED_MESSAGE = void 0;
4
+ exports.cancelPendingAnalysis = cancelPendingAnalysis;
5
+ exports.getAnalysisTimeoutMs = getAnalysisTimeoutMs;
3
6
  exports.analyzeProjects = analyzeProjects;
4
7
  exports.readCachedAnalysisResult = readCachedAnalysisResult;
5
8
  exports.clearCache = clearCache;
6
9
  exports.isAnalysisErrorResult = isAnalysisErrorResult;
7
10
  exports.isAnalysisSuccessResult = isAnalysisSuccessResult;
8
- const node_child_process_1 = require("node:child_process");
9
11
  const node_fs_1 = require("node:fs");
10
12
  const node_path_1 = require("node:path");
11
13
  const devkit_1 = require("@nx/devkit");
12
14
  const internal_1 = require("@nx/devkit/internal");
15
+ const DEFAULT_ANALYSIS_TIMEOUT_SECONDS = (0, internal_1.isCI)() ? 600 : 120;
16
+ // setTimeout silently clamps a delay past the 32-bit signed max to 1ms, which
17
+ // would abort immediately — the opposite of what a large value asks for.
18
+ const MAX_TIMEOUT_MS = 2 ** 31 - 1;
19
+ // Thrown when a newer analysis supersedes an in-flight one. Callers swallow it
20
+ // rather than surfacing an internal string to the user.
21
+ exports.ANALYZER_CANCELLED_MESSAGE = 'Analyzer run was cancelled';
22
+ let currentAbortController;
23
+ /**
24
+ * Cancel any in-flight analyzer process. Safe to call even if nothing is running.
25
+ */
26
+ function cancelPendingAnalysis() {
27
+ if (currentAbortController) {
28
+ currentAbortController.abort('cancelled');
29
+ currentAbortController = undefined;
30
+ }
31
+ }
32
+ function getAnalysisTimeoutMs() {
33
+ const envTimeout = process.env.NX_DOTNET_PROJECT_GRAPH_TIMEOUT;
34
+ if (envTimeout) {
35
+ const parsed = Number(envTimeout);
36
+ if (!Number.isNaN(parsed) && parsed > 0) {
37
+ return Math.min(parsed * 1000, MAX_TIMEOUT_MS);
38
+ }
39
+ }
40
+ return DEFAULT_ANALYSIS_TIMEOUT_SECONDS * 1000;
41
+ }
13
42
  let cache = null;
14
43
  /**
15
44
  * Get the path to the msbuild-analyzer executable
@@ -37,64 +66,11 @@ function getAnalyzerPath() {
37
66
  async function calculateProjectFilesHash(files) {
38
67
  return await (0, internal_1.hashWithWorkspaceContext)(devkit_1.workspaceRoot, files);
39
68
  }
40
- /**
41
- * Joins a project root and a glob pattern, the way `{projectRoot}` inputs are
42
- * expanded for ordinary task hashing.
43
- */
44
- function rootGlob(root, pattern) {
45
- const combined = `${root}/${pattern}`;
46
- // Same problem Nx's own {projectRoot} substitution hits in task-hasher.ts:
47
- // a workspace-root project's root is ".", and "./**" matches nothing.
48
- return combined.startsWith('./') ? combined.slice(2) : combined;
49
- }
50
- /**
51
- * Hash the C# sources of every project that produced atomized targets.
52
- *
53
- * Atomized targets are derived from the test classes and methods declared in a
54
- * project's sources, but `calculateProjectFilesHash` only covers project files.
55
- * Without this, adding or removing a test would leave the cached analysis in
56
- * place and the new test would silently never get a target.
57
- *
58
- * Returns null when nothing atomized, which skips the work entirely — the point
59
- * being that a workspace which never opts in pays nothing for this.
60
- *
61
- * All roots go into a single glob group on purpose. The underlying native
62
- * implementation walks the whole file list once per group, so grouping per
63
- * project root would make this O(roots x files) instead of O(files).
64
- */
65
- async function computeSourceHash(result) {
66
- if (!result.atomizedRoots?.length) {
67
- return null;
68
- }
69
- const rootsHash = await (0, internal_1.hashWithWorkspaceContext)(devkit_1.workspaceRoot, result.atomizedRoots.map((root) => rootGlob(root, '**/*.cs')));
70
- // Discovery reads MSBuild's Compile items, which can point outside the
71
- // project. A linked test file would otherwise change the discovered units
72
- // without changing this hash. Hashed by exact path rather than folded into
73
- // the glob group above: these are literal paths, not patterns, and one
74
- // containing a glob metacharacter (*, !, {, ...) would silently change
75
- // which files the group matches.
76
- const externalHashes = (result.atomizedExternalSources ?? []).map((relativePath) => (0, internal_1.hashFile)((0, node_path_1.join)(devkit_1.workspaceRoot, relativePath)) ?? '');
77
- return externalHashes.length === 0
78
- ? rootsHash
79
- : (0, devkit_1.hashArray)([rootsHash, ...externalHashes]);
80
- }
81
- /**
82
- * Whether a cached analysis still reflects what is on disk.
83
- *
84
- * A null `sourceHash` means the analysis did not depend on C# sources, so the
85
- * project-files hash the entry is keyed by is already sufficient.
86
- */
87
- async function isSourceHashCurrent(sourceHash, result) {
88
- if (sourceHash === null) {
89
- return true;
90
- }
91
- return sourceHash === (await computeSourceHash(result));
92
- }
93
69
  /**
94
70
  * Run the msbuild-analyzer and return the results.
95
71
  * Uses stdin for large file lists to avoid ARG_MAX issues.
96
72
  */
97
- function runAnalyzer(files, options) {
73
+ async function runAnalyzer(files, options) {
98
74
  if (files.length === 0) {
99
75
  return { nodesByFile: {}, referencesByRoot: {} };
100
76
  }
@@ -125,44 +101,106 @@ function runAnalyzer(files, options) {
125
101
  // }
126
102
  // }
127
103
  // }
104
+ // Nothing here may contain a double quote: on Windows `safeSpawn` routes a bare binary
105
+ // name through cmd.exe, whose quoting cannot express one, so it refuses the argument.
106
+ // That is why the options JSON goes over stdin instead (see below).
107
+ const args = [analyzerPath, devkit_1.workspaceRoot];
108
+ // Cancel any in-flight analyzer from a previous call, then create a fresh controller.
109
+ cancelPendingAnalysis();
110
+ const controller = new AbortController();
111
+ currentAbortController = controller;
112
+ const signal = controller.signal;
113
+ const timeoutMs = getAnalysisTimeoutMs();
114
+ const timeoutSeconds = timeoutMs / 1000;
115
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
116
+ let stdout = '';
117
+ let stderr = '';
128
118
  try {
129
- let output;
130
- // Prepare CLI arguments
131
- const args = [analyzerPath, devkit_1.workspaceRoot];
132
- // Add plugin options as JSON string if provided
133
- if (options) {
134
- args.push(JSON.stringify(options));
135
- }
136
- // Use stdin mode for large file lists to avoid ARG_MAX issues. The analyzer
137
- // partitions paths by filename, so we just stream everything in one block.
138
- const input = files.join('\n');
139
- const result = (0, node_child_process_1.spawnSync)('dotnet', args, {
140
- input,
141
- encoding: 'utf-8',
142
- maxBuffer: 10 * 1024 * 1024, // 10MB buffer
143
- stdio: ['pipe', 'pipe', 'pipe'], // stdin, stdout, stderr
144
- windowsHide: true,
145
- env,
119
+ await new Promise((resolve, reject) => {
120
+ const child = (0, internal_1.safeSpawn)('dotnet', args, {
121
+ stdio: ['pipe', 'pipe', 'pipe'],
122
+ windowsHide: true,
123
+ env,
124
+ });
125
+ // A plugin worker torn down by `nx reset` would otherwise orphan the analyzer.
126
+ (0, internal_1.killChildOnHostExit)(child);
127
+ // On abort, kill the process tree and settle immediately — a wedged
128
+ // process that outlives the kill signal would otherwise keep this
129
+ // promise pending and the abort error would never surface.
130
+ const onAbort = () => {
131
+ if (child.pid) {
132
+ (0, internal_1.killProcessTreeGraceful)(child.pid).catch(() => { });
133
+ }
134
+ reject(new Error('Analyzer aborted'));
135
+ };
136
+ signal.addEventListener('abort', onAbort, { once: true });
137
+ child.stdout?.setEncoding('utf-8');
138
+ child.stderr?.setEncoding('utf-8');
139
+ child.stdout?.on('data', (data) => {
140
+ stdout += data;
141
+ });
142
+ child.stderr?.on('data', (data) => {
143
+ stderr += data;
144
+ });
145
+ child.on('error', (error) => {
146
+ signal.removeEventListener('abort', onAbort);
147
+ reject(error);
148
+ });
149
+ child.on('close', (code) => {
150
+ signal.removeEventListener('abort', onAbort);
151
+ if (code === 0) {
152
+ resolve();
153
+ }
154
+ else {
155
+ reject(new Error(`Analyzer exited with code ${code}: ${stderr}`));
156
+ }
157
+ });
158
+ // Stream the options JSON, then the file list, over stdin. The first line is
159
+ // always written, empty when there are no options, so the analyzer never has to
160
+ // infer whether one was sent. The analyzer partitions paths by filename, so we
161
+ // just send everything in one block.
162
+ // EPIPE if the analyzer dies early. Surfaced via `close` only when the
163
+ // child also exits non-zero; a stdin error with a clean exit means the
164
+ // analyzer read a truncated file list.
165
+ child.stdin?.on('error', () => { });
166
+ child.stdin?.end([options ? JSON.stringify(options) : '', ...files].join('\n'));
146
167
  });
147
- if (result.error) {
148
- throw result.error;
168
+ }
169
+ catch (error) {
170
+ if (signal.reason === 'cancelled') {
171
+ throw new Error(exports.ANALYZER_CANCELLED_MESSAGE);
149
172
  }
150
- if (result.status !== 0) {
151
- throw new Error(`Analyzer exited with code ${result.status}: ${result.stderr}`);
173
+ if (signal.aborted) {
174
+ throw new Error(`msbuild-analyzer timed out after ${timeoutSeconds} ${timeoutSeconds === 1 ? 'second' : 'seconds'}.\n` +
175
+ ` 1. Set the environment variable NX_DOTNET_PROJECT_GRAPH_TIMEOUT to a higher value (in seconds) to increase the timeout.\n` +
176
+ ` 2. If the issue persists, set NX_DOTNET_DISABLE=true to disable the .NET plugin entirely.`);
152
177
  }
153
- // Output stderr (includes performance logs when NX_PERF_LOGGING=true)
154
- if (result.stderr) {
155
- console.error(result.stderr);
178
+ const err = error;
179
+ const stderrAlreadyInMessage = err.message.includes(stderr);
180
+ if (stderr && !stderrAlreadyInMessage) {
181
+ devkit_1.logger.error(`msbuild-analyzer error: ${stderr}`);
156
182
  }
157
- output = result.stdout;
158
- return JSON.parse(output);
183
+ throw new Error(`Failed to run msbuild-analyzer: ${err.message}${stderr && !stderrAlreadyInMessage ? `\n${stderr}` : ''}`);
159
184
  }
160
- catch (error) {
161
- const err = error;
162
- if (err.stderr) {
163
- devkit_1.logger.error(`msbuild-analyzer error: ${err.stderr}`);
185
+ finally {
186
+ clearTimeout(timer);
187
+ // Drop the settled controller so a later cancelPendingAnalysis() cannot
188
+ // abort a run that already finished.
189
+ if (currentAbortController === controller) {
190
+ currentAbortController = undefined;
164
191
  }
165
- throw new Error(`Failed to run msbuild-analyzer: ${err.message}${err.stderr ? `\n${err.stderr}` : ''}`);
192
+ }
193
+ // Output stderr (includes performance logs when NX_PERF_LOGGING=true)
194
+ if (stderr) {
195
+ console.error(stderr);
196
+ }
197
+ try {
198
+ // parseJson over JSON.parse: on malformed output it reports line:column with
199
+ // a code frame, where JSON.parse gives only a character offset.
200
+ return (0, devkit_1.parseJson)(stdout);
201
+ }
202
+ catch (error) {
203
+ throw new Error(`Failed to parse msbuild-analyzer output: ${error.message}`);
166
204
  }
167
205
  }
168
206
  /**
@@ -172,47 +210,36 @@ function runAnalyzer(files, options) {
172
210
  */
173
211
  async function analyzeProjects(files, options) {
174
212
  const filesHash = await calculateProjectFilesHash(files);
175
- const optionsHash = (0, internal_1.hashObject)(options);
176
213
  // Return cached results if the hash matches
177
214
  if (cache &&
178
215
  cache.hash === filesHash &&
179
- cache.optionsHash === optionsHash &&
180
216
  // NOTE: We don't read from the cache here if it's an error result,
181
217
  // to allow retrying analysis in case of transient errors or errors fixed
182
218
  // that may not be reflected in the hash (like setting an env var).
183
- isAnalysisSuccessResult(cache.result) &&
184
- (await isSourceHashCurrent(cache.sourceHash, cache.result))) {
219
+ isAnalysisSuccessResult(cache.result)) {
185
220
  return cache.result;
186
221
  }
187
- // The `-v2` marks the cache *value* shape, which now wraps the analysis
188
- // alongside the hash of the sources it came from. Entries written by an
189
- // earlier version live under the old name and are simply never read.
190
- const analyzerCache = new internal_1.PluginCache((0, node_path_1.join)(internal_1.workspaceDataDirectory, `dotnet-v2-${optionsHash}.hash`));
191
- const cachedEntry = analyzerCache.get(filesHash);
192
- if (cachedEntry?.result &&
193
- (await isSourceHashCurrent(cachedEntry.sourceHash, cachedEntry.result))) {
222
+ const optionsHash = (0, internal_1.hashObject)(options);
223
+ const analyzerCache = new internal_1.PluginCache((0, node_path_1.join)(internal_1.workspaceDataDirectory, `dotnet-${optionsHash}.hash`));
224
+ const cachedResult = analyzerCache.get(filesHash);
225
+ if (cachedResult) {
194
226
  // Update cache
195
227
  cache = {
196
228
  hash: filesHash,
197
- optionsHash,
198
- result: cachedEntry.result,
199
- sourceHash: cachedEntry.sourceHash,
229
+ result: cachedResult,
200
230
  };
201
- return cachedEntry.result;
231
+ return cachedResult;
202
232
  }
203
233
  // Run the analyzer
204
234
  try {
205
- const result = runAnalyzer(files, options);
206
- const sourceHash = await computeSourceHash(result);
235
+ const result = await runAnalyzer(files, options);
207
236
  // Update local cache
208
237
  cache = {
209
238
  hash: filesHash,
210
- optionsHash,
211
239
  result,
212
- sourceHash,
213
240
  };
214
241
  // Update persistent cache
215
- analyzerCache.set(filesHash, { result, sourceHash });
242
+ analyzerCache.set(filesHash, result);
216
243
  analyzerCache.writeToDisk();
217
244
  return result;
218
245
  }
@@ -226,12 +253,14 @@ async function analyzeProjects(files, options) {
226
253
  const errorResult = {
227
254
  error: err,
228
255
  };
229
- cache = {
230
- hash: filesHash,
231
- optionsHash,
232
- result: errorResult,
233
- sourceHash: null,
234
- };
256
+ // A superseded run is not a result: caching it would hand the sentinel to
257
+ // createDependencies via readCachedAnalysisResult().
258
+ if (err.message !== exports.ANALYZER_CANCELLED_MESSAGE) {
259
+ cache = {
260
+ hash: filesHash,
261
+ result: errorResult,
262
+ };
263
+ }
235
264
  return errorResult;
236
265
  }
237
266
  }
@@ -10,8 +10,7 @@
10
10
  "dependencies": {
11
11
  "Microsoft.Build": "17.0.0",
12
12
  "Microsoft.Build.Framework": "17.0.0",
13
- "Microsoft.Build.Locator": "1.8.1",
14
- "Microsoft.CodeAnalysis.CSharp": "4.11.0"
13
+ "Microsoft.Build.Locator": "1.8.1"
15
14
  },
16
15
  "runtime": {
17
16
  "MsbuildAnalyzer.dll": {}
@@ -22,9 +21,9 @@
22
21
  "Microsoft.Build.Framework": "17.0.0",
23
22
  "Microsoft.NET.StringTools": "1.0.0",
24
23
  "Microsoft.Win32.Registry": "4.3.0",
25
- "System.Collections.Immutable": "8.0.0",
24
+ "System.Collections.Immutable": "5.0.0",
26
25
  "System.Configuration.ConfigurationManager": "4.7.0",
27
- "System.Reflection.Metadata": "8.0.0",
26
+ "System.Reflection.Metadata": "1.6.0",
28
27
  "System.Security.Principal.Windows": "4.7.0",
29
28
  "System.Text.Encoding.CodePages": "4.0.1",
30
29
  "System.Text.Json": "5.0.2",
@@ -44,22 +43,6 @@
44
43
  }
45
44
  }
46
45
  },
47
- "Microsoft.CodeAnalysis.Analyzers/3.3.4": {},
48
- "Microsoft.CodeAnalysis.Common/4.11.0": {
49
- "dependencies": {
50
- "Microsoft.CodeAnalysis.Analyzers": "3.3.4",
51
- "System.Collections.Immutable": "8.0.0",
52
- "System.Reflection.Metadata": "8.0.0"
53
- }
54
- },
55
- "Microsoft.CodeAnalysis.CSharp/4.11.0": {
56
- "dependencies": {
57
- "Microsoft.CodeAnalysis.Analyzers": "3.3.4",
58
- "Microsoft.CodeAnalysis.Common": "4.11.0",
59
- "System.Collections.Immutable": "8.0.0",
60
- "System.Reflection.Metadata": "8.0.0"
61
- }
62
- },
63
46
  "Microsoft.NET.StringTools/1.0.0": {
64
47
  "dependencies": {
65
48
  "System.Memory": "4.5.4",
@@ -92,7 +75,7 @@
92
75
  "System.Runtime": "4.3.0"
93
76
  }
94
77
  },
95
- "System.Collections.Immutable/8.0.0": {},
78
+ "System.Collections.Immutable/5.0.0": {},
96
79
  "System.Configuration.ConfigurationManager/4.7.0": {
97
80
  "dependencies": {
98
81
  "System.Security.Cryptography.ProtectedData": "4.7.0",
@@ -131,11 +114,7 @@
131
114
  "System.Runtime": "4.3.0"
132
115
  }
133
116
  },
134
- "System.Reflection.Metadata/8.0.0": {
135
- "dependencies": {
136
- "System.Collections.Immutable": "8.0.0"
137
- }
138
- },
117
+ "System.Reflection.Metadata/1.6.0": {},
139
118
  "System.Reflection.Primitives/4.3.0": {
140
119
  "dependencies": {
141
120
  "Microsoft.NETCore.Platforms": "3.1.0",
@@ -269,27 +248,6 @@
269
248
  "path": "microsoft.build.locator/1.8.1",
270
249
  "hashPath": "microsoft.build.locator.1.8.1.nupkg.sha512"
271
250
  },
272
- "Microsoft.CodeAnalysis.Analyzers/3.3.4": {
273
- "type": "package",
274
- "serviceable": true,
275
- "sha512": "sha512-AxkxcPR+rheX0SmvpLVIGLhOUXAKG56a64kV9VQZ4y9gR9ZmPXnqZvHJnmwLSwzrEP6junUF11vuc+aqo5r68g==",
276
- "path": "microsoft.codeanalysis.analyzers/3.3.4",
277
- "hashPath": "microsoft.codeanalysis.analyzers.3.3.4.nupkg.sha512"
278
- },
279
- "Microsoft.CodeAnalysis.Common/4.11.0": {
280
- "type": "package",
281
- "serviceable": true,
282
- "sha512": "sha512-djf8ujmqYImFgB04UGtcsEhHrzVqzHowS+EEl/Yunc5LdrYrZhGBWUTXoCF0NzYXJxtfuD+UVQarWpvrNc94Qg==",
283
- "path": "microsoft.codeanalysis.common/4.11.0",
284
- "hashPath": "microsoft.codeanalysis.common.4.11.0.nupkg.sha512"
285
- },
286
- "Microsoft.CodeAnalysis.CSharp/4.11.0": {
287
- "type": "package",
288
- "serviceable": true,
289
- "sha512": "sha512-6XYi2EusI8JT4y2l/F3VVVS+ISoIX9nqHsZRaG6W5aFeJ5BEuBosHfT/ABb73FN0RZ1Z3cj2j7cL28SToJPXOw==",
290
- "path": "microsoft.codeanalysis.csharp/4.11.0",
291
- "hashPath": "microsoft.codeanalysis.csharp.4.11.0.nupkg.sha512"
292
- },
293
251
  "Microsoft.NET.StringTools/1.0.0": {
294
252
  "type": "package",
295
253
  "serviceable": true,
@@ -332,12 +290,12 @@
332
290
  "path": "system.collections/4.3.0",
333
291
  "hashPath": "system.collections.4.3.0.nupkg.sha512"
334
292
  },
335
- "System.Collections.Immutable/8.0.0": {
293
+ "System.Collections.Immutable/5.0.0": {
336
294
  "type": "package",
337
295
  "serviceable": true,
338
- "sha512": "sha512-AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==",
339
- "path": "system.collections.immutable/8.0.0",
340
- "hashPath": "system.collections.immutable.8.0.0.nupkg.sha512"
296
+ "sha512": "sha512-FXkLXiK0sVVewcso0imKQoOxjoPAj42R8HtjjbSjVPAzwDfzoyoznWxgA3c38LDbN9SJux1xXoXYAhz98j7r2g==",
297
+ "path": "system.collections.immutable/5.0.0",
298
+ "hashPath": "system.collections.immutable.5.0.0.nupkg.sha512"
341
299
  },
342
300
  "System.Configuration.ConfigurationManager/4.7.0": {
343
301
  "type": "package",
@@ -381,12 +339,12 @@
381
339
  "path": "system.reflection/4.3.0",
382
340
  "hashPath": "system.reflection.4.3.0.nupkg.sha512"
383
341
  },
384
- "System.Reflection.Metadata/8.0.0": {
342
+ "System.Reflection.Metadata/1.6.0": {
385
343
  "type": "package",
386
344
  "serviceable": true,
387
- "sha512": "sha512-ptvgrFh7PvWI8bcVqG5rsA/weWM09EnthFHR5SCnS6IN+P4mj6rE1lBDC4U8HL9/57htKAqy4KQ3bBj84cfYyQ==",
388
- "path": "system.reflection.metadata/8.0.0",
389
- "hashPath": "system.reflection.metadata.8.0.0.nupkg.sha512"
345
+ "sha512": "sha512-COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==",
346
+ "path": "system.reflection.metadata/1.6.0",
347
+ "hashPath": "system.reflection.metadata.1.6.0.nupkg.sha512"
390
348
  },
391
349
  "System.Reflection.Primitives/4.3.0": {
392
350
  "type": "package",
Binary file
Binary file
@@ -1,50 +1,10 @@
1
- import { CreateNodes, ProjectConfiguration, TargetConfiguration } from '@nx/devkit';
1
+ import { CreateNodes, TargetConfiguration } from '@nx/devkit';
2
2
  export type TargetConfigurationWithName = Partial<TargetConfiguration> & {
3
3
  /**
4
4
  * The name of the target. Defaults to the target type (e.g., 'build', 'test', etc.)
5
5
  */
6
6
  targetName?: string;
7
7
  };
8
- /**
9
- * Configuration for the test target, including the options that split it into
10
- * one task per test unit.
11
- */
12
- export type TestTargetConfiguration = TargetConfigurationWithName & {
13
- /**
14
- * Name of the target that runs the project's tests split across many tasks.
15
- *
16
- * Leave unset (the default) and nothing is split — the project keeps only its
17
- * ordinary `test` target. Setting it turns on atomization for every project
18
- * this plugin registration matches, so to split a single project, register
19
- * `@nx/dotnet` a second time with an `include` pattern scoped to it.
20
- *
21
- * Requires Microsoft.Testing.Platform. Running the group needs Nx Cloud: Nx
22
- * refuses the parent target without it, because expanding into every task on
23
- * one machine is slower than not splitting at all. Individual tasks carry no
24
- * such restriction and run locally.
25
- */
26
- ciTargetName?: string;
27
- /**
28
- * Overrides the name of the target group these tasks are collapsed into in Nx
29
- * Console and the graph. Defaults to a name derived from `ciTargetName`
30
- * (`test-ci` becomes `TEST (CI)`).
31
- */
32
- ciGroupName?: string;
33
- /**
34
- * Whether each task runs one test class or one test method. Defaults to
35
- * `'class'`.
36
- *
37
- * `'method'` is worth opting into when each test method carries its own
38
- * expensive fixture — spinning up a distributed application, a database, a
39
- * browser — because then grouping methods into one task saves no setup work
40
- * and only serializes tests that could have run on separate agents. For
41
- * ordinary suites it multiplies the target count for no gain, since process
42
- * startup dominates a fast test.
43
- *
44
- * `'method'` currently requires MSTest; see the plugin docs.
45
- */
46
- ciSplitBy?: 'class' | 'method';
47
- };
48
8
  /**
49
9
  * Configuration options for the @nx/dotnet plugin.
50
10
  *
@@ -85,11 +45,9 @@ export interface DotNetPluginOptions {
85
45
  build?: TargetConfigurationWithName | false;
86
46
  /**
87
47
  * Configuration for the test target.
88
- * Use `targetName` to rename the target, `ciTargetName` to additionally split
89
- * the tests across one task per test unit, and provide additional
90
- * options/configurations to merge with the generated target.
48
+ * Use `targetName` to rename the target, and provide additional options/configurations to merge with the generated target.
91
49
  */
92
- test?: TestTargetConfiguration | false;
50
+ test?: TargetConfigurationWithName | false;
93
51
  /**
94
52
  * Configuration for the clean target.
95
53
  * Use `targetName` to rename the target, and provide additional options/configurations to merge with the generated target.
@@ -121,10 +79,6 @@ export interface DotNetPluginOptions {
121
79
  */
122
80
  run?: TargetConfigurationWithName | false;
123
81
  }
124
- /**
125
- * Merge user-specified target configurations with the generated targets from the analyzer
126
- */
127
- export declare function mergeUserTargetConfigurations(node: ProjectConfiguration, options: DotNetPluginOptions): ProjectConfiguration;
128
82
  export declare const createNodes: CreateNodes<DotNetPluginOptions>;
129
83
  /**
130
84
  * @deprecated Use {@link createNodes} instead. This will be removed in Nx 24.
@@ -1 +1 @@
1
- {"version":3,"file":"create-nodes.d.ts","sourceRoot":"","sources":["../../src/plugins/create-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAEX,oBAAoB,EACpB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAUpB,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG;IACvE;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;GAGG;AACH,MAAM,MAAM,uBAAuB,GAAG,2BAA2B,GAAG;IAClE;;;;;;;;;;;;OAYG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;;;;;;;;;;OAYG;IACH,SAAS,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;;;OAKG;IACH,IAAI,CAAC,EAAE,uBAAuB,GAAG,KAAK,CAAC;IACvC;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;OAGG;IACH,OAAO,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC9C;;;OAGG;IACH,OAAO,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC9C;;;OAGG;IACH,IAAI,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC3C;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;OAGG;IACH,GAAG,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;CAC3C;AAaD;;GAEG;AACH,wBAAgB,6BAA6B,CAC3C,IAAI,EAAE,oBAAoB,EAC1B,OAAO,EAAE,mBAAmB,GAC3B,oBAAoB,CA4EtB;AAED,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,mBAAmB,CA8FxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,kCAAc,CAAC"}
1
+ {"version":3,"file":"create-nodes.d.ts","sourceRoot":"","sources":["../../src/plugins/create-nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,WAAW,EAGX,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAQpB,MAAM,MAAM,2BAA2B,GAAG,OAAO,CAAC,mBAAmB,CAAC,GAAG;IACvE;;OAEG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;OAGG;IACH,IAAI,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC3C;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;OAGG;IACH,OAAO,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC9C;;;OAGG;IACH,OAAO,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC9C;;;OAGG;IACH,IAAI,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC3C;;;OAGG;IACH,KAAK,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;IAC5C;;;OAGG;IACH,GAAG,CAAC,EAAE,2BAA2B,GAAG,KAAK,CAAC;CAC3C;AAqFD,eAAO,MAAM,WAAW,EAAE,WAAW,CAAC,mBAAmB,CAqFxD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,kCAAc,CAAC"}
@@ -1,7 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.createNodesV2 = exports.createNodes = void 0;
4
- exports.mergeUserTargetConfigurations = mergeUserTargetConfigurations;
5
4
  const devkit_1 = require("@nx/devkit");
6
5
  const analyzer_client_1 = require("../analyzer/analyzer-client");
7
6
  const internal_1 = require("@nx/devkit/internal");
@@ -38,13 +37,8 @@ function mergeUserTargetConfigurations(node, options) {
38
37
  delete mergedTargets[defaultTargetName];
39
38
  continue;
40
39
  }
41
- // Use empty object as default when option is not provided.
42
- //
43
- // The ci* keys configure how the analyzer generates targets rather than
44
- // being target configuration themselves, so they are pulled out here
45
- // alongside targetName — otherwise they would be merged onto the generated
46
- // target as junk properties.
47
- const { targetName, ciTargetName: _ciTargetName, ciGroupName: _ciGroupName, ciSplitBy: _ciSplitBy, ...userSpecifiedConfig } = targetOption ?? {};
40
+ // Use empty object as default when option is not provided
41
+ const { targetName, ...userSpecifiedConfig } = targetOption ?? {};
48
42
  const actualTargetName = targetName ?? defaultTargetName;
49
43
  // Find the generated target - it might be under the default name or the user-specified name
50
44
  const generatedTarget = mergedTargets[actualTargetName] ?? mergedTargets[defaultTargetName];
@@ -69,7 +63,6 @@ function mergeUserTargetConfigurations(node, options) {
69
63
  return {
70
64
  ...node,
71
65
  targets: mergedTargets,
72
- ...(node.metadata ? { metadata: node.metadata } : {}),
73
66
  };
74
67
  }
75
68
  exports.createNodes = [
@@ -79,23 +72,8 @@ exports.createNodes = [
79
72
  try {
80
73
  // Normalize options to handle undefined (when plugin is registered as string)
81
74
  const normalizedOptions = options ?? {};
82
- const testOptions = normalizedOptions.test === false ? undefined : normalizedOptions.test;
83
- // Only meaningful when the test target itself is enabled; splitting a
84
- // target that will be deleted would generate targets referring to it.
85
- const ciTargetName = testOptions?.ciTargetName;
86
75
  // Extract target names from new format and create options for analyzer
87
76
  const analyzerOptions = {
88
- // Derived here rather than in the analyzer so there is one
89
- // implementation of the naming convention shared with every other Nx
90
- // plugin that splits tests.
91
- testCiTargetName: ciTargetName,
92
- testCiGroupName: ciTargetName
93
- ? (testOptions?.ciGroupName ??
94
- (0, internal_1.deriveGroupNameFromTarget)(ciTargetName))
95
- : undefined,
96
- testCiSplitBy: ciTargetName
97
- ? (testOptions?.ciSplitBy ?? 'class')
98
- : undefined,
99
77
  buildTargetName: (normalizedOptions.build && normalizedOptions.build.targetName) ||
100
78
  'build',
101
79
  testTargetName: (normalizedOptions.test && normalizedOptions.test.targetName) ||
@@ -114,6 +92,14 @@ exports.createNodes = [
114
92
  };
115
93
  const result = await (0, analyzer_client_1.analyzeProjects)([...configFilePaths], analyzerOptions);
116
94
  if ((0, analyzer_client_1.isAnalysisErrorResult)(result)) {
95
+ if (result.error.message === analyzer_client_1.ANALYZER_CANCELLED_MESSAGE) {
96
+ // Superseded by a newer analysis — silently return empty rather than
97
+ // failing the user's command with an internal sentinel.
98
+ // Safe only because the daemon drops this compute at its
99
+ // `stalePostCreateNodes` guard before createDependencies runs; that one
100
+ // would throw on the cache this run deliberately did not write.
101
+ return [];
102
+ }
117
103
  throw result.error;
118
104
  }
119
105
  const { nodesByFile } = result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nx/dotnet",
3
- "version": "23.2.0-pr.36527.2252709",
3
+ "version": "23.2.0-pr.36838.ef8e258",
4
4
  "private": false,
5
5
  "description": "The Nx Plugin for .NET containing graph support for working with .NET projects in an Nx workspace.",
6
6
  "keywords": [
@@ -49,7 +49,7 @@
49
49
  "dependencies": {
50
50
  "tslib": "^2.3.0",
51
51
  "ignore": "^7.0.5",
52
- "@nx/devkit": "23.2.0-pr.36527.2252709"
52
+ "@nx/devkit": "23.2.0-pr.36838.ef8e258"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/jest": "30.0.0",
@@ -58,7 +58,7 @@
58
58
  "memfs": "^4.9.2",
59
59
  "ts-jest": "^29.4.7",
60
60
  "typescript": "~6.0.3",
61
- "nx": "23.2.0-pr.36527.2252709"
61
+ "nx": "23.2.0-pr.36838.ef8e258"
62
62
  },
63
63
  "publishConfig": {
64
64
  "access": "public"