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.
- package/dist/src/ai/configure-ai-agents-disclaimer.d.ts +5 -0
- package/dist/src/ai/configure-ai-agents-disclaimer.js +31 -0
- package/dist/src/command-line/add/add.js +8 -1
- package/dist/src/command-line/import/import.js +1 -1
- package/dist/src/command-line/init/configure-plugins.js +3 -2
- package/dist/src/command-line/init/implementation/add-nx-to-monorepo.js +4 -2
- package/dist/src/command-line/init/implementation/add-nx-to-nest.js +4 -3
- package/dist/src/command-line/init/implementation/add-nx-to-npm-repo.js +4 -3
- package/dist/src/command-line/init/implementation/add-nx-to-turborepo.js +4 -3
- package/dist/src/command-line/init/implementation/angular/index.js +4 -2
- package/dist/src/command-line/init/implementation/utils.d.ts +3 -3
- package/dist/src/command-line/init/implementation/utils.js +22 -3
- package/dist/src/command-line/migrate/migrate.d.ts +13 -0
- package/dist/src/command-line/migrate/migrate.js +45 -0
- package/dist/src/command-line/release/utils/git.js +6 -2
- package/dist/src/command-line/show/show-target/info.js +11 -2
- package/dist/src/daemon/client/client.d.ts +0 -2
- package/dist/src/daemon/client/client.js +17 -19
- package/dist/src/devkit-internals.d.ts +2 -0
- package/dist/src/devkit-internals.js +7 -1
- package/dist/src/native/index.d.ts +1 -1
- package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
- package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
- package/dist/src/plugins/js/lock-file/npm-parser.js +71 -27
- package/dist/src/plugins/js/lock-file/pnpm-parser.js +46 -15
- package/dist/src/plugins/js/lock-file/project-graph-pruning.js +1 -1
- package/dist/src/plugins/js/lock-file/yarn-parser.js +32 -5
- package/dist/src/plugins/js/utils/register.d.ts +18 -0
- package/dist/src/plugins/js/utils/register.js +81 -0
- package/dist/src/plugins/js/utils/typescript.d.ts +9 -0
- package/dist/src/plugins/js/utils/typescript.js +15 -0
- package/dist/src/project-graph/error-types.js +41 -1
- package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +4 -0
- package/dist/src/project-graph/plugins/transpiler.js +4 -0
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.d.ts +9 -12
- package/dist/src/project-graph/utils/project-configuration/name-substitution-manager.js +77 -61
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.d.ts +1 -1
- package/dist/src/project-graph/utils/project-configuration/project-nodes-manager.js +1 -1
- package/dist/src/project-graph/utils/project-configuration-utils.js +4 -6
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +2 -0
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +2 -0
- package/dist/src/tasks-runner/life-cycles/performance-report.js +11 -2
- package/dist/src/tasks-runner/run-command.js +5 -3
- package/dist/src/utils/acknowledge-build-scripts.d.ts +13 -0
- package/dist/src/utils/acknowledge-build-scripts.js +100 -0
- package/dist/src/utils/catalog/manager-utils.d.ts +1 -2
- package/dist/src/utils/catalog/manager-utils.js +22 -5
- package/dist/src/utils/catalog/pnpm-manager.d.ts +1 -0
- package/dist/src/utils/catalog/pnpm-manager.js +3 -15
- package/dist/src/utils/catalog/yarn-manager.d.ts +1 -0
- package/dist/src/utils/catalog/yarn-manager.js +3 -15
- package/dist/src/utils/git-utils.d.ts +2 -3
- package/dist/src/utils/git-utils.js +101 -47
- package/dist/src/utils/min-release-age/behavior/pnpm.js +9 -7
- package/dist/src/utils/provenance.js +12 -2
- package/migrations.json +7 -1
- package/package.json +11 -11
- package/dist/src/migrations/update-17-3-0/nx-release-path.d.ts +0 -3
- package/dist/src/migrations/update-17-3-0/nx-release-path.js +0 -47
|
@@ -25,6 +25,10 @@ function registerPluginTSTranspiler() {
|
|
|
25
25
|
if (exports.unregisterPluginTSTranspiler !== null) {
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
|
+
// Align Node's runtime resolution with the source-first plugin entry so the
|
|
29
|
+
// plugin's transitive workspace imports don't fall through to unbuilt `dist`.
|
|
30
|
+
// A no-op inside a plugin worker/daemon already spawned with `--conditions`.
|
|
31
|
+
(0, register_1.ensureResolveConditionsInjected)((0, typescript_1.getRootTsConfigResolveExportsConditions)(workspace_root_1.workspaceRoot));
|
|
28
32
|
if ((0, register_1.isNativeStripPreferred)()) {
|
|
29
33
|
// Native strip handles `.ts` syntax but doesn't rewrite NodeNext-style
|
|
30
34
|
// `.js` relative specifiers to their `.ts` sources. Patch the CJS resolver
|
|
@@ -4,16 +4,13 @@ import { ProjectConfiguration } from '../../../config/workspace-json-project-jso
|
|
|
4
4
|
* reference. `RootRef` carries the referenced project's root (resolved
|
|
5
5
|
* via nameMap lookup); `UsageRef` carries the raw written name (for
|
|
6
6
|
* forward refs, promoted to `RootRef` in place when the name is
|
|
7
|
-
* identified). `
|
|
8
|
-
* name back; `targetPart` preserves the `:target` suffix from
|
|
7
|
+
* identified). `targetPart` preserves the `:target` suffix from
|
|
9
8
|
* `dependsOn` strings.
|
|
10
9
|
*/
|
|
11
10
|
export declare abstract class NameRef {
|
|
12
11
|
value: string;
|
|
13
|
-
parent: unknown;
|
|
14
|
-
key: string | undefined;
|
|
15
12
|
targetPart: string | undefined;
|
|
16
|
-
constructor(value: string,
|
|
13
|
+
constructor(value: string, targetPart: string | undefined);
|
|
17
14
|
}
|
|
18
15
|
export declare class RootRef extends NameRef {
|
|
19
16
|
}
|
|
@@ -27,15 +24,15 @@ export declare function isUsageRef(value: unknown): value is UsageRef;
|
|
|
27
24
|
* then resolves them after all merging is done.
|
|
28
25
|
*
|
|
29
26
|
* Tracking by array position breaks once `'...'` spreads shuffle indices,
|
|
30
|
-
* so each ref becomes a sentinel object.
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
27
|
+
* so each ref becomes a sentinel object. Merges copy sentinels by
|
|
28
|
+
* reference — one sentinel can end up in many arrays (e.g. a pattern
|
|
29
|
+
* target's dependsOn applied to every matching target) — so the final
|
|
30
|
+
* pass sweeps the merged rootMap and resolves every sentinel where it
|
|
31
|
+
* actually sits. Sentinels in arrays dropped by a full-replace are never
|
|
32
|
+
* visited and vanish with their array.
|
|
35
33
|
*/
|
|
36
34
|
export declare class ProjectNameInNodePropsManager {
|
|
37
35
|
private getNameMap;
|
|
38
|
-
private allRefs;
|
|
39
36
|
private pendingByName;
|
|
40
37
|
constructor(getNameMap?: () => Record<string, ProjectConfiguration>);
|
|
41
38
|
registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>): void;
|
|
@@ -45,6 +42,6 @@ export declare class ProjectNameInNodePropsManager {
|
|
|
45
42
|
private createRef;
|
|
46
43
|
identifyProjectWithRoot(root: string, name: string): void;
|
|
47
44
|
applySubstitutions(rootMap: Record<string, ProjectConfiguration>): void;
|
|
45
|
+
private substituteInArray;
|
|
48
46
|
private resolveFinalName;
|
|
49
|
-
private writeReplacement;
|
|
50
47
|
}
|
|
@@ -11,15 +11,12 @@ const split_target_1 = require("../../../utils/split-target");
|
|
|
11
11
|
* reference. `RootRef` carries the referenced project's root (resolved
|
|
12
12
|
* via nameMap lookup); `UsageRef` carries the raw written name (for
|
|
13
13
|
* forward refs, promoted to `RootRef` in place when the name is
|
|
14
|
-
* identified). `
|
|
15
|
-
* name back; `targetPart` preserves the `:target` suffix from
|
|
14
|
+
* identified). `targetPart` preserves the `:target` suffix from
|
|
16
15
|
* `dependsOn` strings.
|
|
17
16
|
*/
|
|
18
17
|
class NameRef {
|
|
19
|
-
constructor(value,
|
|
18
|
+
constructor(value, targetPart) {
|
|
20
19
|
this.value = value;
|
|
21
|
-
this.parent = parent;
|
|
22
|
-
this.key = key;
|
|
23
20
|
this.targetPart = targetPart;
|
|
24
21
|
}
|
|
25
22
|
}
|
|
@@ -44,15 +41,15 @@ function isUsageRef(value) {
|
|
|
44
41
|
* then resolves them after all merging is done.
|
|
45
42
|
*
|
|
46
43
|
* Tracking by array position breaks once `'...'` spreads shuffle indices,
|
|
47
|
-
* so each ref becomes a sentinel object.
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
44
|
+
* so each ref becomes a sentinel object. Merges copy sentinels by
|
|
45
|
+
* reference — one sentinel can end up in many arrays (e.g. a pattern
|
|
46
|
+
* target's dependsOn applied to every matching target) — so the final
|
|
47
|
+
* pass sweeps the merged rootMap and resolves every sentinel where it
|
|
48
|
+
* actually sits. Sentinels in arrays dropped by a full-replace are never
|
|
49
|
+
* visited and vanish with their array.
|
|
52
50
|
*/
|
|
53
51
|
class ProjectNameInNodePropsManager {
|
|
54
52
|
constructor(getNameMap) {
|
|
55
|
-
this.allRefs = new Set();
|
|
56
53
|
this.pendingByName = new Map();
|
|
57
54
|
this.getNameMap = getNameMap ?? (() => ({}));
|
|
58
55
|
}
|
|
@@ -82,26 +79,20 @@ class ProjectNameInNodePropsManager {
|
|
|
82
79
|
processInputs(inputs) {
|
|
83
80
|
for (let i = 0; i < inputs.length; i++) {
|
|
84
81
|
const entry = inputs[i];
|
|
85
|
-
|
|
86
|
-
// original array, so rebind parent to this one.
|
|
87
|
-
if (isNameRef(entry)) {
|
|
88
|
-
entry.parent = inputs;
|
|
82
|
+
if (isNameRef(entry))
|
|
89
83
|
continue;
|
|
90
|
-
}
|
|
91
84
|
if (!entry || typeof entry !== 'object')
|
|
92
85
|
continue;
|
|
93
86
|
if (!('projects' in entry))
|
|
94
87
|
continue;
|
|
95
88
|
const element = entry;
|
|
96
89
|
const projects = element.projects;
|
|
97
|
-
if (isNameRef(projects))
|
|
98
|
-
// Object-parent sentinel — element identity is stable across spread.
|
|
90
|
+
if (isNameRef(projects))
|
|
99
91
|
continue;
|
|
100
|
-
}
|
|
101
92
|
if (typeof projects === 'string') {
|
|
102
93
|
if (projects === 'self' || projects === 'dependencies')
|
|
103
94
|
continue;
|
|
104
|
-
element.projects = this.createRef(projects
|
|
95
|
+
element.projects = this.createRef(projects);
|
|
105
96
|
}
|
|
106
97
|
else if (Array.isArray(projects)) {
|
|
107
98
|
this.processProjectsArray(projects);
|
|
@@ -111,12 +102,8 @@ class ProjectNameInNodePropsManager {
|
|
|
111
102
|
processDependsOn(dependsOn, ownerTargets, ownerName) {
|
|
112
103
|
for (let i = 0; i < dependsOn.length; i++) {
|
|
113
104
|
const dep = dependsOn[i];
|
|
114
|
-
|
|
115
|
-
// merge copied it out of its original.
|
|
116
|
-
if (isNameRef(dep)) {
|
|
117
|
-
dep.parent = dependsOn;
|
|
105
|
+
if (isNameRef(dep))
|
|
118
106
|
continue;
|
|
119
|
-
}
|
|
120
107
|
if (typeof dep === 'string') {
|
|
121
108
|
// `^target` and same-project targets aren't cross-project refs.
|
|
122
109
|
if (dep.startsWith('^') || (ownerTargets && dep in ownerTargets)) {
|
|
@@ -126,7 +113,7 @@ class ProjectNameInNodePropsManager {
|
|
|
126
113
|
if (rest.length === 0)
|
|
127
114
|
continue;
|
|
128
115
|
const targetPart = rest.join(':');
|
|
129
|
-
dependsOn[i] = this.createRef(maybeProject,
|
|
116
|
+
dependsOn[i] = this.createRef(maybeProject, targetPart);
|
|
130
117
|
continue;
|
|
131
118
|
}
|
|
132
119
|
if (!dep || typeof dep !== 'object' || !('projects' in dep))
|
|
@@ -142,7 +129,7 @@ class ProjectNameInNodePropsManager {
|
|
|
142
129
|
projects === 'dependencies') {
|
|
143
130
|
continue;
|
|
144
131
|
}
|
|
145
|
-
element.projects = this.createRef(projects
|
|
132
|
+
element.projects = this.createRef(projects);
|
|
146
133
|
}
|
|
147
134
|
else if (Array.isArray(projects)) {
|
|
148
135
|
this.processProjectsArray(projects);
|
|
@@ -152,24 +139,21 @@ class ProjectNameInNodePropsManager {
|
|
|
152
139
|
processProjectsArray(projects) {
|
|
153
140
|
for (let j = 0; j < projects.length; j++) {
|
|
154
141
|
const name = projects[j];
|
|
155
|
-
if (isNameRef(name))
|
|
156
|
-
name.parent = projects;
|
|
142
|
+
if (isNameRef(name))
|
|
157
143
|
continue;
|
|
158
|
-
}
|
|
159
144
|
if (typeof name !== 'string')
|
|
160
145
|
continue;
|
|
161
146
|
if ((0, globs_1.isGlobPattern)(name))
|
|
162
147
|
continue;
|
|
163
|
-
projects[j] = this.createRef(name
|
|
148
|
+
projects[j] = this.createRef(name);
|
|
164
149
|
}
|
|
165
150
|
}
|
|
166
151
|
// Builds a sentinel and registers it.
|
|
167
|
-
createRef(referencedName,
|
|
152
|
+
createRef(referencedName, targetPart) {
|
|
168
153
|
const referencedRoot = this.getNameMap()[referencedName]?.root;
|
|
169
154
|
const ref = referencedRoot !== undefined
|
|
170
|
-
? new RootRef(referencedRoot,
|
|
171
|
-
: new UsageRef(referencedName,
|
|
172
|
-
this.allRefs.add(ref);
|
|
155
|
+
? new RootRef(referencedRoot, targetPart)
|
|
156
|
+
: new UsageRef(referencedName, targetPart);
|
|
173
157
|
if (ref instanceof UsageRef) {
|
|
174
158
|
let set = this.pendingByName.get(referencedName);
|
|
175
159
|
if (!set) {
|
|
@@ -195,25 +179,72 @@ class ProjectNameInNodePropsManager {
|
|
|
195
179
|
ref.value = root;
|
|
196
180
|
}
|
|
197
181
|
}
|
|
198
|
-
//
|
|
199
|
-
//
|
|
182
|
+
// Resolves every sentinel in the merged rootMap in place. Sweeping the
|
|
183
|
+
// final config (rather than writing through back-references held by the
|
|
184
|
+
// sentinels) covers sentinels that merges copied into arrays other than
|
|
185
|
+
// the one they were created in, e.g. a pattern target's dependsOn applied
|
|
186
|
+
// to every matching target. Called once after all plugin results have
|
|
187
|
+
// been merged.
|
|
200
188
|
applySubstitutions(rootMap) {
|
|
201
189
|
const nameByRoot = {};
|
|
202
190
|
for (const root in rootMap) {
|
|
203
191
|
nameByRoot[root] = rootMap[root]?.name;
|
|
204
192
|
}
|
|
205
|
-
for (const
|
|
206
|
-
const
|
|
207
|
-
if (
|
|
208
|
-
continue;
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
193
|
+
for (const root in rootMap) {
|
|
194
|
+
const targets = rootMap[root]?.targets;
|
|
195
|
+
if (!targets)
|
|
196
|
+
continue;
|
|
197
|
+
for (const targetName in targets) {
|
|
198
|
+
const targetConfig = targets[targetName];
|
|
199
|
+
if (!targetConfig || typeof targetConfig !== 'object')
|
|
200
|
+
continue;
|
|
201
|
+
if (Array.isArray(targetConfig.inputs)) {
|
|
202
|
+
this.substituteInArray(targetConfig.inputs, nameByRoot);
|
|
203
|
+
}
|
|
204
|
+
if (Array.isArray(targetConfig.dependsOn)) {
|
|
205
|
+
this.substituteInArray(targetConfig.dependsOn, nameByRoot);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
213
208
|
}
|
|
214
|
-
this.allRefs.clear();
|
|
215
209
|
this.pendingByName.clear();
|
|
216
210
|
}
|
|
211
|
+
substituteInArray(entries, nameByRoot) {
|
|
212
|
+
for (let i = 0; i < entries.length; i++) {
|
|
213
|
+
const entry = entries[i];
|
|
214
|
+
if (isNameRef(entry)) {
|
|
215
|
+
const finalName = this.resolveFinalName(entry, nameByRoot);
|
|
216
|
+
if (finalName !== undefined) {
|
|
217
|
+
entries[i] =
|
|
218
|
+
entry.targetPart !== undefined
|
|
219
|
+
? `${finalName}:${entry.targetPart}`
|
|
220
|
+
: finalName;
|
|
221
|
+
}
|
|
222
|
+
continue;
|
|
223
|
+
}
|
|
224
|
+
if (!entry || typeof entry !== 'object' || !('projects' in entry)) {
|
|
225
|
+
continue;
|
|
226
|
+
}
|
|
227
|
+
const element = entry;
|
|
228
|
+
if (isNameRef(element.projects)) {
|
|
229
|
+
const finalName = this.resolveFinalName(element.projects, nameByRoot);
|
|
230
|
+
if (finalName !== undefined) {
|
|
231
|
+
element.projects = finalName;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
else if (Array.isArray(element.projects)) {
|
|
235
|
+
const projects = element.projects;
|
|
236
|
+
for (let j = 0; j < projects.length; j++) {
|
|
237
|
+
const name = projects[j];
|
|
238
|
+
if (!isNameRef(name))
|
|
239
|
+
continue;
|
|
240
|
+
const finalName = this.resolveFinalName(name, nameByRoot);
|
|
241
|
+
if (finalName !== undefined) {
|
|
242
|
+
projects[j] = finalName;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
217
248
|
resolveFinalName(ref, nameByRoot) {
|
|
218
249
|
if (ref instanceof RootRef) {
|
|
219
250
|
return nameByRoot[ref.value];
|
|
@@ -221,20 +252,5 @@ class ProjectNameInNodePropsManager {
|
|
|
221
252
|
// Unpromoted forward ref — best effort, fall back to the written name.
|
|
222
253
|
return this.getNameMap()[ref.value]?.name ?? ref.value;
|
|
223
254
|
}
|
|
224
|
-
writeReplacement(ref, replacement) {
|
|
225
|
-
const parent = ref.parent;
|
|
226
|
-
if (Array.isArray(parent)) {
|
|
227
|
-
// One sentinel may appear at multiple indices (e.g. `[..., ...]`
|
|
228
|
-
// pushed the same reference twice via spread), so replace all.
|
|
229
|
-
for (let i = 0; i < parent.length; i++) {
|
|
230
|
-
if (parent[i] === ref)
|
|
231
|
-
parent[i] = replacement;
|
|
232
|
-
}
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
if (parent && typeof parent === 'object' && ref.key !== undefined) {
|
|
236
|
-
parent[ref.key] = replacement;
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
255
|
}
|
|
240
256
|
exports.ProjectNameInNodePropsManager = ProjectNameInNodePropsManager;
|
|
@@ -34,7 +34,7 @@ export declare class ProjectNodesManager {
|
|
|
34
34
|
*
|
|
35
35
|
* Pass a different `mergedRootMap` for the default-plugin intermediate
|
|
36
36
|
* pass, then call again with `this.rootMap` after it's applied so
|
|
37
|
-
*
|
|
37
|
+
* name refs introduced by that merge are sentinelized as well.
|
|
38
38
|
*/
|
|
39
39
|
registerNameRefs(pluginResultProjects?: Record<string, Omit<ProjectConfiguration, 'root'> & Partial<ProjectConfiguration>>, mergedRootMap?: Record<string, ProjectConfiguration>): void;
|
|
40
40
|
/**
|
|
@@ -263,7 +263,7 @@ class ProjectNodesManager {
|
|
|
263
263
|
*
|
|
264
264
|
* Pass a different `mergedRootMap` for the default-plugin intermediate
|
|
265
265
|
* pass, then call again with `this.rootMap` after it's applied so
|
|
266
|
-
*
|
|
266
|
+
* name refs introduced by that merge are sentinelized as well.
|
|
267
267
|
*/
|
|
268
268
|
registerNameRefs(pluginResultProjects, mergedRootMap = this.rootMap) {
|
|
269
269
|
if (!pluginResultProjects)
|
|
@@ -225,12 +225,10 @@ function mergeCreateNodesResults(specifiedResults, defaultResults, nxJsonConfigu
|
|
|
225
225
|
}));
|
|
226
226
|
}
|
|
227
227
|
}
|
|
228
|
-
// The intermediate apply may have rebuilt dependsOn / inputs arrays
|
|
229
|
-
//
|
|
230
|
-
//
|
|
231
|
-
// the final
|
|
232
|
-
// `parent` to the current array (see
|
|
233
|
-
// ProjectNameInNodePropsManager#processInputs / processDependsOn).
|
|
228
|
+
// The intermediate apply may have rebuilt dependsOn / inputs arrays via
|
|
229
|
+
// spread merges, introducing name-ref strings that weren't visible in any
|
|
230
|
+
// single plugin result. Re-walking the final merged targets sentinelizes
|
|
231
|
+
// them so the final substitution sweep resolves them too.
|
|
234
232
|
nodesManager.registerNameRefs(intermediateDefaultRootMap);
|
|
235
233
|
// Overlay default-plugin attribution onto the main source maps using
|
|
236
234
|
// "only fill missing" semantics. Any key already present in
|
|
@@ -60,6 +60,8 @@ export interface PerformanceSummary {
|
|
|
60
60
|
cacheableCount: number;
|
|
61
61
|
cacheSkipped: boolean;
|
|
62
62
|
remoteCacheEnabled: boolean;
|
|
63
|
+
/** The workspace opted out of Nx Cloud (`neverConnectToCloud` / NX_NO_CLOUD) — never recommend it. */
|
|
64
|
+
cloudOptedOut: boolean;
|
|
63
65
|
}
|
|
64
66
|
/** Construction-time inputs for {@link PerformanceLifeCycle}. */
|
|
65
67
|
export interface PerformanceLifeCycleOptions {
|
|
@@ -18,6 +18,8 @@ const CACHE_HIT_STATUSES = new Set([
|
|
|
18
18
|
]);
|
|
19
19
|
/** Gap (ms) still counted as the same pre-dispatch hashing phase; a larger gap means older, unrelated hashing (e.g. a daemon's previous run). */
|
|
20
20
|
const PRE_DISPATCH_HASH_GAP = 1000;
|
|
21
|
+
/** A critical-path task shorter than this fraction of the path is noise, not a speed-up target. */
|
|
22
|
+
const CRITICAL_PATH_TOP_MIN_FRACTION = 0.2;
|
|
21
23
|
/**
|
|
22
24
|
* Pure analysis over one finished run's collected timings — no lifecycle state and no
|
|
23
25
|
* mutation. Built once per run from the {@link PerformanceLifeCycle}'s accumulated
|
|
@@ -171,15 +173,17 @@ class PerformanceAnalysis {
|
|
|
171
173
|
* (their duration is just restore time); no-status tasks (synthetic test runs) are
|
|
172
174
|
* kept.
|
|
173
175
|
*/
|
|
174
|
-
computeCriticalPathTop(criticalPathTasks, durations) {
|
|
175
|
-
return criticalPathTasks
|
|
176
|
+
computeCriticalPathTop(criticalPathTasks, durations, criticalPathDuration) {
|
|
177
|
+
return (criticalPathTasks
|
|
176
178
|
.filter((id) => {
|
|
177
179
|
const status = this.statuses.get(id);
|
|
178
180
|
return !status || !CACHE_HIT_STATUSES.has(status);
|
|
179
181
|
})
|
|
180
182
|
.map((id) => ({ id, duration: durations.get(id) ?? 0 }))
|
|
183
|
+
// Only tasks that meaningfully shape the path are worth speeding up.
|
|
184
|
+
.filter((t) => t.duration >= CRITICAL_PATH_TOP_MIN_FRACTION * criticalPathDuration)
|
|
181
185
|
.sort((a, b) => b.duration - a.duration)
|
|
182
|
-
.slice(0, 3);
|
|
186
|
+
.slice(0, 3));
|
|
183
187
|
}
|
|
184
188
|
/** Cache outcome: tasks restored (`cacheHits`) and the total with a cache outcome (`cacheableCount` = hits + ran). No-status tasks count for neither. */
|
|
185
189
|
computeCacheStats() {
|
|
@@ -244,12 +248,15 @@ class PerformanceAnalysis {
|
|
|
244
248
|
cores,
|
|
245
249
|
criticalPathDuration,
|
|
246
250
|
});
|
|
247
|
-
const criticalPathTop = this.computeCriticalPathTop(criticalPathTasks, durations);
|
|
251
|
+
const criticalPathTop = this.computeCriticalPathTop(criticalPathTasks, durations, criticalPathDuration);
|
|
248
252
|
const { cacheHits, cacheableCount } = this.computeCacheStats();
|
|
249
253
|
// `skipNxCache` already folds in NX_SKIP_NX_CACHE / NX_DISABLE_NX_CACHE
|
|
250
254
|
// (normalized in command-line-utils) — don't re-read those env vars here.
|
|
251
255
|
const cacheSkipped = this.options.skipNxCache === true;
|
|
252
256
|
const remoteCacheEnabled = this.remoteCacheEnabled();
|
|
257
|
+
const cloudOptedOut = this.options.nxJson
|
|
258
|
+
? !!(0, nx_cloud_utils_1.isNxCloudDisabled)(this.options.nxJson)
|
|
259
|
+
: false;
|
|
253
260
|
// Coordinator-dominated: hashing/scheduling outweighs task work by >3x the
|
|
254
261
|
// critical path, which keeps cold runs critical-path-bound.
|
|
255
262
|
const coordinatorDominated = coordinatorOverhead >= performance_report_1.MEANINGFUL_OVERHEAD &&
|
|
@@ -271,14 +278,16 @@ class PerformanceAnalysis {
|
|
|
271
278
|
parallel,
|
|
272
279
|
cores,
|
|
273
280
|
isCI,
|
|
274
|
-
// Can only start distributing in CI when not already doing so
|
|
275
|
-
|
|
281
|
+
// Can only start distributing in CI when not already doing so — and never
|
|
282
|
+
// suggest Nx Agents to a workspace that opted out of Nx Cloud.
|
|
283
|
+
canDistribute: isCI && !distributing && !cloudOptedOut,
|
|
276
284
|
distributing,
|
|
277
285
|
coordinatorDominated,
|
|
278
286
|
cacheHits,
|
|
279
287
|
cacheableCount,
|
|
280
288
|
cacheSkipped,
|
|
281
289
|
remoteCacheEnabled,
|
|
290
|
+
cloudOptedOut,
|
|
282
291
|
};
|
|
283
292
|
}
|
|
284
293
|
}
|
|
@@ -25,6 +25,8 @@ interface RecLink {
|
|
|
25
25
|
* Links are URL-less (the popup re-links them from {@link PerformanceSummaryPayload.links}).
|
|
26
26
|
*/
|
|
27
27
|
export declare function recommendationToPayloadString(rec: Recommendation): string;
|
|
28
|
+
/** Below this run duration (ms), the run is already fast — recommend nothing. */
|
|
29
|
+
export declare const MIN_RECOMMENDATION_RUN_DURATION = 30000;
|
|
28
30
|
/** Below this (ms) overhead is noise, not worth a recommendation. */
|
|
29
31
|
export declare const MEANINGFUL_OVERHEAD = 1000;
|
|
30
32
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MEANINGFUL_OVERHEAD = void 0;
|
|
3
|
+
exports.MEANINGFUL_OVERHEAD = exports.MIN_RECOMMENDATION_RUN_DURATION = void 0;
|
|
4
4
|
exports.recommendationToPayloadString = recommendationToPayloadString;
|
|
5
5
|
exports.buildRecommendations = buildRecommendations;
|
|
6
6
|
exports.formatReport = formatReport;
|
|
@@ -59,6 +59,8 @@ function recommendationLinks(recommendations) {
|
|
|
59
59
|
.filter(isRecLink)
|
|
60
60
|
.map((part) => ({ text: part.visible, href: part.href })));
|
|
61
61
|
}
|
|
62
|
+
/** Below this run duration (ms), the run is already fast — recommend nothing. */
|
|
63
|
+
exports.MIN_RECOMMENDATION_RUN_DURATION = 30_000;
|
|
62
64
|
/** At/below this hit rate, recommend remote cache (if off); above it caching works. */
|
|
63
65
|
const LOW_CACHE_HIT_RATE = 0.1;
|
|
64
66
|
/** Below this (ms) overhead is noise, not worth a recommendation. */
|
|
@@ -136,8 +138,10 @@ const RECOMMENDATIONS = [
|
|
|
136
138
|
},
|
|
137
139
|
{
|
|
138
140
|
// Barely-used cache with no remote: set up Nx Cloud. Whole-phrase link; the payload
|
|
139
|
-
// string stays URL-less (the popup re-links the phrase).
|
|
141
|
+
// string stays URL-less (the popup re-links the phrase). Never pushed at a
|
|
142
|
+
// workspace that opted out of Nx Cloud.
|
|
140
143
|
isApplicable: (c) => !c.cacheSkipped &&
|
|
144
|
+
!c.cloudOptedOut &&
|
|
141
145
|
c.cacheableCount > 0 &&
|
|
142
146
|
!c.remoteCacheEnabled &&
|
|
143
147
|
c.cacheHits / c.cacheableCount <= LOW_CACHE_HIT_RATE,
|
|
@@ -181,6 +185,10 @@ const RECOMMENDATIONS = [
|
|
|
181
185
|
* the GitHub-summary Markdown, and the TUI payload.
|
|
182
186
|
*/
|
|
183
187
|
function buildRecommendations(s) {
|
|
188
|
+
// A fast run has nothing worth optimizing — stats only, no advice.
|
|
189
|
+
if (s.runDuration < exports.MIN_RECOMMENDATION_RUN_DURATION) {
|
|
190
|
+
return [];
|
|
191
|
+
}
|
|
184
192
|
const c = {
|
|
185
193
|
recoverableByParallel: s.recoverableByParallel,
|
|
186
194
|
recoverableByMachines: s.recoverableByMachines,
|
|
@@ -194,6 +202,7 @@ function buildRecommendations(s) {
|
|
|
194
202
|
cacheableCount: s.cacheableCount,
|
|
195
203
|
cacheSkipped: s.cacheSkipped,
|
|
196
204
|
remoteCacheEnabled: s.remoteCacheEnabled,
|
|
205
|
+
cloudOptedOut: s.cloudOptedOut,
|
|
197
206
|
};
|
|
198
207
|
return RECOMMENDATIONS.filter((r) => r.isApplicable(c)).map((r) => r.build(c));
|
|
199
208
|
}
|
|
@@ -25,6 +25,7 @@ const update_manager_1 = require("../nx-cloud/update-manager");
|
|
|
25
25
|
const logger_1 = require("../utils/logger");
|
|
26
26
|
const nx_key_1 = require("../utils/nx-key");
|
|
27
27
|
const output_1 = require("../utils/output");
|
|
28
|
+
const configure_ai_agents_disclaimer_1 = require("../ai/configure-ai-agents-disclaimer");
|
|
28
29
|
const sync_generators_1 = require("../utils/sync-generators");
|
|
29
30
|
const workspace_root_1 = require("../utils/workspace-root");
|
|
30
31
|
const create_task_graph_1 = require("./create-task-graph");
|
|
@@ -168,7 +169,7 @@ async function getTerminalOutputLifeCycle(initiatingProject, initiatingTasks, pr
|
|
|
168
169
|
const lifeCycles = [tsLifeCycle];
|
|
169
170
|
// Only run the TUI if there are tasks to run
|
|
170
171
|
if (tasks.length > 0) {
|
|
171
|
-
appLifeCycle = new AppLifeCycle(tasks, initiatingTasks.map((t) => t.id), isRunOne ? 0 /* RunMode.RunOne */ : 1 /* RunMode.RunMany */, pinnedTasks, nxArgs ?? {}, nxJson.tui ?? {}, titleText, workspace_root_1.workspaceRoot, taskGraph);
|
|
172
|
+
appLifeCycle = new AppLifeCycle(tasks, initiatingTasks.map((t) => t.id), isRunOne ? 0 /* RunMode.RunOne */ : 1 /* RunMode.RunMany */, pinnedTasks, nxArgs ?? {}, nxJson.tui ?? {}, titleText, workspace_root_1.workspaceRoot, taskGraph, (0, nx_cloud_utils_1.isNxCloudUsed)(nxJson));
|
|
172
173
|
// The native endCommand renders the perf report in the exit popup; the runner
|
|
173
174
|
// sources the payload and CompositeLifeCycle forwards it here.
|
|
174
175
|
lifeCycles.unshift(appLifeCycle);
|
|
@@ -410,9 +411,10 @@ async function printConfigureAiAgentsDisclaimer() {
|
|
|
410
411
|
return;
|
|
411
412
|
}
|
|
412
413
|
const { outdatedAgents } = await client_1.daemonClient.getConfigureAiAgentsStatus();
|
|
413
|
-
if (outdatedAgents.
|
|
414
|
-
|
|
414
|
+
if (!(0, configure_ai_agents_disclaimer_1.shouldPrintConfigureAiAgentsDisclaimer)(outdatedAgents, workspace_root_1.workspaceRoot)) {
|
|
415
|
+
return;
|
|
415
416
|
}
|
|
417
|
+
output_1.output.logRawLine(output_1.output.dim('Your AI agent configuration is outdated. Run "nx configure-ai-agents" to update.'));
|
|
416
418
|
}
|
|
417
419
|
catch {
|
|
418
420
|
// Silently ignore errors
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Tree } from '../generators/tree';
|
|
2
|
+
import type { PackageManager } from './package-manager';
|
|
3
|
+
/**
|
|
4
|
+
* Records build-script decisions for dependencies that are about to be
|
|
5
|
+
* installed, in whatever form the given package manager understands.
|
|
6
|
+
*
|
|
7
|
+
* Only pnpm needs this today: pnpm 11+ refuses to install a dependency whose
|
|
8
|
+
* build scripts are neither allowed nor denied, so the generator or command
|
|
9
|
+
* that introduces such a dependency records the decision up front. Other
|
|
10
|
+
* package managers run build scripts unconditionally, so this is a no-op for
|
|
11
|
+
* them.
|
|
12
|
+
*/
|
|
13
|
+
export declare function acknowledgeBuildScripts(treeOrRoot: Tree | string, packageManager: PackageManager, entries: Record<string, boolean>): void;
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.acknowledgeBuildScripts = acknowledgeBuildScripts;
|
|
4
|
+
const fs_1 = require("fs");
|
|
5
|
+
const path_1 = require("path");
|
|
6
|
+
const yaml_1 = require("yaml");
|
|
7
|
+
const semver_1 = require("semver");
|
|
8
|
+
const package_manager_1 = require("./package-manager");
|
|
9
|
+
const fileutils_1 = require("./fileutils");
|
|
10
|
+
const PNPM_WORKSPACE_FILE = 'pnpm-workspace.yaml';
|
|
11
|
+
/**
|
|
12
|
+
* Records build-script decisions for dependencies that are about to be
|
|
13
|
+
* installed, in whatever form the given package manager understands.
|
|
14
|
+
*
|
|
15
|
+
* Only pnpm needs this today: pnpm 11+ refuses to install a dependency whose
|
|
16
|
+
* build scripts are neither allowed nor denied, so the generator or command
|
|
17
|
+
* that introduces such a dependency records the decision up front. Other
|
|
18
|
+
* package managers run build scripts unconditionally, so this is a no-op for
|
|
19
|
+
* them.
|
|
20
|
+
*/
|
|
21
|
+
function acknowledgeBuildScripts(treeOrRoot, packageManager, entries) {
|
|
22
|
+
if (packageManager !== 'pnpm') {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
acknowledgePnpmBuildScripts(treeOrRoot, entries);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Records `allowBuilds` decisions in pnpm-workspace.yaml, creating the file
|
|
29
|
+
* when missing (mirroring `pnpm approve-builds` in single-package repos).
|
|
30
|
+
*
|
|
31
|
+
* Comment-preserving. Existing entries are never overwritten, so user
|
|
32
|
+
* decisions always win. No-op for pnpm < 11, which warns instead of erroring
|
|
33
|
+
* and does not read `allowBuilds`.
|
|
34
|
+
*/
|
|
35
|
+
function acknowledgePnpmBuildScripts(treeOrRoot, entries) {
|
|
36
|
+
const host = createHost(treeOrRoot);
|
|
37
|
+
const pnpmVersion = getPnpmVersion(host);
|
|
38
|
+
if (!pnpmVersion || !(0, semver_1.gte)(pnpmVersion, '11.0.0')) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
const parsed = (0, yaml_1.parseDocument)(host.exists(PNPM_WORKSPACE_FILE) ? host.read(PNPM_WORKSPACE_FILE) : '');
|
|
42
|
+
// A file that doesn't parse cleanly or whose root isn't a mapping is
|
|
43
|
+
// malformed for pnpm; leave it alone rather than crashing or replacing the
|
|
44
|
+
// user's content. pnpm's own error on the file is the actionable signal.
|
|
45
|
+
if (parsed.errors.length > 0 ||
|
|
46
|
+
(parsed.contents != null && !(parsed.contents instanceof yaml_1.YAMLMap))) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
const doc = parsed.contents instanceof yaml_1.YAMLMap ? parsed : new yaml_1.Document({});
|
|
50
|
+
let changed = false;
|
|
51
|
+
for (const [pkg, allowed] of Object.entries(entries)) {
|
|
52
|
+
// Only a real boolean is a user decision. pnpm's non-strict installs stub
|
|
53
|
+
// undecided packages with a placeholder string ("set this to true or
|
|
54
|
+
// false"), which would fail the next strict install if left in place.
|
|
55
|
+
if (typeof doc.getIn(['allowBuilds', pkg]) !== 'boolean') {
|
|
56
|
+
doc.setIn(['allowBuilds', pkg], allowed);
|
|
57
|
+
changed = true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (changed) {
|
|
61
|
+
host.write(PNPM_WORKSPACE_FILE, doc.toString());
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function createHost(treeOrRoot) {
|
|
65
|
+
if (typeof treeOrRoot === 'string') {
|
|
66
|
+
return {
|
|
67
|
+
root: treeOrRoot,
|
|
68
|
+
exists: (p) => (0, fs_1.existsSync)((0, path_1.join)(treeOrRoot, p)),
|
|
69
|
+
read: (p) => (0, fs_1.readFileSync)((0, path_1.join)(treeOrRoot, p), 'utf-8'),
|
|
70
|
+
write: (p, c) => (0, fs_1.writeFileSync)((0, path_1.join)(treeOrRoot, p), c),
|
|
71
|
+
readJson: (p) => (0, fileutils_1.readJsonFile)((0, path_1.join)(treeOrRoot, p)),
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
return {
|
|
75
|
+
root: treeOrRoot.root,
|
|
76
|
+
exists: (p) => treeOrRoot.exists(p),
|
|
77
|
+
read: (p) => treeOrRoot.read(p, 'utf-8'),
|
|
78
|
+
write: (p, c) => treeOrRoot.write(p, c),
|
|
79
|
+
readJson: (p) => JSON.parse(treeOrRoot.read(p, 'utf-8')),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function getPnpmVersion(host) {
|
|
83
|
+
// The host's packageManager field wins: during workspace creation the
|
|
84
|
+
// in-flight package.json only exists in the tree, not on disk.
|
|
85
|
+
if (host.exists('package.json')) {
|
|
86
|
+
const { packageManager } = host.readJson('package.json');
|
|
87
|
+
const version = (0, package_manager_1.parseVersionFromPackageManagerField)('pnpm', typeof packageManager === 'string' ? packageManager : undefined);
|
|
88
|
+
if (version) {
|
|
89
|
+
return version;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
return (0, package_manager_1.getPackageManagerVersion)('pnpm', host.root);
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
// The version cannot be probed (e.g. pnpm is not on PATH). Leave the
|
|
97
|
+
// workspace file untouched; pnpm's own install error remains actionable.
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import type { Tree } from '../../generators/tree';
|
|
2
2
|
import type { CatalogDefinitions } from './types';
|
|
3
|
-
export declare function
|
|
4
|
-
export declare function readCatalogConfigFromTree(filename: string, tree: Tree): CatalogDefinitions | null;
|
|
3
|
+
export declare function readCatalogDefinitions(filename: string, treeOrRoot: Tree | string, cache: Map<string, CatalogDefinitions | null>): CatalogDefinitions | null;
|
|
5
4
|
export declare function updateCatalogVersionsInFile(filename: string, treeOrRoot: Tree | string, updates: Array<{
|
|
6
5
|
packageName: string;
|
|
7
6
|
version: string;
|