nx 23.1.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.
- package/dist/bin/nx.js +1 -1
- 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/ai-agent-prompts.d.ts +1 -1
- package/dist/src/command-line/init/ai-agent-prompts.js +7 -6
- package/dist/src/command-line/init/command-object.js +10 -2
- 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/execute-migration.d.ts +72 -0
- package/dist/src/command-line/migrate/execute-migration.js +389 -0
- package/dist/src/command-line/migrate/migrate-ui-api.js +17 -12
- package/dist/src/command-line/migrate/migrate.d.ts +14 -45
- package/dist/src/command-line/migrate/migrate.js +58 -375
- package/dist/src/command-line/release/utils/git.js +6 -2
- package/dist/src/command-line/release/utils/release-graph.d.ts +2 -2
- package/dist/src/command-line/release/utils/shared.d.ts +2 -2
- package/dist/src/command-line/show/show-target/info.js +11 -2
- package/dist/src/commands-runner/create-command-graph.js +5 -0
- package/dist/src/config/nx-json.d.ts +1 -1
- package/dist/src/core/graph/main.js +1 -1
- 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/generators/utils/nx-json.js +13 -3
- 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/file-utils.js +6 -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 +11 -4
- package/dist/src/tasks-runner/life-cycles/performance-analysis.js +15 -6
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +18 -8
- package/dist/src/tasks-runner/life-cycles/performance-report.js +66 -29
- 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/command-line-utils.js +30 -20
- package/dist/src/utils/git-revision.d.ts +12 -0
- package/dist/src/utils/git-revision.js +25 -0
- 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/package-manager.js +6 -2
- 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
|
@@ -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
|
}
|
|
@@ -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
|
|
146
|
-
//
|
|
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
|
-
*
|
|
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
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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,16 +19,22 @@ 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}).
|
|
26
34
|
*/
|
|
27
35
|
export declare function recommendationToPayloadString(rec: Recommendation): string;
|
|
36
|
+
/** Below this run duration (ms), the run is already fast — recommend nothing. */
|
|
37
|
+
export declare const MIN_RECOMMENDATION_RUN_DURATION = 30000;
|
|
28
38
|
/** Below this (ms) overhead is noise, not worth a recommendation. */
|
|
29
39
|
export declare const MEANINGFUL_OVERHEAD = 1000;
|
|
30
40
|
/**
|
|
@@ -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;
|
|
@@ -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
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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) {
|
|
@@ -59,6 +89,8 @@ function recommendationLinks(recommendations) {
|
|
|
59
89
|
.filter(isRecLink)
|
|
60
90
|
.map((part) => ({ text: part.visible, href: part.href })));
|
|
61
91
|
}
|
|
92
|
+
/** Below this run duration (ms), the run is already fast — recommend nothing. */
|
|
93
|
+
exports.MIN_RECOMMENDATION_RUN_DURATION = 30_000;
|
|
62
94
|
/** At/below this hit rate, recommend remote cache (if off); above it caching works. */
|
|
63
95
|
const LOW_CACHE_HIT_RATE = 0.1;
|
|
64
96
|
/** Below this (ms) overhead is noise, not worth a recommendation. */
|
|
@@ -75,7 +107,8 @@ function recoverableTime(s) {
|
|
|
75
107
|
}
|
|
76
108
|
/** Render the longest critical-path tasks as aligned columns: task (left), duration (right). */
|
|
77
109
|
function formatTopTaskRows(tasks) {
|
|
78
|
-
//
|
|
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.
|
|
79
112
|
const idWidth = Math.max(...tasks.map((t) => t.id.length));
|
|
80
113
|
const durations = tasks.map((t) => (0, native_1.formatDuration)(t.duration));
|
|
81
114
|
const durWidth = Math.max(...durations.map((d) => d.length));
|
|
@@ -136,8 +169,10 @@ const RECOMMENDATIONS = [
|
|
|
136
169
|
},
|
|
137
170
|
{
|
|
138
171
|
// 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).
|
|
172
|
+
// string stays URL-less (the popup re-links the phrase). Never pushed at a
|
|
173
|
+
// workspace that opted out of Nx Cloud.
|
|
140
174
|
isApplicable: (c) => !c.cacheSkipped &&
|
|
175
|
+
!c.cloudOptedOut &&
|
|
141
176
|
c.cacheableCount > 0 &&
|
|
142
177
|
!c.remoteCacheEnabled &&
|
|
143
178
|
c.cacheHits / c.cacheableCount <= LOW_CACHE_HIT_RATE,
|
|
@@ -168,10 +203,8 @@ const RECOMMENDATIONS = [
|
|
|
168
203
|
// only multi-line rec). Nothing ran (fully cached) → it doesn't apply.
|
|
169
204
|
isApplicable: (c) => criticalPathBound(c) && c.criticalPathTop.length > 0,
|
|
170
205
|
build: (c) => [
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
...formatTopTaskRows(c.criticalPathTop),
|
|
174
|
-
].join('\n'),
|
|
206
|
+
`Speed up or split the longest tasks on the critical path:`,
|
|
207
|
+
c.criticalPathTop,
|
|
175
208
|
],
|
|
176
209
|
},
|
|
177
210
|
];
|
|
@@ -181,6 +214,10 @@ const RECOMMENDATIONS = [
|
|
|
181
214
|
* the GitHub-summary Markdown, and the TUI payload.
|
|
182
215
|
*/
|
|
183
216
|
function buildRecommendations(s) {
|
|
217
|
+
// A fast run has nothing worth optimizing — stats only, no advice.
|
|
218
|
+
if (s.runDuration < exports.MIN_RECOMMENDATION_RUN_DURATION) {
|
|
219
|
+
return [];
|
|
220
|
+
}
|
|
184
221
|
const c = {
|
|
185
222
|
recoverableByParallel: s.recoverableByParallel,
|
|
186
223
|
recoverableByMachines: s.recoverableByMachines,
|
|
@@ -194,6 +231,7 @@ function buildRecommendations(s) {
|
|
|
194
231
|
cacheableCount: s.cacheableCount,
|
|
195
232
|
cacheSkipped: s.cacheSkipped,
|
|
196
233
|
remoteCacheEnabled: s.remoteCacheEnabled,
|
|
234
|
+
cloudOptedOut: s.cloudOptedOut,
|
|
197
235
|
};
|
|
198
236
|
return RECOMMENDATIONS.filter((r) => r.isApplicable(c)).map((r) => r.build(c));
|
|
199
237
|
}
|
|
@@ -252,18 +290,17 @@ function formatReport(s) {
|
|
|
252
290
|
}
|
|
253
291
|
/**
|
|
254
292
|
* A recommendation as Markdown: every link becomes `[phrase](href)` (no OSC 8, unlike the
|
|
255
|
-
* terminal renderer) — the whole sentence reads as prose and is the link text.
|
|
256
|
-
*
|
|
257
|
-
*
|
|
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).
|
|
258
296
|
*/
|
|
259
297
|
function recommendationToMarkdownString(rec) {
|
|
260
|
-
return rec
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
.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
|
+
});
|
|
267
304
|
}
|
|
268
305
|
/**
|
|
269
306
|
* The performance report as GitHub-flavored Markdown for the Actions job summary
|
|
@@ -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;
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.
|
|
4
|
-
exports.readCatalogConfigFromTree = readCatalogConfigFromTree;
|
|
3
|
+
exports.readCatalogDefinitions = readCatalogDefinitions;
|
|
5
4
|
exports.updateCatalogVersionsInFile = updateCatalogVersionsInFile;
|
|
6
|
-
// Keep in sync with packages/devkit/src/utils/catalog/manager-utils.ts; the body
|
|
7
|
-
// below the imports is duplicated because @nx/devkit supports a range of nx majors
|
|
8
|
-
// and this logic isn't part of the nx surface it can import across that range.
|
|
9
5
|
const js_yaml_1 = require("@zkochan/js-yaml");
|
|
10
6
|
const node_fs_1 = require("node:fs");
|
|
11
7
|
const node_path_1 = require("node:path");
|
|
@@ -119,6 +115,27 @@ function readCatalogConfigFromTree(filename, tree) {
|
|
|
119
115
|
return null;
|
|
120
116
|
}
|
|
121
117
|
}
|
|
118
|
+
// Managers are created per operation (getCatalogManager news one up), so the
|
|
119
|
+
// fs (string-root) branch is cached to read the file once per pass instead of
|
|
120
|
+
// once per catalog reference. The Tree branch stays live since the tree is
|
|
121
|
+
// mutable within a generator.
|
|
122
|
+
function readCatalogDefinitions(filename, treeOrRoot, cache) {
|
|
123
|
+
if (typeof treeOrRoot === 'string') {
|
|
124
|
+
if (cache.has(treeOrRoot)) {
|
|
125
|
+
return cache.get(treeOrRoot);
|
|
126
|
+
}
|
|
127
|
+
const configPath = (0, node_path_1.join)(treeOrRoot, filename);
|
|
128
|
+
const defs = (0, node_fs_1.existsSync)(configPath)
|
|
129
|
+
? readCatalogConfigFromFs(filename, configPath)
|
|
130
|
+
: null;
|
|
131
|
+
cache.set(treeOrRoot, defs);
|
|
132
|
+
return defs;
|
|
133
|
+
}
|
|
134
|
+
if (!treeOrRoot.exists(filename)) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
return readCatalogConfigFromTree(filename, treeOrRoot);
|
|
138
|
+
}
|
|
122
139
|
function updateCatalogVersionsInFile(filename, treeOrRoot, updates) {
|
|
123
140
|
let checkExists;
|
|
124
141
|
let readYaml;
|
|
@@ -7,6 +7,7 @@ import type { CatalogDefinitions, CatalogReference } from './types';
|
|
|
7
7
|
export declare class PnpmCatalogManager implements CatalogManager {
|
|
8
8
|
readonly name = "pnpm";
|
|
9
9
|
readonly catalogProtocol = "catalog:";
|
|
10
|
+
private definitionsByRoot;
|
|
10
11
|
isCatalogReference(version: string): boolean;
|
|
11
12
|
parseCatalogReference(version: string): CatalogReference | null;
|
|
12
13
|
getCatalogDefinitionFilePaths(): string[];
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.PnpmCatalogManager = void 0;
|
|
4
|
-
const node_fs_1 = require("node:fs");
|
|
5
|
-
const node_path_1 = require("node:path");
|
|
6
4
|
const manager_1 = require("./manager");
|
|
7
5
|
const manager_utils_1 = require("./manager-utils");
|
|
8
6
|
const PNPM_WORKSPACE_FILENAME = 'pnpm-workspace.yaml';
|
|
@@ -13,6 +11,8 @@ class PnpmCatalogManager {
|
|
|
13
11
|
constructor() {
|
|
14
12
|
this.name = 'pnpm';
|
|
15
13
|
this.catalogProtocol = 'catalog:';
|
|
14
|
+
// Parsed fs-root definitions, cached per pass. See readCatalogDefinitions.
|
|
15
|
+
this.definitionsByRoot = new Map();
|
|
16
16
|
}
|
|
17
17
|
isCatalogReference(version) {
|
|
18
18
|
return version.startsWith(this.catalogProtocol);
|
|
@@ -33,19 +33,7 @@ class PnpmCatalogManager {
|
|
|
33
33
|
return [PNPM_WORKSPACE_FILENAME];
|
|
34
34
|
}
|
|
35
35
|
getCatalogDefinitions(treeOrRoot) {
|
|
36
|
-
|
|
37
|
-
const configPath = (0, node_path_1.join)(treeOrRoot, PNPM_WORKSPACE_FILENAME);
|
|
38
|
-
if (!(0, node_fs_1.existsSync)(configPath)) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
return (0, manager_utils_1.readCatalogConfigFromFs)(PNPM_WORKSPACE_FILENAME, configPath);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
if (!treeOrRoot.exists(PNPM_WORKSPACE_FILENAME)) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
return (0, manager_utils_1.readCatalogConfigFromTree)(PNPM_WORKSPACE_FILENAME, treeOrRoot);
|
|
48
|
-
}
|
|
36
|
+
return (0, manager_utils_1.readCatalogDefinitions)(PNPM_WORKSPACE_FILENAME, treeOrRoot, this.definitionsByRoot);
|
|
49
37
|
}
|
|
50
38
|
resolveCatalogReference(treeOrRoot, packageName, version) {
|
|
51
39
|
const catalogRef = this.parseCatalogReference(version);
|
|
@@ -7,6 +7,7 @@ import type { CatalogDefinitions, CatalogReference } from './types';
|
|
|
7
7
|
export declare class YarnCatalogManager implements CatalogManager {
|
|
8
8
|
readonly name = "yarn";
|
|
9
9
|
readonly catalogProtocol = "catalog:";
|
|
10
|
+
private definitionsByRoot;
|
|
10
11
|
isCatalogReference(version: string): boolean;
|
|
11
12
|
parseCatalogReference(version: string): CatalogReference | null;
|
|
12
13
|
getCatalogDefinitionFilePaths(): string[];
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.YarnCatalogManager = void 0;
|
|
4
|
-
const node_fs_1 = require("node:fs");
|
|
5
|
-
const node_path_1 = require("node:path");
|
|
6
4
|
const manager_1 = require("./manager");
|
|
7
5
|
const manager_utils_1 = require("./manager-utils");
|
|
8
6
|
const YARNRC_FILENAME = '.yarnrc.yml';
|
|
@@ -13,6 +11,8 @@ class YarnCatalogManager {
|
|
|
13
11
|
constructor() {
|
|
14
12
|
this.name = 'yarn';
|
|
15
13
|
this.catalogProtocol = 'catalog:';
|
|
14
|
+
// Parsed fs-root definitions, cached per pass. See readCatalogDefinitions.
|
|
15
|
+
this.definitionsByRoot = new Map();
|
|
16
16
|
}
|
|
17
17
|
isCatalogReference(version) {
|
|
18
18
|
return version.startsWith(this.catalogProtocol);
|
|
@@ -33,19 +33,7 @@ class YarnCatalogManager {
|
|
|
33
33
|
return [YARNRC_FILENAME];
|
|
34
34
|
}
|
|
35
35
|
getCatalogDefinitions(treeOrRoot) {
|
|
36
|
-
|
|
37
|
-
const configPath = (0, node_path_1.join)(treeOrRoot, YARNRC_FILENAME);
|
|
38
|
-
if (!(0, node_fs_1.existsSync)(configPath)) {
|
|
39
|
-
return null;
|
|
40
|
-
}
|
|
41
|
-
return (0, manager_utils_1.readCatalogConfigFromFs)(YARNRC_FILENAME, configPath);
|
|
42
|
-
}
|
|
43
|
-
else {
|
|
44
|
-
if (!treeOrRoot.exists(YARNRC_FILENAME)) {
|
|
45
|
-
return null;
|
|
46
|
-
}
|
|
47
|
-
return (0, manager_utils_1.readCatalogConfigFromTree)(YARNRC_FILENAME, treeOrRoot);
|
|
48
|
-
}
|
|
36
|
+
return (0, manager_utils_1.readCatalogDefinitions)(YARNRC_FILENAME, treeOrRoot, this.definitionsByRoot);
|
|
49
37
|
}
|
|
50
38
|
resolveCatalogReference(treeOrRoot, packageName, version) {
|
|
51
39
|
const catalogRef = this.parseCatalogReference(version);
|