nx 23.2.0-beta.0 → 23.2.0-beta.2
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/analytics/analytics.d.ts +6 -0
- package/dist/src/analytics/analytics.js +7 -1
- package/dist/src/analytics/index.d.ts +1 -1
- package/dist/src/analytics/index.js +2 -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/implementation/angular/integrated-workspace.js +2 -5
- package/dist/src/command-line/init/implementation/angular/legacy-angular-versions.js +3 -0
- package/dist/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +4 -1
- 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 +1 -45
- package/dist/src/command-line/migrate/migrate.js +17 -375
- package/dist/src/command-line/nx-cloud/connect/view-logs.js +2 -0
- 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/inputs.js +50 -153
- package/dist/src/command-line/show/show-target/outputs.js +48 -173
- package/dist/src/command-line/show/show-target/utils.d.ts +28 -0
- package/dist/src/command-line/show/show-target/utils.js +83 -3
- package/dist/src/command-line/yargs-utils/shared-options.d.ts +1 -1
- 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/server/handle-tasks-execution-hooks.d.ts +1 -1
- package/dist/src/daemon/server/server.js +7 -4
- package/dist/src/devkit-internals.d.ts +1 -0
- package/dist/src/devkit-internals.js +5 -2
- package/dist/src/generators/utils/nx-json.js +13 -3
- package/dist/src/hasher/check-task-files.d.ts +86 -0
- package/dist/src/hasher/check-task-files.js +378 -0
- package/dist/src/native/index.d.ts +25 -2
- package/dist/src/native/native-bindings.js +2 -0
- 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/utils/pnpm-normalizer.js +3 -0
- package/dist/src/project-graph/build-project-graph.js +7 -1
- package/dist/src/project-graph/file-utils.js +6 -1
- package/dist/src/project-graph/plugins/loaded-nx-plugin.js +1 -0
- package/dist/src/project-graph/project-graph.js +3 -0
- package/dist/src/tasks-runner/life-cycle.d.ts +2 -2
- package/dist/src/tasks-runner/life-cycle.js +2 -2
- package/dist/src/tasks-runner/life-cycles/performance-analysis.d.ts +9 -4
- package/dist/src/tasks-runner/life-cycles/performance-life-cycle.js +5 -4
- package/dist/src/tasks-runner/life-cycles/performance-report.d.ts +16 -8
- package/dist/src/tasks-runner/life-cycles/performance-report.js +55 -27
- package/dist/src/tasks-runner/pseudo-terminal.d.ts +1 -1
- package/dist/src/tasks-runner/pseudo-terminal.js +2 -2
- package/dist/src/tasks-runner/task-orchestrator.d.ts +4 -1
- package/dist/src/tasks-runner/task-orchestrator.js +23 -6
- package/dist/src/utils/acknowledge-build-scripts.js +7 -5
- 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/package-manager.js +6 -2
- package/package.json +11 -11
|
@@ -7,6 +7,7 @@ exports.applyProjectMetadata = applyProjectMetadata;
|
|
|
7
7
|
const fs_1 = require("fs");
|
|
8
8
|
const path_1 = require("path");
|
|
9
9
|
const perf_hooks_1 = require("perf_hooks");
|
|
10
|
+
const analytics_1 = require("../analytics");
|
|
10
11
|
const configuration_1 = require("../config/configuration");
|
|
11
12
|
const file_hasher_1 = require("../hasher/file-hasher");
|
|
12
13
|
const typescript_1 = require("../plugins/js/utils/typescript");
|
|
@@ -222,7 +223,12 @@ async function updateProjectGraphWithPlugins(context, initProjectGraph, plugins,
|
|
|
222
223
|
perf_hooks_1.performance.measure(`${plugin.name}:createDependencies`, {
|
|
223
224
|
start: `${plugin.name}:createDependencies - start`,
|
|
224
225
|
end: `${plugin.name}:createDependencies - end`,
|
|
225
|
-
detail: {
|
|
226
|
+
detail: {
|
|
227
|
+
track: true,
|
|
228
|
+
...(analytics_1.customDimensions && {
|
|
229
|
+
[analytics_1.customDimensions.sampleRate]: analytics_1.PERF_SPAN_SAMPLE_RATE,
|
|
230
|
+
}),
|
|
231
|
+
},
|
|
226
232
|
});
|
|
227
233
|
}));
|
|
228
234
|
perf_hooks_1.performance.mark('createDependencies:end');
|
|
@@ -12,6 +12,7 @@ const fs_1 = require("fs");
|
|
|
12
12
|
const os_1 = require("os");
|
|
13
13
|
const path_1 = require("path");
|
|
14
14
|
const fileutils_1 = require("../utils/fileutils");
|
|
15
|
+
const git_revision_1 = require("../utils/git-revision");
|
|
15
16
|
const ignore_1 = require("../utils/ignore");
|
|
16
17
|
const json_diff_1 = require("../utils/json-diff");
|
|
17
18
|
const workspace_root_1 = require("../utils/workspace-root");
|
|
@@ -116,11 +117,14 @@ function readLockFileAtRevision(file, lockFileName, revision, readFileAtRevision
|
|
|
116
117
|
}
|
|
117
118
|
exports.TEN_MEGABYTES = 1024 * 10000;
|
|
118
119
|
function defaultReadFileAtRevision(file, revision) {
|
|
120
|
+
if (revision) {
|
|
121
|
+
(0, git_revision_1.assertValidGitRevision)(revision);
|
|
122
|
+
}
|
|
119
123
|
try {
|
|
120
124
|
const filePathInGitRepository = getFilePathInGitRepository(file);
|
|
121
125
|
return !revision
|
|
122
126
|
? (0, fs_1.readFileSync)(file, 'utf-8')
|
|
123
|
-
: (0, child_process_1.
|
|
127
|
+
: (0, child_process_1.execFileSync)('git', ['show', `${revision}:${filePathInGitRepository}`], {
|
|
124
128
|
maxBuffer: exports.TEN_MEGABYTES,
|
|
125
129
|
stdio: ['pipe', 'pipe', 'ignore'],
|
|
126
130
|
windowsHide: true,
|
|
@@ -140,6 +144,7 @@ function defaultReadBunLockFileAtRevision(file, revision) {
|
|
|
140
144
|
windowsHide: true,
|
|
141
145
|
}).trim();
|
|
142
146
|
}
|
|
147
|
+
(0, git_revision_1.assertValidGitRevision)(revision);
|
|
143
148
|
const filePathInGitRepository = getFilePathInGitRepository(file);
|
|
144
149
|
const tempDirectory = (0, fs_1.mkdtempSync)((0, path_1.join)((0, os_1.tmpdir)(), 'nx-bun-lock-'));
|
|
145
150
|
const tempLockfilePath = (0, path_1.join)(tempDirectory, 'bun.lockb');
|
|
@@ -244,6 +244,7 @@ async function createProjectGraphAndSourceMapsAsync(opts = {
|
|
|
244
244
|
track: true,
|
|
245
245
|
...(analytics_1.customDimensions && {
|
|
246
246
|
[analytics_1.customDimensions.projectCount]: Object.keys(currentProjectGraph.nodes).length,
|
|
247
|
+
[analytics_1.customDimensions.sampleRate]: analytics_1.PERF_SPAN_SAMPLE_RATE,
|
|
247
248
|
}),
|
|
248
249
|
},
|
|
249
250
|
});
|
|
@@ -310,6 +311,7 @@ async function createProjectGraphAndSourceMapsAsync(opts = {
|
|
|
310
311
|
...(analytics_1.customDimensions && {
|
|
311
312
|
[analytics_1.customDimensions.projectCount]: Object.keys(res.projectGraph.nodes)
|
|
312
313
|
.length,
|
|
314
|
+
[analytics_1.customDimensions.sampleRate]: analytics_1.PERF_SPAN_SAMPLE_RATE,
|
|
313
315
|
}),
|
|
314
316
|
},
|
|
315
317
|
});
|
|
@@ -333,6 +335,7 @@ async function createProjectGraphAndSourceMapsAsync(opts = {
|
|
|
333
335
|
track: true,
|
|
334
336
|
...(analytics_1.customDimensions && {
|
|
335
337
|
[analytics_1.customDimensions.projectCount]: Object.keys(projectGraphAndSourceMaps.projectGraph.nodes).length,
|
|
338
|
+
[analytics_1.customDimensions.sampleRate]: analytics_1.PERF_SPAN_SAMPLE_RATE,
|
|
336
339
|
}),
|
|
337
340
|
},
|
|
338
341
|
});
|
|
@@ -44,7 +44,7 @@ export interface LifeCycle {
|
|
|
44
44
|
startTasks?(task: Task[], metadata: TaskMetadata): void | Promise<void>;
|
|
45
45
|
endTasks?(taskResults: TaskResult[], metadata: TaskMetadata): void | Promise<void>;
|
|
46
46
|
printTaskTerminalOutput?(task: Task, status: TaskStatus, output: string): void;
|
|
47
|
-
registerRunningTask?(taskId: string,
|
|
47
|
+
registerRunningTask?(taskId: string, ptyHandles: ExternalObject<[any, any, any]>): void;
|
|
48
48
|
registerRunningTaskWithEmptyParser?(taskId: string): void;
|
|
49
49
|
appendTaskOutput?(taskId: string, output: string, isPtyTask: boolean): void;
|
|
50
50
|
setTaskStatus?(taskId: string, status: NativeTaskStatus): void;
|
|
@@ -72,7 +72,7 @@ export declare class CompositeLifeCycle implements LifeCycle {
|
|
|
72
72
|
startTasks(tasks: Task[], metadata: TaskMetadata): Promise<void>;
|
|
73
73
|
endTasks(taskResults: TaskResult[], metadata: TaskMetadata): Promise<void>;
|
|
74
74
|
printTaskTerminalOutput(task: Task, status: TaskStatus, output: string): void;
|
|
75
|
-
registerRunningTask(taskId: string,
|
|
75
|
+
registerRunningTask(taskId: string, ptyHandles: ExternalObject<[any, any, any]>): void;
|
|
76
76
|
registerRunningTaskWithEmptyParser(taskId: string): void;
|
|
77
77
|
appendTaskOutput(taskId: string, output: string, isPtyTask: boolean): void;
|
|
78
78
|
setTaskStatus(taskId: string, status: NativeTaskStatus): void;
|
|
@@ -67,10 +67,10 @@ class CompositeLifeCycle {
|
|
|
67
67
|
}
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
|
-
registerRunningTask(taskId,
|
|
70
|
+
registerRunningTask(taskId, ptyHandles) {
|
|
71
71
|
for (let l of this.lifeCycles) {
|
|
72
72
|
if (l.registerRunningTask) {
|
|
73
|
-
l.registerRunningTask(taskId,
|
|
73
|
+
l.registerRunningTask(taskId, ptyHandles);
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -29,15 +29,20 @@ export interface Timespan {
|
|
|
29
29
|
/** `parallelism: false` tasks running during the slice (each one holds every slot). */
|
|
30
30
|
nonParallel: number;
|
|
31
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* A task and how long it ran (ms). The unit the report's task lists are built from —
|
|
34
|
+
* shared with the renderers so the producer and the formatters can't drift.
|
|
35
|
+
*/
|
|
36
|
+
export interface TaskDurationRow {
|
|
37
|
+
id: string;
|
|
38
|
+
duration: number;
|
|
39
|
+
}
|
|
32
40
|
export interface PerformanceSummary {
|
|
33
41
|
runDuration: number;
|
|
34
42
|
criticalPathDuration: number;
|
|
35
43
|
criticalPathTaskCount: number;
|
|
36
44
|
/** Longest critical-path tasks that ran (desc, capped at a few), cache hits excluded; empty when the path was fully cached. */
|
|
37
|
-
criticalPathTop:
|
|
38
|
-
id: string;
|
|
39
|
-
duration: number;
|
|
40
|
-
}>;
|
|
45
|
+
criticalPathTop: TaskDurationRow[];
|
|
41
46
|
/** Ids of tasks that failed (slowest first), for the GitHub Actions summary's failed-tasks list. Continuous tasks and tasks without a complete window are excluded. */
|
|
42
47
|
failedTasks: string[];
|
|
43
48
|
/** runDuration − criticalPathDuration. */
|
|
@@ -142,8 +142,9 @@ function flushPerformanceReport() {
|
|
|
142
142
|
// restore_terminal cooks the terminal back post-TUI, so console.log's plain \n
|
|
143
143
|
// renders fine; it also supplies the single trailing newline formatReport omits.
|
|
144
144
|
console.log((0, performance_report_1.formatReport)(summary));
|
|
145
|
-
// In GitHub Actions, also append the report
|
|
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,11 +19,15 @@ export type Recommendation = RecPart[];
|
|
|
15
19
|
* re-links the phrase from {@link PerformanceSummaryPayload.links}.
|
|
16
20
|
*/
|
|
17
21
|
interface RecLink {
|
|
18
|
-
/** Visible label: the sentence that links. */
|
|
19
22
|
visible: string;
|
|
20
|
-
/** OSC 8 click target / appended URL: the utm-tagged URL. */
|
|
21
23
|
href: string;
|
|
22
24
|
}
|
|
25
|
+
/**
|
|
26
|
+
* The critical path's longest tasks as data, so each renderer formats them natively:
|
|
27
|
+
* the terminal and payload as space-aligned columns, Markdown as a nested list
|
|
28
|
+
* (HTML collapses space runs, so aligned columns don't survive rendering there).
|
|
29
|
+
*/
|
|
30
|
+
type RecTaskRows = TaskDurationRow[];
|
|
23
31
|
/**
|
|
24
32
|
* The recommendation string the napi payload ships and the Rust popup matches against.
|
|
25
33
|
* Links are URL-less (the popup re-links them from {@link PerformanceSummaryPayload.links}).
|
|
@@ -25,15 +25,49 @@ const NX_DISTRIBUTE_CTA = 'Distribute across machines with Nx Agents';
|
|
|
25
25
|
function phraseLink(phrase, taggedUrl) {
|
|
26
26
|
return { visible: phrase, href: taggedUrl };
|
|
27
27
|
}
|
|
28
|
+
// Discriminate positively — test for what each part *is*. A `!isRecTaskRows` catch-all
|
|
29
|
+
// would misclassify a future `RecPart` member as a link; TS can't catch that (it never
|
|
30
|
+
// checks a predicate body), so `recommendationLinks`' `.filter(isRecLink)` would ship
|
|
31
|
+
// `{text: undefined, href: undefined}` to the popup.
|
|
28
32
|
function isRecLink(part) {
|
|
29
|
-
return typeof part !== 'string';
|
|
33
|
+
return typeof part !== 'string' && 'href' in part;
|
|
34
|
+
}
|
|
35
|
+
function isRecTaskRows(part) {
|
|
36
|
+
return Array.isArray(part);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Project a recommendation to a string, formatting each non-text part with the caller's
|
|
40
|
+
* renderers. The three output targets (payload, terminal, Markdown) share this one dispatch.
|
|
41
|
+
* After the string and task-rows branches a part is a {@link RecLink}, so `render.link`
|
|
42
|
+
* takes it directly — and a new {@link RecPart} member that is neither would fail to satisfy
|
|
43
|
+
* that `RecLink` parameter, turning "forgot to handle it" into a compile error right here.
|
|
44
|
+
*/
|
|
45
|
+
function renderRecommendation(rec, render) {
|
|
46
|
+
return rec
|
|
47
|
+
.map((part) => {
|
|
48
|
+
if (typeof part === 'string') {
|
|
49
|
+
return part;
|
|
50
|
+
}
|
|
51
|
+
if (isRecTaskRows(part)) {
|
|
52
|
+
return render.taskRows(part);
|
|
53
|
+
}
|
|
54
|
+
return render.link(part);
|
|
55
|
+
})
|
|
56
|
+
.join('');
|
|
30
57
|
}
|
|
31
58
|
/**
|
|
32
59
|
* The recommendation string the napi payload ships and the Rust popup matches against.
|
|
33
60
|
* Links are URL-less (the popup re-links them from {@link PerformanceSummaryPayload.links}).
|
|
34
61
|
*/
|
|
35
62
|
function recommendationToPayloadString(rec) {
|
|
36
|
-
return rec
|
|
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) {
|
|
@@ -77,7 +107,8 @@ function recoverableTime(s) {
|
|
|
77
107
|
}
|
|
78
108
|
/** Render the longest critical-path tasks as aligned columns: task (left), duration (right). */
|
|
79
109
|
function formatTopTaskRows(tasks) {
|
|
80
|
-
//
|
|
110
|
+
// Non-empty by construction: the only recommendation carrying task rows requires
|
|
111
|
+
// `criticalPathTop.length > 0` to apply, so no empty array reaches the widths below.
|
|
81
112
|
const idWidth = Math.max(...tasks.map((t) => t.id.length));
|
|
82
113
|
const durations = tasks.map((t) => (0, native_1.formatDuration)(t.duration));
|
|
83
114
|
const durWidth = Math.max(...durations.map((d) => d.length));
|
|
@@ -172,10 +203,8 @@ const RECOMMENDATIONS = [
|
|
|
172
203
|
// only multi-line rec). Nothing ran (fully cached) → it doesn't apply.
|
|
173
204
|
isApplicable: (c) => criticalPathBound(c) && c.criticalPathTop.length > 0,
|
|
174
205
|
build: (c) => [
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
...formatTopTaskRows(c.criticalPathTop),
|
|
178
|
-
].join('\n'),
|
|
206
|
+
`Speed up or split the longest tasks on the critical path:`,
|
|
207
|
+
c.criticalPathTop,
|
|
179
208
|
],
|
|
180
209
|
},
|
|
181
210
|
];
|
|
@@ -261,18 +290,17 @@ function formatReport(s) {
|
|
|
261
290
|
}
|
|
262
291
|
/**
|
|
263
292
|
* A recommendation as Markdown: every link becomes `[phrase](href)` (no OSC 8, unlike the
|
|
264
|
-
* terminal renderer) — the whole sentence reads as prose and is the link text.
|
|
265
|
-
*
|
|
266
|
-
*
|
|
293
|
+
* terminal renderer) — the whole sentence reads as prose and is the link text. Task rows
|
|
294
|
+
* become a nested list under the recommendation's bullet (space-aligned columns don't
|
|
295
|
+
* survive HTML's whitespace collapsing).
|
|
267
296
|
*/
|
|
268
297
|
function recommendationToMarkdownString(rec) {
|
|
269
|
-
return rec
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
.join('<br>');
|
|
298
|
+
return renderRecommendation(rec, {
|
|
299
|
+
link: (link) => `[${link.visible}](${link.href})`,
|
|
300
|
+
taskRows: (rows) => rows
|
|
301
|
+
.map((t) => `\n - \`${t.id}\` — ${(0, native_1.formatDuration)(t.duration)}`)
|
|
302
|
+
.join(''),
|
|
303
|
+
});
|
|
276
304
|
}
|
|
277
305
|
/**
|
|
278
306
|
* The performance report as GitHub-flavored Markdown for the Actions job summary
|
|
@@ -47,7 +47,7 @@ export declare class PseudoTtyProcess implements RunningTask {
|
|
|
47
47
|
onOutput(callback: (message: string) => void): void;
|
|
48
48
|
getPid(): number | undefined;
|
|
49
49
|
kill(s?: NodeJS.Signals): Promise<void>;
|
|
50
|
-
|
|
50
|
+
getPtyHandles(): import("../native").ExternalObject<[import("../native").ParserArc, import("../native").WriterArc, import("../native").MasterArc]>;
|
|
51
51
|
}
|
|
52
52
|
export declare class PseudoTtyProcessWithSend extends PseudoTtyProcess {
|
|
53
53
|
rustPseudoTerminal: RustPseudoTerminal;
|
|
@@ -150,8 +150,8 @@ class PseudoTtyProcess {
|
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
|
-
|
|
154
|
-
return this.childProcess.
|
|
153
|
+
getPtyHandles() {
|
|
154
|
+
return this.childProcess.getPtyHandles();
|
|
155
155
|
}
|
|
156
156
|
}
|
|
157
157
|
exports.PseudoTtyProcess = PseudoTtyProcess;
|
|
@@ -35,6 +35,7 @@ export declare class TaskOrchestrator {
|
|
|
35
35
|
private reverseTaskDeps;
|
|
36
36
|
private initializingTaskIds;
|
|
37
37
|
private processedTasks;
|
|
38
|
+
private cacheMissedHashes;
|
|
38
39
|
private completedTasks;
|
|
39
40
|
private waitingForTasks;
|
|
40
41
|
private pendingDiscreteWorkers;
|
|
@@ -100,7 +101,9 @@ export declare class TaskOrchestrator {
|
|
|
100
101
|
* The coordinator relies on this running unconditionally (when cache
|
|
101
102
|
* is enabled): tasks dispatched in step 5 via runTaskDirectly skip
|
|
102
103
|
* their own cache lookup on the assumption that this has already
|
|
103
|
-
* confirmed them as misses.
|
|
104
|
+
* confirmed them as misses. Excluding cacheMissedHashes preserves that
|
|
105
|
+
* invariant — every dispatched hash was queried exactly once — but
|
|
106
|
+
* don't add other length-based bails.
|
|
104
107
|
*/
|
|
105
108
|
private resolveCachedTasksBulk;
|
|
106
109
|
/**
|
|
@@ -79,6 +79,12 @@ class TaskOrchestrator {
|
|
|
79
79
|
// underlying real tasks become the anchors.
|
|
80
80
|
this.initializingTaskIds = (0, utils_1.expandInitiatingTasksThroughNoop)(this.initiatingTasks, this.taskGraph, this.projectGraph);
|
|
81
81
|
this.processedTasks = new Map();
|
|
82
|
+
// Hashes confirmed absent from the cache this run. A confirmed miss can
|
|
83
|
+
// only become a hit when the task itself runs — and then it leaves the
|
|
84
|
+
// schedule — so a missed hash never needs re-querying. Without this, a
|
|
85
|
+
// miss waiting for a worker slot is re-queried (including the remote
|
|
86
|
+
// retrieval) on every coordinator cycle.
|
|
87
|
+
this.cacheMissedHashes = new Set();
|
|
82
88
|
this.completedTasks = new Map();
|
|
83
89
|
this.waitingForTasks = [];
|
|
84
90
|
this.pendingDiscreteWorkers = new Set();
|
|
@@ -324,9 +330,12 @@ class TaskOrchestrator {
|
|
|
324
330
|
* inside DbCache.getBatch.
|
|
325
331
|
*/
|
|
326
332
|
async fetchCacheHits(tasks) {
|
|
327
|
-
const
|
|
333
|
+
const tasksToQuery = tasks.filter((t) => t.hash && !this.cacheMissedHashes.has(t.hash));
|
|
334
|
+
if (tasksToQuery.length === 0)
|
|
335
|
+
return [];
|
|
336
|
+
const batchResults = await this.cache.getBatch(tasksToQuery);
|
|
328
337
|
const cacheHits = [];
|
|
329
|
-
for (const task of
|
|
338
|
+
for (const task of tasksToQuery) {
|
|
330
339
|
const cachedResult = batchResults.get(task.hash);
|
|
331
340
|
// Replay a cached result only under the same condition it was cached
|
|
332
341
|
// under (shouldCacheTaskResult): successes always, failures only when
|
|
@@ -335,6 +344,9 @@ class TaskOrchestrator {
|
|
|
335
344
|
if (cachedResult && this.shouldCacheTaskResult(task, cachedResult.code)) {
|
|
336
345
|
cacheHits.push({ task, cachedResult });
|
|
337
346
|
}
|
|
347
|
+
else {
|
|
348
|
+
this.cacheMissedHashes.add(task.hash);
|
|
349
|
+
}
|
|
338
350
|
}
|
|
339
351
|
return cacheHits;
|
|
340
352
|
}
|
|
@@ -396,14 +408,19 @@ class TaskOrchestrator {
|
|
|
396
408
|
* The coordinator relies on this running unconditionally (when cache
|
|
397
409
|
* is enabled): tasks dispatched in step 5 via runTaskDirectly skip
|
|
398
410
|
* their own cache lookup on the assumption that this has already
|
|
399
|
-
* confirmed them as misses.
|
|
411
|
+
* confirmed them as misses. Excluding cacheMissedHashes preserves that
|
|
412
|
+
* invariant — every dispatched hash was queried exactly once — but
|
|
413
|
+
* don't add other length-based bails.
|
|
400
414
|
*/
|
|
401
415
|
async resolveCachedTasksBulk() {
|
|
402
416
|
const { scheduledTasks } = this.tasksSchedule.getAllScheduledTasks();
|
|
403
417
|
const candidates = [];
|
|
404
418
|
for (const id of scheduledTasks) {
|
|
405
419
|
const task = this.taskGraph.tasks[id];
|
|
406
|
-
if (task.hash &&
|
|
420
|
+
if (task.hash &&
|
|
421
|
+
!this.cacheMissedHashes.has(task.hash) &&
|
|
422
|
+
!task.continuous &&
|
|
423
|
+
task.cache) {
|
|
407
424
|
candidates.push(task);
|
|
408
425
|
}
|
|
409
426
|
}
|
|
@@ -790,7 +807,7 @@ class TaskOrchestrator {
|
|
|
790
807
|
else if (this.tuiEnabled) {
|
|
791
808
|
if (runningTask instanceof pseudo_terminal_1.PseudoTtyProcess) {
|
|
792
809
|
// This is an external of a the pseudo terminal where a task is running and can be passed to the TUI
|
|
793
|
-
this.options.lifeCycle.registerRunningTask(task.id, runningTask.
|
|
810
|
+
this.options.lifeCycle.registerRunningTask(task.id, runningTask.getPtyHandles());
|
|
794
811
|
runningTask.onOutput((output) => {
|
|
795
812
|
this.options.lifeCycle.appendTaskOutput(task.id, output, true);
|
|
796
813
|
});
|
|
@@ -839,7 +856,7 @@ class TaskOrchestrator {
|
|
|
839
856
|
if (this.tuiEnabled) {
|
|
840
857
|
if (runningTask instanceof pseudo_terminal_1.PseudoTtyProcess) {
|
|
841
858
|
// This is an external of a the pseudo terminal where a task is running and can be passed to the TUI
|
|
842
|
-
this.options.lifeCycle.registerRunningTask(task.id, runningTask.
|
|
859
|
+
this.options.lifeCycle.registerRunningTask(task.id, runningTask.getPtyHandles());
|
|
843
860
|
runningTask.onOutput((output) => {
|
|
844
861
|
this.options.lifeCycle.appendTaskOutput(task.id, output, true);
|
|
845
862
|
});
|
|
@@ -7,6 +7,7 @@ const yaml_1 = require("yaml");
|
|
|
7
7
|
const semver_1 = require("semver");
|
|
8
8
|
const package_manager_1 = require("./package-manager");
|
|
9
9
|
const fileutils_1 = require("./fileutils");
|
|
10
|
+
const json_1 = require("./json");
|
|
10
11
|
const PNPM_WORKSPACE_FILE = 'pnpm-workspace.yaml';
|
|
11
12
|
/**
|
|
12
13
|
* Records build-script decisions for dependencies that are about to be
|
|
@@ -42,23 +43,24 @@ function acknowledgePnpmBuildScripts(treeOrRoot, entries) {
|
|
|
42
43
|
// A file that doesn't parse cleanly or whose root isn't a mapping is
|
|
43
44
|
// malformed for pnpm; leave it alone rather than crashing or replacing the
|
|
44
45
|
// user's content. pnpm's own error on the file is the actionable signal.
|
|
46
|
+
// Empty and comment-only files have no contents at all; setIn creates the
|
|
47
|
+
// mapping for them while keeping whatever comments they carry.
|
|
45
48
|
if (parsed.errors.length > 0 ||
|
|
46
49
|
(parsed.contents != null && !(parsed.contents instanceof yaml_1.YAMLMap))) {
|
|
47
50
|
return;
|
|
48
51
|
}
|
|
49
|
-
const doc = parsed.contents instanceof yaml_1.YAMLMap ? parsed : new yaml_1.Document({});
|
|
50
52
|
let changed = false;
|
|
51
53
|
for (const [pkg, allowed] of Object.entries(entries)) {
|
|
52
54
|
// Only a real boolean is a user decision. pnpm's non-strict installs stub
|
|
53
55
|
// undecided packages with a placeholder string ("set this to true or
|
|
54
56
|
// false"), which would fail the next strict install if left in place.
|
|
55
|
-
if (typeof
|
|
56
|
-
|
|
57
|
+
if (typeof parsed.getIn(['allowBuilds', pkg]) !== 'boolean') {
|
|
58
|
+
parsed.setIn(['allowBuilds', pkg], allowed);
|
|
57
59
|
changed = true;
|
|
58
60
|
}
|
|
59
61
|
}
|
|
60
62
|
if (changed) {
|
|
61
|
-
host.write(PNPM_WORKSPACE_FILE,
|
|
63
|
+
host.write(PNPM_WORKSPACE_FILE, parsed.toString());
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
function createHost(treeOrRoot) {
|
|
@@ -76,7 +78,7 @@ function createHost(treeOrRoot) {
|
|
|
76
78
|
exists: (p) => treeOrRoot.exists(p),
|
|
77
79
|
read: (p) => treeOrRoot.read(p, 'utf-8'),
|
|
78
80
|
write: (p, c) => treeOrRoot.write(p, c),
|
|
79
|
-
readJson: (p) =>
|
|
81
|
+
readJson: (p) => (0, json_1.parseJson)(treeOrRoot.read(p, 'utf-8')),
|
|
80
82
|
};
|
|
81
83
|
}
|
|
82
84
|
function getPnpmVersion(host) {
|
|
@@ -11,6 +11,7 @@ const yargs_parser_1 = tslib_1.__importDefault(require("yargs-parser"));
|
|
|
11
11
|
const file_utils_1 = require("../project-graph/file-utils");
|
|
12
12
|
const output_1 = require("./output");
|
|
13
13
|
const child_process_1 = require("child_process");
|
|
14
|
+
const git_revision_1 = require("./git-revision");
|
|
14
15
|
const workspace_root_1 = require("./workspace-root");
|
|
15
16
|
const shared_options_1 = require("../command-line/yargs-utils/shared-options");
|
|
16
17
|
function createOverrides(__overrides_unparsed__ = []) {
|
|
@@ -192,30 +193,27 @@ function parseFiles(options) {
|
|
|
192
193
|
}
|
|
193
194
|
}
|
|
194
195
|
function getUncommittedFiles() {
|
|
195
|
-
return parseGitOutput(
|
|
196
|
+
return parseGitOutput([
|
|
197
|
+
'diff',
|
|
198
|
+
'--name-only',
|
|
199
|
+
'--no-renames',
|
|
200
|
+
'--relative',
|
|
201
|
+
'HEAD',
|
|
202
|
+
'.',
|
|
203
|
+
]);
|
|
196
204
|
}
|
|
197
205
|
function getUntrackedFiles() {
|
|
198
|
-
return parseGitOutput(
|
|
206
|
+
return parseGitOutput(['ls-files', '--others', '--exclude-standard']);
|
|
199
207
|
}
|
|
200
208
|
function getMergeBase(base, head = 'HEAD') {
|
|
209
|
+
(0, git_revision_1.assertValidGitRevision)(base);
|
|
210
|
+
(0, git_revision_1.assertValidGitRevision)(head);
|
|
201
211
|
try {
|
|
202
|
-
return (
|
|
203
|
-
maxBuffer: file_utils_1.TEN_MEGABYTES,
|
|
204
|
-
cwd: workspace_root_1.workspaceRoot,
|
|
205
|
-
stdio: 'pipe',
|
|
206
|
-
windowsHide: true,
|
|
207
|
-
})
|
|
208
|
-
.toString()
|
|
209
|
-
.trim();
|
|
212
|
+
return runGit(['merge-base', base, head]).toString().trim();
|
|
210
213
|
}
|
|
211
214
|
catch {
|
|
212
215
|
try {
|
|
213
|
-
return (
|
|
214
|
-
maxBuffer: file_utils_1.TEN_MEGABYTES,
|
|
215
|
-
cwd: workspace_root_1.workspaceRoot,
|
|
216
|
-
stdio: 'pipe',
|
|
217
|
-
windowsHide: true,
|
|
218
|
-
})
|
|
216
|
+
return runGit(['merge-base', '--fork-point', base, head])
|
|
219
217
|
.toString()
|
|
220
218
|
.trim();
|
|
221
219
|
}
|
|
@@ -225,15 +223,27 @@ function getMergeBase(base, head = 'HEAD') {
|
|
|
225
223
|
}
|
|
226
224
|
}
|
|
227
225
|
function getFilesUsingBaseAndHead(base, head) {
|
|
228
|
-
|
|
226
|
+
(0, git_revision_1.assertValidGitRevision)(base);
|
|
227
|
+
(0, git_revision_1.assertValidGitRevision)(head);
|
|
228
|
+
return parseGitOutput([
|
|
229
|
+
'diff',
|
|
230
|
+
'--name-only',
|
|
231
|
+
'--no-renames',
|
|
232
|
+
'--relative',
|
|
233
|
+
base,
|
|
234
|
+
head,
|
|
235
|
+
]);
|
|
229
236
|
}
|
|
230
|
-
function
|
|
231
|
-
return (0, child_process_1.
|
|
237
|
+
function runGit(args) {
|
|
238
|
+
return (0, child_process_1.execFileSync)('git', args, {
|
|
232
239
|
maxBuffer: file_utils_1.TEN_MEGABYTES,
|
|
233
240
|
cwd: workspace_root_1.workspaceRoot,
|
|
234
241
|
stdio: 'pipe',
|
|
235
242
|
windowsHide: true,
|
|
236
|
-
})
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function parseGitOutput(args) {
|
|
246
|
+
return runGit(args)
|
|
237
247
|
.toString('utf-8')
|
|
238
248
|
.split('\n')
|
|
239
249
|
.map((a) => a.trim())
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git parses any argument beginning with `-` as an option rather than a
|
|
3
|
+
* revision, so a flag-shaped revision could smuggle options such as
|
|
4
|
+
* `--upload-pack` into the git commands Nx runs.
|
|
5
|
+
*/
|
|
6
|
+
export declare function assertValidGitRevision(revision: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* For refs Nx itself recorded from `git rev-parse` and later read back off
|
|
9
|
+
* disk, where anything other than a commit sha means the value was tampered
|
|
10
|
+
* with rather than that the user picked an unusual revision.
|
|
11
|
+
*/
|
|
12
|
+
export declare function assertValidGitSha(sha: string): void;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.assertValidGitRevision = assertValidGitRevision;
|
|
4
|
+
exports.assertValidGitSha = assertValidGitSha;
|
|
5
|
+
/**
|
|
6
|
+
* Git parses any argument beginning with `-` as an option rather than a
|
|
7
|
+
* revision, so a flag-shaped revision could smuggle options such as
|
|
8
|
+
* `--upload-pack` into the git commands Nx runs.
|
|
9
|
+
*/
|
|
10
|
+
function assertValidGitRevision(revision) {
|
|
11
|
+
if (revision.startsWith('-')) {
|
|
12
|
+
throw new Error(`Invalid git revision: "${revision}". Git revisions cannot start with "-".`);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
const COMMIT_SHA = /^[0-9a-f]{7,40}$/i;
|
|
16
|
+
/**
|
|
17
|
+
* For refs Nx itself recorded from `git rev-parse` and later read back off
|
|
18
|
+
* disk, where anything other than a commit sha means the value was tampered
|
|
19
|
+
* with rather than that the user picked an unusual revision.
|
|
20
|
+
*/
|
|
21
|
+
function assertValidGitSha(sha) {
|
|
22
|
+
if (!COMMIT_SHA.test(sha)) {
|
|
23
|
+
throw new Error(`Invalid git commit sha: "${sha}". Expected a hexadecimal commit sha.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
@@ -165,8 +165,12 @@ function getPackageManagerCommand(packageManager = detectPackageManager(), root
|
|
|
165
165
|
install: 'pnpm install --no-frozen-lockfile', // explicitly disable in case of CI
|
|
166
166
|
ciInstall: 'pnpm install --frozen-lockfile',
|
|
167
167
|
updateLockFile: 'pnpm install --lockfile-only',
|
|
168
|
-
add: isPnpmWorkspace
|
|
169
|
-
|
|
168
|
+
add: isPnpmWorkspace
|
|
169
|
+
? 'pnpm add -w --config.frozen-lockfile=false'
|
|
170
|
+
: 'pnpm add --config.frozen-lockfile=false',
|
|
171
|
+
addDev: isPnpmWorkspace
|
|
172
|
+
? 'pnpm add -Dw --config.frozen-lockfile=false'
|
|
173
|
+
: 'pnpm add -D --config.frozen-lockfile=false',
|
|
170
174
|
rm: 'pnpm rm',
|
|
171
175
|
exec: modernPnpm ? 'pnpm exec' : 'pnpx',
|
|
172
176
|
dlx: modernPnpm ? 'pnpm dlx' : 'pnpx',
|