nx 19.4.0-canary.20240621-472459d → 19.4.0-canary.20240626-3a2e8d4
Sign up to get free protection for your applications and to get access to all the features.
- package/package.json +12 -12
- package/release/changelog-renderer/index.d.ts +6 -0
- package/release/changelog-renderer/index.js +1 -1
- package/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +5 -6
- package/src/command-line/release/changelog.js +6 -1
- package/src/command-line/release/config/config.js +3 -0
- package/src/core/graph/main.js +1 -1
- package/src/daemon/client/client.d.ts +5 -0
- package/src/daemon/client/client.js +14 -0
- package/src/daemon/message-types/task-history.d.ts +13 -0
- package/src/daemon/message-types/task-history.js +19 -0
- package/src/daemon/server/handle-get-task-history.d.ts +4 -0
- package/src/daemon/server/handle-get-task-history.js +12 -0
- package/src/daemon/server/handle-write-task-runs-to-history.d.ts +5 -0
- package/src/daemon/server/handle-write-task-runs-to-history.js +12 -0
- package/src/daemon/server/plugins.js +12 -2
- package/src/daemon/server/server.js +9 -0
- package/src/project-graph/plugins/isolation/index.d.ts +1 -1
- package/src/project-graph/plugins/isolation/index.js +11 -4
- package/src/project-graph/plugins/isolation/plugin-pool.d.ts +1 -1
- package/src/project-graph/plugins/isolation/plugin-pool.js +10 -4
- package/src/project-graph/project-graph.js +7 -1
- package/src/project-graph/utils/project-configuration-utils.js +1 -1
- package/src/tasks-runner/default-tasks-runner.js +2 -2
- package/src/tasks-runner/life-cycle.d.ts +10 -10
- package/src/tasks-runner/life-cycle.js +10 -10
- package/src/tasks-runner/life-cycles/task-history-life-cycle.d.ts +9 -0
- package/src/tasks-runner/life-cycles/task-history-life-cycle.js +54 -0
- package/src/tasks-runner/run-command.js +6 -0
- package/src/tasks-runner/task-orchestrator.js +4 -4
- package/src/utils/serialize-target.d.ts +1 -0
- package/src/utils/serialize-target.js +7 -0
- package/src/utils/task-history.d.ts +8 -0
- package/src/utils/task-history.js +97 -0
@@ -6,6 +6,7 @@ import { Hash } from '../../hasher/task-hasher';
|
|
6
6
|
import { Task, TaskGraph } from '../../config/task-graph';
|
7
7
|
import { ConfigurationSourceMaps } from '../../project-graph/utils/project-configuration-utils';
|
8
8
|
import { NxWorkspaceFiles } from '../../native';
|
9
|
+
import { TaskRun } from '../../utils/task-history';
|
9
10
|
export type UnregisterCallback = () => void;
|
10
11
|
export type ChangedFile = {
|
11
12
|
path: string;
|
@@ -51,6 +52,10 @@ export declare class DaemonClient {
|
|
51
52
|
getWorkspaceFiles(projectRootMap: Record<string, string>): Promise<NxWorkspaceFiles>;
|
52
53
|
getFilesInDirectory(dir: string): Promise<string[]>;
|
53
54
|
hashGlob(globs: string[], exclude?: string[]): Promise<string>;
|
55
|
+
getTaskHistoryForHashes(hashes: string[]): Promise<{
|
56
|
+
[hash: string]: TaskRun[];
|
57
|
+
}>;
|
58
|
+
writeTaskRunsToHistory(taskRuns: TaskRun[]): Promise<void>;
|
54
59
|
isServerAvailable(): Promise<boolean>;
|
55
60
|
private sendToDaemonViaQueue;
|
56
61
|
private setUpConnection;
|
@@ -221,6 +221,20 @@ class DaemonClient {
|
|
221
221
|
};
|
222
222
|
return this.sendToDaemonViaQueue(message);
|
223
223
|
}
|
224
|
+
getTaskHistoryForHashes(hashes) {
|
225
|
+
const message = {
|
226
|
+
type: 'GET_TASK_HISTORY_FOR_HASHES',
|
227
|
+
hashes,
|
228
|
+
};
|
229
|
+
return this.sendToDaemonViaQueue(message);
|
230
|
+
}
|
231
|
+
writeTaskRunsToHistory(taskRuns) {
|
232
|
+
const message = {
|
233
|
+
type: 'WRITE_TASK_RUNS_TO_HISTORY',
|
234
|
+
taskRuns,
|
235
|
+
};
|
236
|
+
return this.sendMessageToDaemon(message);
|
237
|
+
}
|
224
238
|
async isServerAvailable() {
|
225
239
|
return new Promise((resolve) => {
|
226
240
|
try {
|
@@ -0,0 +1,13 @@
|
|
1
|
+
import { TaskRun } from '../../utils/task-history';
|
2
|
+
export declare const GET_TASK_HISTORY_FOR_HASHES: "GET_TASK_HISTORY_FOR_HASHES";
|
3
|
+
export type HandleGetTaskHistoryForHashesMessage = {
|
4
|
+
type: typeof GET_TASK_HISTORY_FOR_HASHES;
|
5
|
+
hashes: string[];
|
6
|
+
};
|
7
|
+
export declare function isHandleGetTaskHistoryForHashesMessage(message: unknown): message is HandleGetTaskHistoryForHashesMessage;
|
8
|
+
export declare const WRITE_TASK_RUNS_TO_HISTORY: "WRITE_TASK_RUNS_TO_HISTORY";
|
9
|
+
export type HandleWriteTaskRunsToHistoryMessage = {
|
10
|
+
type: typeof WRITE_TASK_RUNS_TO_HISTORY;
|
11
|
+
taskRuns: TaskRun[];
|
12
|
+
};
|
13
|
+
export declare function isHandleWriteTaskRunsToHistoryMessage(message: unknown): message is HandleWriteTaskRunsToHistoryMessage;
|
@@ -0,0 +1,19 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.isHandleWriteTaskRunsToHistoryMessage = exports.WRITE_TASK_RUNS_TO_HISTORY = exports.isHandleGetTaskHistoryForHashesMessage = exports.GET_TASK_HISTORY_FOR_HASHES = void 0;
|
4
|
+
exports.GET_TASK_HISTORY_FOR_HASHES = 'GET_TASK_HISTORY_FOR_HASHES';
|
5
|
+
function isHandleGetTaskHistoryForHashesMessage(message) {
|
6
|
+
return (typeof message === 'object' &&
|
7
|
+
message !== null &&
|
8
|
+
'type' in message &&
|
9
|
+
message['type'] === exports.GET_TASK_HISTORY_FOR_HASHES);
|
10
|
+
}
|
11
|
+
exports.isHandleGetTaskHistoryForHashesMessage = isHandleGetTaskHistoryForHashesMessage;
|
12
|
+
exports.WRITE_TASK_RUNS_TO_HISTORY = 'WRITE_TASK_RUNS_TO_HISTORY';
|
13
|
+
function isHandleWriteTaskRunsToHistoryMessage(message) {
|
14
|
+
return (typeof message === 'object' &&
|
15
|
+
message !== null &&
|
16
|
+
'type' in message &&
|
17
|
+
message['type'] === exports.WRITE_TASK_RUNS_TO_HISTORY);
|
18
|
+
}
|
19
|
+
exports.isHandleWriteTaskRunsToHistoryMessage = isHandleWriteTaskRunsToHistoryMessage;
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.handleGetTaskHistoryForHashes = void 0;
|
4
|
+
const task_history_1 = require("../../utils/task-history");
|
5
|
+
async function handleGetTaskHistoryForHashes(hashes) {
|
6
|
+
const history = await (0, task_history_1.getHistoryForHashes)(hashes);
|
7
|
+
return {
|
8
|
+
response: JSON.stringify(history),
|
9
|
+
description: 'handleGetTaskHistoryForHashes',
|
10
|
+
};
|
11
|
+
}
|
12
|
+
exports.handleGetTaskHistoryForHashes = handleGetTaskHistoryForHashes;
|
@@ -0,0 +1,12 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.handleWriteTaskRunsToHistory = void 0;
|
4
|
+
const task_history_1 = require("../../utils/task-history");
|
5
|
+
async function handleWriteTaskRunsToHistory(taskRuns) {
|
6
|
+
await (0, task_history_1.writeTaskRunsToHistory)(taskRuns);
|
7
|
+
return {
|
8
|
+
response: 'true',
|
9
|
+
description: 'handleWriteTaskRunsToHistory',
|
10
|
+
};
|
11
|
+
}
|
12
|
+
exports.handleWriteTaskRunsToHistory = handleWriteTaskRunsToHistory;
|
@@ -1,16 +1,26 @@
|
|
1
1
|
"use strict";
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
3
3
|
exports.cleanupPlugins = exports.getPlugins = void 0;
|
4
|
+
const file_hasher_1 = require("../../hasher/file-hasher");
|
4
5
|
const nx_json_1 = require("../../config/nx-json");
|
5
6
|
const internal_api_1 = require("../../project-graph/plugins/internal-api");
|
6
7
|
const workspace_root_1 = require("../../utils/workspace-root");
|
8
|
+
let currentPluginsConfigurationHash;
|
7
9
|
let loadedPlugins;
|
8
10
|
let cleanup;
|
9
11
|
async function getPlugins() {
|
10
|
-
|
12
|
+
const pluginsConfiguration = (0, nx_json_1.readNxJson)().plugins ?? [];
|
13
|
+
const pluginsConfigurationHash = (0, file_hasher_1.hashObject)(pluginsConfiguration);
|
14
|
+
// If the plugins configuration has not changed, reuse the current plugins
|
15
|
+
if (loadedPlugins &&
|
16
|
+
pluginsConfigurationHash === currentPluginsConfigurationHash) {
|
11
17
|
return loadedPlugins;
|
12
18
|
}
|
13
|
-
|
19
|
+
// Cleanup current plugins before loading new ones
|
20
|
+
if (cleanup) {
|
21
|
+
cleanup();
|
22
|
+
}
|
23
|
+
currentPluginsConfigurationHash = pluginsConfigurationHash;
|
14
24
|
const [result, cleanupFn] = await (0, internal_api_1.loadNxPlugins)(pluginsConfiguration, workspace_root_1.workspaceRoot);
|
15
25
|
cleanup = cleanupFn;
|
16
26
|
loadedPlugins = result;
|
@@ -35,6 +35,9 @@ const get_files_in_directory_1 = require("../message-types/get-files-in-director
|
|
35
35
|
const handle_get_files_in_directory_1 = require("./handle-get-files-in-directory");
|
36
36
|
const hash_glob_1 = require("../message-types/hash-glob");
|
37
37
|
const handle_hash_glob_1 = require("./handle-hash-glob");
|
38
|
+
const task_history_1 = require("../message-types/task-history");
|
39
|
+
const handle_get_task_history_1 = require("./handle-get-task-history");
|
40
|
+
const handle_write_task_runs_to_history_1 = require("./handle-write-task-runs-to-history");
|
38
41
|
let performanceObserver;
|
39
42
|
let workspaceWatcherError;
|
40
43
|
let outputsWatcherError;
|
@@ -121,6 +124,12 @@ async function handleMessage(socket, data) {
|
|
121
124
|
else if ((0, hash_glob_1.isHandleHashGlobMessage)(payload)) {
|
122
125
|
await handleResult(socket, hash_glob_1.HASH_GLOB, () => (0, handle_hash_glob_1.handleHashGlob)(payload.globs, payload.exclude));
|
123
126
|
}
|
127
|
+
else if ((0, task_history_1.isHandleGetTaskHistoryForHashesMessage)(payload)) {
|
128
|
+
await handleResult(socket, 'GET_TASK_HISTORY_FOR_HASHES', () => (0, handle_get_task_history_1.handleGetTaskHistoryForHashes)(payload.hashes));
|
129
|
+
}
|
130
|
+
else if ((0, task_history_1.isHandleWriteTaskRunsToHistoryMessage)(payload)) {
|
131
|
+
await handleResult(socket, 'WRITE_TASK_RUNS_TO_HISTORY', () => (0, handle_write_task_runs_to_history_1.handleWriteTaskRunsToHistory)(payload.taskRuns));
|
132
|
+
}
|
124
133
|
else {
|
125
134
|
await (0, shutdown_utils_1.respondWithErrorAndExit)(socket, `Invalid payload from the client`, new Error(`Unsupported payload sent to daemon server: ${unparsedPayload}`));
|
126
135
|
}
|
@@ -1,3 +1,3 @@
|
|
1
1
|
import { PluginConfiguration } from '../../../config/nx-json';
|
2
2
|
import { LoadedNxPlugin } from '../internal-api';
|
3
|
-
export declare function loadNxPluginInIsolation(plugin: PluginConfiguration, root?: string): [Promise<LoadedNxPlugin>, () => void];
|
3
|
+
export declare function loadNxPluginInIsolation(plugin: PluginConfiguration, root?: string): readonly [Promise<LoadedNxPlugin>, () => void];
|
@@ -10,11 +10,18 @@ const remotePluginCache = new Map();
|
|
10
10
|
function loadNxPluginInIsolation(plugin, root = workspace_root_1.workspaceRoot) {
|
11
11
|
const cacheKey = JSON.stringify(plugin);
|
12
12
|
if (remotePluginCache.has(cacheKey)) {
|
13
|
-
return
|
13
|
+
return remotePluginCache.get(cacheKey);
|
14
14
|
}
|
15
|
-
const loadingPlugin = (0, plugin_pool_1.loadRemoteNxPlugin)(plugin, root);
|
16
|
-
remotePluginCache.set(cacheKey, loadingPlugin);
|
15
|
+
const [loadingPlugin, cleanup] = (0, plugin_pool_1.loadRemoteNxPlugin)(plugin, root);
|
17
16
|
// We clean up plugin workers when Nx process completes.
|
18
|
-
|
17
|
+
const val = [
|
18
|
+
loadingPlugin,
|
19
|
+
() => {
|
20
|
+
cleanup();
|
21
|
+
remotePluginCache.delete(cacheKey);
|
22
|
+
},
|
23
|
+
];
|
24
|
+
remotePluginCache.set(cacheKey, val);
|
25
|
+
return val;
|
19
26
|
}
|
20
27
|
exports.loadNxPluginInIsolation = loadNxPluginInIsolation;
|
@@ -1,3 +1,3 @@
|
|
1
1
|
import { PluginConfiguration } from '../../../config/nx-json';
|
2
2
|
import { LoadedNxPlugin } from '../internal-api';
|
3
|
-
export declare function loadRemoteNxPlugin(plugin: PluginConfiguration, root: string): Promise<LoadedNxPlugin
|
3
|
+
export declare function loadRemoteNxPlugin(plugin: PluginConfiguration, root: string): [Promise<LoadedNxPlugin>, () => void];
|
@@ -42,10 +42,16 @@ function loadRemoteNxPlugin(plugin, root) {
|
|
42
42
|
shutdownPluginWorker(worker);
|
43
43
|
};
|
44
44
|
cleanupFunctions.add(cleanupFunction);
|
45
|
-
return
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
return [
|
46
|
+
new Promise((res, rej) => {
|
47
|
+
worker.on('message', createWorkerHandler(worker, pendingPromises, res, rej));
|
48
|
+
worker.on('exit', exitHandler);
|
49
|
+
}),
|
50
|
+
() => {
|
51
|
+
cleanupFunction();
|
52
|
+
cleanupFunctions.delete(cleanupFunction);
|
53
|
+
},
|
54
|
+
];
|
49
55
|
}
|
50
56
|
exports.loadRemoteNxPlugin = loadRemoteNxPlugin;
|
51
57
|
function shutdownPluginWorker(worker) {
|
@@ -110,7 +110,13 @@ async function buildProjectGraphAndSourceMapsWithoutDaemon() {
|
|
110
110
|
}
|
111
111
|
}
|
112
112
|
finally {
|
113
|
-
|
113
|
+
// When plugins are isolated we don't clean them up during
|
114
|
+
// a single run of the CLI. They are cleaned up when the CLI
|
115
|
+
// process exits. Cleaning them here could cause issues if pending
|
116
|
+
// promises are not resolved.
|
117
|
+
if (process.env.NX_ISOLATE_PLUGINS !== 'true') {
|
118
|
+
cleanup();
|
119
|
+
}
|
114
120
|
}
|
115
121
|
const { projectGraph, projectFileMapCache } = projectGraphResult;
|
116
122
|
perf_hooks_1.performance.mark('build-project-graph-using-project-file-map:end');
|
@@ -293,7 +293,7 @@ function mergeCreateNodesResults(results, errors) {
|
|
293
293
|
const externalNodes = {};
|
294
294
|
const configurationSourceMaps = {};
|
295
295
|
for (const result of results.flat()) {
|
296
|
-
const [
|
296
|
+
const [pluginName, file, nodes] = result;
|
297
297
|
const { projects: projectNodes, externalNodes: pluginExternalNodes } = nodes;
|
298
298
|
const sourceInfo = [file, pluginName];
|
299
299
|
if (result[symbols_1.OVERRIDE_SOURCE_FILE]) {
|
@@ -13,12 +13,12 @@ const defaultTasksRunner = async (tasks, options, context) => {
|
|
13
13
|
options['parallel'] === '') {
|
14
14
|
options['parallel'] = Number(options['maxParallel'] || 3);
|
15
15
|
}
|
16
|
-
options.lifeCycle.startCommand();
|
16
|
+
await options.lifeCycle.startCommand();
|
17
17
|
try {
|
18
18
|
return await runAllTasks(tasks, options, context);
|
19
19
|
}
|
20
20
|
finally {
|
21
|
-
options.lifeCycle.endCommand();
|
21
|
+
await options.lifeCycle.endCommand();
|
22
22
|
}
|
23
23
|
};
|
24
24
|
exports.defaultTasksRunner = defaultTasksRunner;
|
@@ -10,9 +10,9 @@ export interface TaskMetadata {
|
|
10
10
|
groupId: number;
|
11
11
|
}
|
12
12
|
export interface LifeCycle {
|
13
|
-
startCommand?(): void
|
14
|
-
endCommand?(): void
|
15
|
-
scheduleTask?(task: Task): void
|
13
|
+
startCommand?(): void | Promise<void>;
|
14
|
+
endCommand?(): void | Promise<void>;
|
15
|
+
scheduleTask?(task: Task): void | Promise<void>;
|
16
16
|
/**
|
17
17
|
* @deprecated use startTasks
|
18
18
|
*
|
@@ -25,19 +25,19 @@ export interface LifeCycle {
|
|
25
25
|
* endTask won't be supported after Nx 14 is released.
|
26
26
|
*/
|
27
27
|
endTask?(task: Task, code: number): void;
|
28
|
-
startTasks?(task: Task[], metadata: TaskMetadata): void
|
29
|
-
endTasks?(taskResults: TaskResult[], metadata: TaskMetadata): void
|
28
|
+
startTasks?(task: Task[], metadata: TaskMetadata): void | Promise<void>;
|
29
|
+
endTasks?(taskResults: TaskResult[], metadata: TaskMetadata): void | Promise<void>;
|
30
30
|
printTaskTerminalOutput?(task: Task, status: TaskStatus, output: string): void;
|
31
31
|
}
|
32
32
|
export declare class CompositeLifeCycle implements LifeCycle {
|
33
33
|
private readonly lifeCycles;
|
34
34
|
constructor(lifeCycles: LifeCycle[]);
|
35
|
-
startCommand(): void
|
36
|
-
endCommand(): void
|
37
|
-
scheduleTask(task: Task): void
|
35
|
+
startCommand(): Promise<void>;
|
36
|
+
endCommand(): Promise<void>;
|
37
|
+
scheduleTask(task: Task): Promise<void>;
|
38
38
|
startTask(task: Task): void;
|
39
39
|
endTask(task: Task, code: number): void;
|
40
|
-
startTasks(tasks: Task[], metadata: TaskMetadata): void
|
41
|
-
endTasks(taskResults: TaskResult[], metadata: TaskMetadata): void
|
40
|
+
startTasks(tasks: Task[], metadata: TaskMetadata): Promise<void>;
|
41
|
+
endTasks(taskResults: TaskResult[], metadata: TaskMetadata): Promise<void>;
|
42
42
|
printTaskTerminalOutput(task: Task, status: TaskStatus, output: string): void;
|
43
43
|
}
|
@@ -5,24 +5,24 @@ class CompositeLifeCycle {
|
|
5
5
|
constructor(lifeCycles) {
|
6
6
|
this.lifeCycles = lifeCycles;
|
7
7
|
}
|
8
|
-
startCommand() {
|
8
|
+
async startCommand() {
|
9
9
|
for (let l of this.lifeCycles) {
|
10
10
|
if (l.startCommand) {
|
11
|
-
l.startCommand();
|
11
|
+
await l.startCommand();
|
12
12
|
}
|
13
13
|
}
|
14
14
|
}
|
15
|
-
endCommand() {
|
15
|
+
async endCommand() {
|
16
16
|
for (let l of this.lifeCycles) {
|
17
17
|
if (l.endCommand) {
|
18
|
-
l.endCommand();
|
18
|
+
await l.endCommand();
|
19
19
|
}
|
20
20
|
}
|
21
21
|
}
|
22
|
-
scheduleTask(task) {
|
22
|
+
async scheduleTask(task) {
|
23
23
|
for (let l of this.lifeCycles) {
|
24
24
|
if (l.scheduleTask) {
|
25
|
-
l.scheduleTask(task);
|
25
|
+
await l.scheduleTask(task);
|
26
26
|
}
|
27
27
|
}
|
28
28
|
}
|
@@ -40,20 +40,20 @@ class CompositeLifeCycle {
|
|
40
40
|
}
|
41
41
|
}
|
42
42
|
}
|
43
|
-
startTasks(tasks, metadata) {
|
43
|
+
async startTasks(tasks, metadata) {
|
44
44
|
for (let l of this.lifeCycles) {
|
45
45
|
if (l.startTasks) {
|
46
|
-
l.startTasks(tasks, metadata);
|
46
|
+
await l.startTasks(tasks, metadata);
|
47
47
|
}
|
48
48
|
else if (l.startTask) {
|
49
49
|
tasks.forEach((t) => l.startTask(t));
|
50
50
|
}
|
51
51
|
}
|
52
52
|
}
|
53
|
-
endTasks(taskResults, metadata) {
|
53
|
+
async endTasks(taskResults, metadata) {
|
54
54
|
for (let l of this.lifeCycles) {
|
55
55
|
if (l.endTasks) {
|
56
|
-
l.endTasks(taskResults, metadata);
|
56
|
+
await l.endTasks(taskResults, metadata);
|
57
57
|
}
|
58
58
|
else if (l.endTask) {
|
59
59
|
taskResults.forEach((t) => l.endTask(t.task, t.code));
|
@@ -0,0 +1,9 @@
|
|
1
|
+
import { Task } from '../../config/task-graph';
|
2
|
+
import { LifeCycle, TaskResult } from '../life-cycle';
|
3
|
+
export declare class TaskHistoryLifeCycle implements LifeCycle {
|
4
|
+
private startTimings;
|
5
|
+
private taskRuns;
|
6
|
+
startTasks(tasks: Task[]): void;
|
7
|
+
endTasks(taskResults: TaskResult[]): Promise<void>;
|
8
|
+
endCommand(): Promise<void>;
|
9
|
+
}
|
@@ -0,0 +1,54 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.TaskHistoryLifeCycle = void 0;
|
4
|
+
const serialize_target_1 = require("../../utils/serialize-target");
|
5
|
+
const output_1 = require("../../utils/output");
|
6
|
+
const task_history_1 = require("../../utils/task-history");
|
7
|
+
class TaskHistoryLifeCycle {
|
8
|
+
constructor() {
|
9
|
+
this.startTimings = {};
|
10
|
+
this.taskRuns = [];
|
11
|
+
}
|
12
|
+
startTasks(tasks) {
|
13
|
+
for (let task of tasks) {
|
14
|
+
this.startTimings[task.id] = new Date().getTime();
|
15
|
+
}
|
16
|
+
}
|
17
|
+
async endTasks(taskResults) {
|
18
|
+
const taskRuns = taskResults.map((taskResult) => ({
|
19
|
+
project: taskResult.task.target.project,
|
20
|
+
target: taskResult.task.target.target,
|
21
|
+
configuration: taskResult.task.target.configuration,
|
22
|
+
hash: taskResult.task.hash,
|
23
|
+
code: taskResult.code.toString(),
|
24
|
+
status: taskResult.status,
|
25
|
+
start: (taskResult.task.startTime ?? this.startTimings[taskResult.task.id]).toString(),
|
26
|
+
end: (taskResult.task.endTime ?? new Date().getTime()).toString(),
|
27
|
+
}));
|
28
|
+
this.taskRuns.push(...taskRuns);
|
29
|
+
}
|
30
|
+
async endCommand() {
|
31
|
+
await (0, task_history_1.writeTaskRunsToHistory)(this.taskRuns);
|
32
|
+
const history = await (0, task_history_1.getHistoryForHashes)(this.taskRuns.map((t) => t.hash));
|
33
|
+
const flakyTasks = [];
|
34
|
+
// check if any hash has different exit codes => flaky
|
35
|
+
for (let hash in history) {
|
36
|
+
if (history[hash].length > 1 &&
|
37
|
+
history[hash].some((run) => run.code !== history[hash][0].code)) {
|
38
|
+
flakyTasks.push((0, serialize_target_1.serializeTarget)(history[hash][0].project, history[hash][0].target, history[hash][0].configuration));
|
39
|
+
}
|
40
|
+
}
|
41
|
+
if (flakyTasks.length > 0) {
|
42
|
+
output_1.output.warn({
|
43
|
+
title: `Nx detected ${flakyTasks.length === 1 ? 'a flaky task' : ' flaky tasks'}`,
|
44
|
+
bodyLines: [
|
45
|
+
,
|
46
|
+
...flakyTasks.map((t) => ` ${t}`),
|
47
|
+
'',
|
48
|
+
`Flaky tasks can disrupt your CI pipeline. Automatically retry them with Nx Cloud. Learn more at https://nx.dev/ci/features/flaky-tasks`,
|
49
|
+
],
|
50
|
+
});
|
51
|
+
}
|
52
|
+
}
|
53
|
+
}
|
54
|
+
exports.TaskHistoryLifeCycle = TaskHistoryLifeCycle;
|
@@ -14,6 +14,7 @@ const dynamic_run_many_terminal_output_life_cycle_1 = require("./life-cycles/dyn
|
|
14
14
|
const task_profiling_life_cycle_1 = require("./life-cycles/task-profiling-life-cycle");
|
15
15
|
const is_ci_1 = require("../utils/is-ci");
|
16
16
|
const dynamic_run_one_terminal_output_life_cycle_1 = require("./life-cycles/dynamic-run-one-terminal-output-life-cycle");
|
17
|
+
const nx_json_1 = require("../config/nx-json");
|
17
18
|
const create_task_graph_1 = require("./create-task-graph");
|
18
19
|
const task_graph_utils_1 = require("./task-graph-utils");
|
19
20
|
const params_1 = require("../utils/params");
|
@@ -21,6 +22,8 @@ const hash_task_1 = require("../hasher/hash-task");
|
|
21
22
|
const client_1 = require("../daemon/client/client");
|
22
23
|
const store_run_information_life_cycle_1 = require("./life-cycles/store-run-information-life-cycle");
|
23
24
|
const create_task_hasher_1 = require("../hasher/create-task-hasher");
|
25
|
+
const task_history_life_cycle_1 = require("./life-cycles/task-history-life-cycle");
|
26
|
+
const nx_cloud_utils_1 = require("../utils/nx-cloud-utils");
|
24
27
|
async function getTerminalOutputLifeCycle(initiatingProject, projectNames, tasks, nxArgs, nxJson, overrides) {
|
25
28
|
const { runnerOptions } = getRunner(nxArgs, nxJson);
|
26
29
|
const isRunOne = initiatingProject != null;
|
@@ -203,6 +206,9 @@ function constructLifeCycles(lifeCycle) {
|
|
203
206
|
if (process.env.NX_PROFILE) {
|
204
207
|
lifeCycles.push(new task_profiling_life_cycle_1.TaskProfilingLifeCycle(process.env.NX_PROFILE));
|
205
208
|
}
|
209
|
+
if (!(0, nx_cloud_utils_1.isNxCloudUsed)((0, nx_json_1.readNxJson)())) {
|
210
|
+
lifeCycles.push(new task_history_life_cycle_1.TaskHistoryLifeCycle());
|
211
|
+
}
|
206
212
|
return lifeCycles;
|
207
213
|
}
|
208
214
|
function mergeTargetDependencies(defaults, deps) {
|
@@ -88,7 +88,7 @@ class TaskOrchestrator {
|
|
88
88
|
if (!task.hash) {
|
89
89
|
await (0, hash_task_1.hashTask)(this.hasher, this.projectGraph, this.taskGraph, task, taskSpecificEnv);
|
90
90
|
}
|
91
|
-
this.options.lifeCycle.scheduleTask(task);
|
91
|
+
await this.options.lifeCycle.scheduleTask(task);
|
92
92
|
return taskSpecificEnv;
|
93
93
|
}
|
94
94
|
async processScheduledBatch(batch) {
|
@@ -96,7 +96,7 @@ class TaskOrchestrator {
|
|
96
96
|
if (!task.hash) {
|
97
97
|
await (0, hash_task_1.hashTask)(this.hasher, this.projectGraph, this.taskGraph, task, this.batchEnv);
|
98
98
|
}
|
99
|
-
this.options.lifeCycle.scheduleTask(task);
|
99
|
+
await this.options.lifeCycle.scheduleTask(task);
|
100
100
|
}));
|
101
101
|
}
|
102
102
|
processAllScheduledTasks() {
|
@@ -312,7 +312,7 @@ class TaskOrchestrator {
|
|
312
312
|
// endregion Single Task
|
313
313
|
// region Lifecycle
|
314
314
|
async preRunSteps(tasks, metadata) {
|
315
|
-
this.options.lifeCycle.startTasks(tasks, metadata);
|
315
|
+
await this.options.lifeCycle.startTasks(tasks, metadata);
|
316
316
|
}
|
317
317
|
async postRunSteps(tasks, results, doNotSkipCache, { groupId }) {
|
318
318
|
for (const task of tasks) {
|
@@ -342,7 +342,7 @@ class TaskOrchestrator {
|
|
342
342
|
perf_hooks_1.performance.mark('cache-results-end');
|
343
343
|
perf_hooks_1.performance.measure('cache-results', 'cache-results-start', 'cache-results-end');
|
344
344
|
}
|
345
|
-
this.options.lifeCycle.endTasks(results.map((result) => {
|
345
|
+
await this.options.lifeCycle.endTasks(results.map((result) => {
|
346
346
|
const code = result.status === 'success' ||
|
347
347
|
result.status === 'local-cache' ||
|
348
348
|
result.status === 'local-cache-kept-existing' ||
|
@@ -0,0 +1 @@
|
|
1
|
+
export declare function serializeTarget(project: any, target: any, configuration: any): string;
|
@@ -0,0 +1,7 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.serializeTarget = void 0;
|
4
|
+
function serializeTarget(project, target, configuration) {
|
5
|
+
return [project, target, configuration].filter((part) => !!part).join(':');
|
6
|
+
}
|
7
|
+
exports.serializeTarget = serializeTarget;
|
@@ -0,0 +1,8 @@
|
|
1
|
+
declare const taskRunKeys: readonly ["project", "target", "configuration", "hash", "code", "status", "start", "end"];
|
2
|
+
export type TaskRun = Record<(typeof taskRunKeys)[number], string>;
|
3
|
+
export declare function getHistoryForHashes(hashes: string[]): Promise<{
|
4
|
+
[hash: string]: TaskRun[];
|
5
|
+
}>;
|
6
|
+
export declare function writeTaskRunsToHistory(taskRuns: TaskRun[]): Promise<void>;
|
7
|
+
export declare const taskHistoryFile: string;
|
8
|
+
export {};
|
@@ -0,0 +1,97 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.taskHistoryFile = exports.writeTaskRunsToHistory = exports.getHistoryForHashes = void 0;
|
4
|
+
const fs_1 = require("fs");
|
5
|
+
const path_1 = require("path");
|
6
|
+
const client_1 = require("../daemon/client/client");
|
7
|
+
const is_on_daemon_1 = require("../daemon/is-on-daemon");
|
8
|
+
const cache_directory_1 = require("./cache-directory");
|
9
|
+
const taskRunKeys = [
|
10
|
+
'project',
|
11
|
+
'target',
|
12
|
+
'configuration',
|
13
|
+
'hash',
|
14
|
+
'code',
|
15
|
+
'status',
|
16
|
+
'start',
|
17
|
+
'end',
|
18
|
+
];
|
19
|
+
let taskHistory = undefined;
|
20
|
+
let taskHashToIndicesMap = new Map();
|
21
|
+
async function getHistoryForHashes(hashes) {
|
22
|
+
if ((0, is_on_daemon_1.isOnDaemon)() || !client_1.daemonClient.enabled()) {
|
23
|
+
if (taskHistory === undefined) {
|
24
|
+
loadTaskHistoryFromDisk();
|
25
|
+
}
|
26
|
+
const result = {};
|
27
|
+
for (let hash of hashes) {
|
28
|
+
const indices = taskHashToIndicesMap.get(hash);
|
29
|
+
if (!indices) {
|
30
|
+
result[hash] = [];
|
31
|
+
}
|
32
|
+
else {
|
33
|
+
result[hash] = indices.map((index) => taskHistory[index]);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
return result;
|
37
|
+
}
|
38
|
+
return await client_1.daemonClient.getTaskHistoryForHashes(hashes);
|
39
|
+
}
|
40
|
+
exports.getHistoryForHashes = getHistoryForHashes;
|
41
|
+
async function writeTaskRunsToHistory(taskRuns) {
|
42
|
+
if ((0, is_on_daemon_1.isOnDaemon)() || !client_1.daemonClient.enabled()) {
|
43
|
+
if (taskHistory === undefined) {
|
44
|
+
loadTaskHistoryFromDisk();
|
45
|
+
}
|
46
|
+
const serializedLines = [];
|
47
|
+
for (let taskRun of taskRuns) {
|
48
|
+
const serializedLine = taskRunKeys.map((key) => taskRun[key]).join(',');
|
49
|
+
serializedLines.push(serializedLine);
|
50
|
+
recordTaskRunInMemory(taskRun);
|
51
|
+
}
|
52
|
+
if (!(0, fs_1.existsSync)(exports.taskHistoryFile)) {
|
53
|
+
(0, fs_1.writeFileSync)(exports.taskHistoryFile, `${taskRunKeys.join(',')}\n`);
|
54
|
+
}
|
55
|
+
(0, fs_1.appendFileSync)(exports.taskHistoryFile, serializedLines.join('\n') + '\n');
|
56
|
+
}
|
57
|
+
else {
|
58
|
+
await client_1.daemonClient.writeTaskRunsToHistory(taskRuns);
|
59
|
+
}
|
60
|
+
}
|
61
|
+
exports.writeTaskRunsToHistory = writeTaskRunsToHistory;
|
62
|
+
exports.taskHistoryFile = (0, path_1.join)(cache_directory_1.workspaceDataDirectory, 'task-history.csv');
|
63
|
+
function loadTaskHistoryFromDisk() {
|
64
|
+
taskHashToIndicesMap.clear();
|
65
|
+
taskHistory = [];
|
66
|
+
if (!(0, fs_1.existsSync)(exports.taskHistoryFile)) {
|
67
|
+
return;
|
68
|
+
}
|
69
|
+
const fileContent = (0, fs_1.readFileSync)(exports.taskHistoryFile, 'utf8');
|
70
|
+
if (!fileContent) {
|
71
|
+
return;
|
72
|
+
}
|
73
|
+
const lines = fileContent.split('\n');
|
74
|
+
// if there are no lines or just the header, return
|
75
|
+
if (lines.length <= 1) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
const contentLines = lines.slice(1).filter((l) => l.trim() !== '');
|
79
|
+
// read the values from csv format where each header is a key and the value is the value
|
80
|
+
for (let line of contentLines) {
|
81
|
+
const values = line.trim().split(',');
|
82
|
+
const run = {};
|
83
|
+
taskRunKeys.forEach((header, index) => {
|
84
|
+
run[header] = values[index];
|
85
|
+
});
|
86
|
+
recordTaskRunInMemory(run);
|
87
|
+
}
|
88
|
+
}
|
89
|
+
function recordTaskRunInMemory(taskRun) {
|
90
|
+
const index = taskHistory.push(taskRun) - 1;
|
91
|
+
if (taskHashToIndicesMap.has(taskRun.hash)) {
|
92
|
+
taskHashToIndicesMap.get(taskRun.hash).push(index);
|
93
|
+
}
|
94
|
+
else {
|
95
|
+
taskHashToIndicesMap.set(taskRun.hash, [index]);
|
96
|
+
}
|
97
|
+
}
|