@nx/dotnet 23.2.0-beta.10 → 23.2.0-beta.12
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 +2 -2
- package/dist/analyzer/analyzer-client.d.ts +6 -0
- package/dist/analyzer/analyzer-client.d.ts.map +1 -1
- package/dist/analyzer/analyzer-client.js +133 -38
- package/dist/lib/MsbuildAnalyzer.dll +0 -0
- package/dist/lib/MsbuildAnalyzer.pdb +0 -0
- package/dist/plugins/create-nodes.d.ts.map +1 -1
- package/dist/plugins/create-nodes.js +8 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
|
|
8
8
|
<div style="text-align: center;">
|
|
9
9
|
|
|
10
|
-
[]()
|
|
10
|
+
[](<>)
|
|
11
11
|
[](https://www.npmjs.com/package/nx)
|
|
12
|
-
[]()
|
|
12
|
+
[](<>)
|
|
13
13
|
[](http://commitizen.github.io/cz-cli/)
|
|
14
14
|
[](https://go.nx.dev/community)
|
|
15
15
|
[](https://nx.dev/docs/features/ci-features/sandboxing)
|
|
@@ -1,4 +1,10 @@
|
|
|
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, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analyzer-client.d.ts","sourceRoot":"","sources":["../../src/analyzer/analyzer-client.ts"],"names":[],"mappings":"
|
|
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
|
|
@@ -41,7 +70,7 @@ async function calculateProjectFilesHash(files) {
|
|
|
41
70
|
* Run the msbuild-analyzer and return the results.
|
|
42
71
|
* Uses stdin for large file lists to avoid ARG_MAX issues.
|
|
43
72
|
*/
|
|
44
|
-
function runAnalyzer(files, options) {
|
|
73
|
+
async function runAnalyzer(files, options) {
|
|
45
74
|
if (files.length === 0) {
|
|
46
75
|
return { nodesByFile: {}, referencesByRoot: {} };
|
|
47
76
|
}
|
|
@@ -72,44 +101,106 @@ function runAnalyzer(files, options) {
|
|
|
72
101
|
// }
|
|
73
102
|
// }
|
|
74
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 = '';
|
|
75
118
|
try {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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'));
|
|
93
167
|
});
|
|
94
|
-
|
|
95
|
-
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
if (signal.reason === 'cancelled') {
|
|
171
|
+
throw new Error(exports.ANALYZER_CANCELLED_MESSAGE);
|
|
172
|
+
}
|
|
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.`);
|
|
96
177
|
}
|
|
97
|
-
|
|
98
|
-
|
|
178
|
+
const err = error;
|
|
179
|
+
const stderrAlreadyInMessage = err.message.includes(stderr);
|
|
180
|
+
if (stderr && !stderrAlreadyInMessage) {
|
|
181
|
+
devkit_1.logger.error(`msbuild-analyzer error: ${stderr}`);
|
|
99
182
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
183
|
+
throw new Error(`Failed to run msbuild-analyzer: ${err.message}${stderr && !stderrAlreadyInMessage ? `\n${stderr}` : ''}`);
|
|
184
|
+
}
|
|
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;
|
|
103
191
|
}
|
|
104
|
-
|
|
105
|
-
|
|
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);
|
|
106
201
|
}
|
|
107
202
|
catch (error) {
|
|
108
|
-
|
|
109
|
-
if (err.stderr) {
|
|
110
|
-
devkit_1.logger.error(`msbuild-analyzer error: ${err.stderr}`);
|
|
111
|
-
}
|
|
112
|
-
throw new Error(`Failed to run msbuild-analyzer: ${err.message}${err.stderr ? `\n${err.stderr}` : ''}`);
|
|
203
|
+
throw new Error(`Failed to parse msbuild-analyzer output: ${error.message}`);
|
|
113
204
|
}
|
|
114
205
|
}
|
|
115
206
|
/**
|
|
@@ -141,7 +232,7 @@ async function analyzeProjects(files, options) {
|
|
|
141
232
|
}
|
|
142
233
|
// Run the analyzer
|
|
143
234
|
try {
|
|
144
|
-
const result = runAnalyzer(files, options);
|
|
235
|
+
const result = await runAnalyzer(files, options);
|
|
145
236
|
// Update local cache
|
|
146
237
|
cache = {
|
|
147
238
|
hash: filesHash,
|
|
@@ -162,10 +253,14 @@ async function analyzeProjects(files, options) {
|
|
|
162
253
|
const errorResult = {
|
|
163
254
|
error: err,
|
|
164
255
|
};
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
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
|
+
}
|
|
169
264
|
return errorResult;
|
|
170
265
|
}
|
|
171
266
|
}
|
|
Binary file
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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"}
|
|
@@ -92,6 +92,14 @@ exports.createNodes = [
|
|
|
92
92
|
};
|
|
93
93
|
const result = await (0, analyzer_client_1.analyzeProjects)([...configFilePaths], analyzerOptions);
|
|
94
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
|
+
}
|
|
95
103
|
throw result.error;
|
|
96
104
|
}
|
|
97
105
|
const { nodesByFile } = result;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nx/dotnet",
|
|
3
|
-
"version": "23.2.0-beta.
|
|
3
|
+
"version": "23.2.0-beta.12",
|
|
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-beta.
|
|
52
|
+
"@nx/devkit": "23.2.0-beta.12"
|
|
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-beta.
|
|
61
|
+
"nx": "23.2.0-beta.12"
|
|
62
62
|
},
|
|
63
63
|
"publishConfig": {
|
|
64
64
|
"access": "public"
|