nx 22.7.10 → 22.7.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/dist/src/daemon/client/client.d.ts +2 -0
  2. package/dist/src/daemon/client/client.js +73 -7
  3. package/dist/src/daemon/client/daemon-environment.d.ts +11 -0
  4. package/dist/src/daemon/client/daemon-environment.js +20 -0
  5. package/dist/src/daemon/client/daemon-socket-messenger.d.ts +1 -1
  6. package/dist/src/daemon/client/daemon-socket-messenger.js +8 -3
  7. package/dist/src/daemon/is-v8-serializer-enabled.d.ts +2 -2
  8. package/dist/src/daemon/is-v8-serializer-enabled.js +2 -2
  9. package/dist/src/daemon/server/client-socket-context.d.ts +1 -1
  10. package/dist/src/daemon/server/client-socket-context.js +2 -3
  11. package/dist/src/daemon/server/handle-hash-tasks.js +1 -1
  12. package/dist/src/daemon/server/project-graph-incremental-recomputation.d.ts +1 -4
  13. package/dist/src/daemon/server/project-graph-incremental-recomputation.js +1 -14
  14. package/dist/src/daemon/server/server.js +17 -24
  15. package/dist/src/daemon/server/shutdown-utils.d.ts +1 -1
  16. package/dist/src/daemon/server/shutdown-utils.js +2 -2
  17. package/dist/src/daemon/socket-utils.d.ts +26 -7
  18. package/dist/src/daemon/socket-utils.js +45 -31
  19. package/dist/src/hasher/create-task-hasher.js +1 -1
  20. package/dist/src/native/nx.wasm32-wasi.debug.wasm +0 -0
  21. package/dist/src/native/nx.wasm32-wasi.wasm +0 -0
  22. package/dist/src/project-graph/build-project-graph.d.ts +2 -4
  23. package/dist/src/project-graph/build-project-graph.js +2 -7
  24. package/dist/src/project-graph/file-map-utils.d.ts +2 -4
  25. package/dist/src/project-graph/file-map-utils.js +0 -3
  26. package/dist/src/project-graph/plugins/isolation/isolated-plugin.js +18 -2
  27. package/dist/src/project-graph/plugins/isolation/messaging.js +1 -3
  28. package/dist/src/project-graph/plugins/isolation/plugin-worker.js +9 -1
  29. package/dist/src/project-graph/project-graph.js +4 -4
  30. package/dist/src/project-graph/utils/retrieve-workspace-files.d.ts +1 -2
  31. package/dist/src/project-graph/utils/retrieve-workspace-files.js +1 -3
  32. package/dist/src/tasks-runner/forked-process-task-runner.d.ts +3 -0
  33. package/dist/src/tasks-runner/forked-process-task-runner.js +15 -6
  34. package/dist/src/tasks-runner/init-tasks-runner.js +32 -2
  35. package/dist/src/tasks-runner/life-cycles/task-history-life-cycle.js +16 -11
  36. package/dist/src/tasks-runner/pseudo-ipc.js +14 -13
  37. package/dist/src/tasks-runner/running-tasks/batch-process.js +3 -0
  38. package/dist/src/tasks-runner/running-tasks/shared-running-task.d.ts +1 -0
  39. package/dist/src/tasks-runner/running-tasks/shared-running-task.js +12 -1
  40. package/dist/src/tasks-runner/task-orchestrator.d.ts +3 -0
  41. package/dist/src/tasks-runner/task-orchestrator.js +40 -6
  42. package/dist/src/utils/consume-messages-from-socket.d.ts +42 -7
  43. package/dist/src/utils/consume-messages-from-socket.js +272 -38
  44. package/dist/src/utils/params.js +1 -1
  45. package/package.json +11 -11
@@ -4,7 +4,9 @@ exports.getPluginOsSocketPath = exports.getForkedProcessOsSocketPath = exports.g
4
4
  exports.getPluginSocketFileName = getPluginSocketFileName;
5
5
  exports.killSocketOrPath = killSocketOrPath;
6
6
  exports.serializeResult = serializeResult;
7
+ exports.serializeWithFallback = serializeWithFallback;
7
8
  exports.serialize = serialize;
9
+ exports.sendMessage = sendMessage;
8
10
  const fs_1 = require("fs");
9
11
  const os_1 = require("os");
10
12
  const path_1 = require("path");
@@ -12,6 +14,7 @@ const tmp_dir_1 = require("./tmp-dir");
12
14
  const serializable_error_1 = require("../utils/serializable-error");
13
15
  const is_v8_serializer_enabled_1 = require("./is-v8-serializer-enabled");
14
16
  const v8_1 = require("v8");
17
+ const consume_messages_from_socket_1 = require("../utils/consume-messages-from-socket");
15
18
  exports.isWindows = (0, os_1.platform)() === 'win32';
16
19
  /**
17
20
  * For IPC with the daemon server we use unix sockets or windows named pipes, depending on the user's operating system.
@@ -77,41 +80,52 @@ function serializeResult(error, serializedProjectGraph, serializedSourceMaps) {
77
80
  // We do not want to repeat work `JSON.stringify`ing an object containing the potentially large project graph so merge as strings
78
81
  return `{ "error": ${JSON.stringify(error ? (0, serializable_error_1.createSerializableError)(error) : error)}, "projectGraph": ${serializedProjectGraph}, "sourceMaps": ${serializedSourceMaps} }`;
79
82
  }
83
+ function serializeAs(data, format) {
84
+ return format === 'v8'
85
+ ? (0, v8_1.serialize)(data)
86
+ : Buffer.from(JSON.stringify(data), 'utf8');
87
+ }
80
88
  /**
81
- * Helper to serialize data either using v8 serialization or JSON serialization, based on
82
- * the user's preference and the success of each method. Should only be used by "client" side
83
- * connections, daemon or other servers should respond based on the type of serialization used
84
- * by the client it is communicating with.
89
+ * Serialize using `preferred`, falling back to the other format when it throws.
90
+ * Neither format subsumes the other: JSON cannot represent a BigInt and hits the
91
+ * max string length far sooner, while v8 cannot clone a function.
85
92
  *
86
93
  * @param data Data to serialize
87
- * @param force Forces one serialization method over the other
88
- * @returns Serialized data as a string
94
+ * @param preferred Format to attempt first
95
+ * @returns Serialized data as bytes ready to be framed onto a socket
89
96
  */
90
- function serialize(data, force) {
91
- if (force === 'v8' || (0, is_v8_serializer_enabled_1.isV8SerializerEnabled)()) {
92
- try {
93
- return (0, v8_1.serialize)(data).toString('binary');
94
- }
95
- catch (e) {
96
- if (force !== 'v8') {
97
- console.warn(`Data could not be serialized using v8 serialization: ${e}. Falling back to JSON serialization.`);
98
- // Fall back to JSON serialization
99
- return JSON.stringify(data);
100
- }
101
- throw e;
102
- }
97
+ function serializeWithFallback(data, preferred) {
98
+ try {
99
+ return serializeAs(data, preferred);
103
100
  }
104
- else {
105
- try {
106
- return JSON.stringify(data);
107
- }
108
- catch (e) {
109
- if (force !== 'json') {
110
- // Fall back to v8 serialization
111
- console.warn(`Data could not be serialized using JSON.stringify: ${e}. Falling back to v8 serialization.`);
112
- return (0, v8_1.serialize)(data).toString('binary');
113
- }
114
- throw e;
115
- }
101
+ catch (e) {
102
+ const fallback = preferred === 'v8' ? 'json' : 'v8';
103
+ console.warn(`Data could not be serialized using ${preferred} serialization: ${e}. Falling back to ${fallback} serialization.`);
104
+ return serializeAs(data, fallback);
116
105
  }
117
106
  }
107
+ /**
108
+ * Serialize data for IPC using the format the user configured.
109
+ *
110
+ * @param data Data to serialize
111
+ * @param force Use this format without falling back. For callers whose data is
112
+ * known to be unrepresentable in the other format, where a fallback
113
+ * would only swap one failure for a less obvious one.
114
+ * @returns Serialized data as bytes ready to be framed onto a socket
115
+ */
116
+ function serialize(data, force) {
117
+ return force
118
+ ? serializeAs(data, force)
119
+ : serializeWithFallback(data, (0, is_v8_serializer_enabled_1.isV8SerializerEnabled)() ? 'v8' : 'json');
120
+ }
121
+ /**
122
+ * Serialize `data` and write it as one framed message.
123
+ *
124
+ * Lives here rather than in `writeMessage` so the framing stays a byte-level
125
+ * primitive: `utils/consume-messages-from-socket` is shared by callers that
126
+ * already hold bytes, and having it reach back into the daemon's serializer
127
+ * would invert the dependency.
128
+ */
129
+ function sendMessage(socket, data, force, callback) {
130
+ (0, consume_messages_from_socket_1.writeMessage)(socket, serialize(data, force), callback);
131
+ }
@@ -9,7 +9,7 @@ function createTaskHasher(projectGraph, nxJson, runnerOptions) {
9
9
  return new task_hasher_1.DaemonBasedTaskHasher(client_1.daemonClient, runnerOptions);
10
10
  }
11
11
  else {
12
- const { fileMap, allWorkspaceFiles, rustReferences } = (0, build_project_graph_1.getFileMap)();
12
+ const { rustReferences } = (0, build_project_graph_1.getFileMap)();
13
13
  return new task_hasher_1.InProcessTaskHasher(projectGraph, nxJson, rustReferences, runnerOptions);
14
14
  }
15
15
  }
Binary file
@@ -2,18 +2,16 @@ import { FileMap, ProjectGraph, ProjectGraphExternalNode } from '../config/proje
2
2
  import { ProjectConfiguration } from '../config/workspace-json-project-json';
3
3
  import { NxWorkspaceFilesExternals } from '../native';
4
4
  import { CreateMetadataError } from './error-types';
5
- import { FileData } from './file-utils';
6
5
  import { FileMapCache } from './nx-deps-cache';
7
6
  import { CreateMetadataContext } from './plugins';
8
7
  import type { LoadedNxPlugin } from './plugins/loaded-nx-plugin';
9
8
  import type { ConfigurationSourceMaps } from './utils/project-configuration/source-maps';
10
9
  export declare function getFileMap(): {
11
10
  fileMap: FileMap;
12
- allWorkspaceFiles: FileData[];
13
11
  rustReferences: NxWorkspaceFilesExternals | null;
14
12
  };
15
- export declare function hydrateFileMap(fileMap: FileMap, allWorkspaceFiles: FileData[], rustReferences: NxWorkspaceFilesExternals): void;
16
- export declare function buildProjectGraphUsingProjectFileMap(projectRootMap: Record<string, ProjectConfiguration>, externalNodes: Record<string, ProjectGraphExternalNode>, fileMap: FileMap, allWorkspaceFiles: FileData[], rustReferences: NxWorkspaceFilesExternals, fileMapCache: FileMapCache | null, plugins: LoadedNxPlugin[], sourceMap: ConfigurationSourceMaps): Promise<{
13
+ export declare function hydrateFileMap(fileMap: FileMap, rustReferences: NxWorkspaceFilesExternals): void;
14
+ export declare function buildProjectGraphUsingProjectFileMap(projectRootMap: Record<string, ProjectConfiguration>, externalNodes: Record<string, ProjectGraphExternalNode>, fileMap: FileMap, rustReferences: NxWorkspaceFilesExternals, fileMapCache: FileMapCache | null, plugins: LoadedNxPlugin[], sourceMap: ConfigurationSourceMaps): Promise<{
17
15
  projectGraph: ProjectGraph;
18
16
  projectFileMapCache: FileMapCache;
19
17
  }>;
@@ -23,13 +23,11 @@ const implicit_project_dependencies_1 = require("./utils/implicit-project-depend
23
23
  const normalize_project_nodes_1 = require("./utils/normalize-project-nodes");
24
24
  const target_merging_1 = require("./utils/project-configuration/target-merging");
25
25
  let storedFileMap = null;
26
- let storedAllWorkspaceFiles = null;
27
26
  let storedRustReferences = null;
28
27
  function getFileMap() {
29
28
  if (!!storedFileMap) {
30
29
  return {
31
30
  fileMap: storedFileMap,
32
- allWorkspaceFiles: storedAllWorkspaceFiles,
33
31
  rustReferences: storedRustReferences,
34
32
  };
35
33
  }
@@ -39,19 +37,16 @@ function getFileMap() {
39
37
  nonProjectFiles: [],
40
38
  projectFileMap: {},
41
39
  },
42
- allWorkspaceFiles: [],
43
40
  rustReferences: null,
44
41
  };
45
42
  }
46
43
  }
47
- function hydrateFileMap(fileMap, allWorkspaceFiles, rustReferences) {
44
+ function hydrateFileMap(fileMap, rustReferences) {
48
45
  storedFileMap = fileMap;
49
- storedAllWorkspaceFiles = allWorkspaceFiles;
50
46
  storedRustReferences = rustReferences;
51
47
  }
52
- async function buildProjectGraphUsingProjectFileMap(projectRootMap, externalNodes, fileMap, allWorkspaceFiles, rustReferences, fileMapCache, plugins, sourceMap) {
48
+ async function buildProjectGraphUsingProjectFileMap(projectRootMap, externalNodes, fileMap, rustReferences, fileMapCache, plugins, sourceMap) {
53
49
  storedFileMap = fileMap;
54
- storedAllWorkspaceFiles = allWorkspaceFiles;
55
50
  storedRustReferences = rustReferences;
56
51
  const projects = {};
57
52
  for (const root in projectRootMap) {
@@ -1,8 +1,7 @@
1
- import { FileData, FileMap, ProjectFileMap, ProjectGraph } from '../config/project-graph';
2
- import { ProjectConfiguration, ProjectsConfigurations } from '../config/workspace-json-project-json';
1
+ import type { FileData, FileMap, ProjectFileMap, ProjectGraph } from '../config/project-graph';
2
+ import type { ProjectConfiguration, ProjectsConfigurations } from '../config/workspace-json-project-json';
3
3
  import { NxWorkspaceFilesExternals } from '../native';
4
4
  export interface WorkspaceFileMap {
5
- allWorkspaceFiles: FileData[];
6
5
  fileMap: FileMap;
7
6
  }
8
7
  export declare function createProjectFileMapUsingProjectGraph(graph: ProjectGraph): Promise<ProjectFileMap>;
@@ -10,6 +9,5 @@ export declare function createFileMapUsingProjectGraph(graph: ProjectGraph): Pro
10
9
  export declare function createFileMap(projectsConfigurations: ProjectsConfigurations, allWorkspaceFiles: FileData[]): WorkspaceFileMap;
11
10
  export declare function updateFileMap(projectsConfigurations: Record<string, ProjectConfiguration>, rustReferences: NxWorkspaceFilesExternals, updatedFiles: Record<string, string>, deletedFiles: string[]): {
12
11
  fileMap: import("../native").FileMap;
13
- allWorkspaceFiles: FileData[];
14
12
  rustReferences: NxWorkspaceFilesExternals;
15
13
  };
@@ -7,7 +7,6 @@ exports.updateFileMap = updateFileMap;
7
7
  const workspace_context_1 = require("../utils/workspace-context");
8
8
  const workspace_root_1 = require("../utils/workspace-root");
9
9
  const project_graph_1 = require("./project-graph");
10
- const build_all_workspace_files_1 = require("./utils/build-all-workspace-files");
11
10
  const find_project_for_path_1 = require("./utils/find-project-for-path");
12
11
  async function createProjectFileMapUsingProjectGraph(graph) {
13
12
  return (await createFileMapUsingProjectGraph(graph)).fileMap.projectFileMap;
@@ -38,7 +37,6 @@ function createFileMap(projectsConfigurations, allWorkspaceFiles) {
38
37
  }
39
38
  }
40
39
  return {
41
- allWorkspaceFiles,
42
40
  fileMap: {
43
41
  projectFileMap,
44
42
  nonProjectFiles,
@@ -49,7 +47,6 @@ function updateFileMap(projectsConfigurations, rustReferences, updatedFiles, del
49
47
  const updates = (0, workspace_context_1.updateProjectFiles)(Object.fromEntries((0, find_project_for_path_1.createProjectRootMappingsFromProjectConfigurations)(projectsConfigurations)), rustReferences, updatedFiles, deletedFiles);
50
48
  return {
51
49
  fileMap: updates.fileMap,
52
- allWorkspaceFiles: (0, build_all_workspace_files_1.buildAllWorkspaceFiles)(updates.fileMap.projectFileMap, updates.fileMap.nonProjectFiles),
53
50
  rustReferences: updates.externalReferences,
54
51
  };
55
52
  }
@@ -52,7 +52,17 @@ class IsolatedPlugin {
52
52
  this.responseHandlers = new Map();
53
53
  this.exitHandler = null;
54
54
  this.handleSocketData = (raw) => {
55
- const message = (0, consume_messages_from_socket_1.parseMessage)(raw);
55
+ let message;
56
+ try {
57
+ message = (0, consume_messages_from_socket_1.parseMessage)(raw);
58
+ }
59
+ catch (e) {
60
+ // Runs inside a synchronous socket 'data' callback, so a throw here
61
+ // becomes an uncaughtException in the host process rather than a failed
62
+ // plugin call.
63
+ logger_2.logger.error(`[plugin-client] "${this.name}" sent a message that could not be parsed: ${e instanceof Error ? e.message : e}\nReceived: ${(0, consume_messages_from_socket_1.describeMessage)(raw)}`);
64
+ return;
65
+ }
56
66
  if ((0, messaging_1.isPluginWorkerNotification)(message)) {
57
67
  handlePluginWorkerNotification(message);
58
68
  return;
@@ -94,7 +104,13 @@ class IsolatedPlugin {
94
104
  this.responseHandlers.clear();
95
105
  };
96
106
  worker.on('exit', this.exitHandler);
97
- socket.on('data', (0, consume_messages_from_socket_1.consumeMessagesFromSocket)(this.handleSocketData));
107
+ socket.on('data', (0, consume_messages_from_socket_1.consumeMessagesFromSocket)(this.handleSocketData, (err) => {
108
+ // Nothing else settles the pending hook promises on a framing
109
+ // failure, so surface it the same way a dead worker would.
110
+ this.exitHandler?.();
111
+ socket.destroy();
112
+ console.error(err.message);
113
+ }));
98
114
  return this.sendLoadMessage();
99
115
  }
100
116
  /**
@@ -6,7 +6,6 @@ exports.isPluginWorkerResult = isPluginWorkerResult;
6
6
  exports.consumeMessage = consumeMessage;
7
7
  exports.sendMessageOverSocket = sendMessageOverSocket;
8
8
  const socket_utils_1 = require("../../../daemon/socket-utils");
9
- const consume_messages_from_socket_1 = require("../../../utils/consume-messages-from-socket");
10
9
  const workspace_root_1 = require("../../../utils/workspace-root");
11
10
  const NOTIFICATION_TYPES = [
12
11
  'emitLog',
@@ -84,6 +83,5 @@ async function consumeMessage(socket, raw, handlers) {
84
83
  function sendMessageOverSocket(socket, message) {
85
84
  // Stamped here rather than in each constructor, mirroring the daemon.
86
85
  message.workspaceRoot = workspace_root_1.workspaceRoot;
87
- socket.write((0, socket_utils_1.serialize)(message));
88
- socket.write(consume_messages_from_socket_1.MESSAGE_END_SEQ);
86
+ (0, socket_utils_1.sendMessage)(socket, message);
89
87
  }
@@ -87,7 +87,8 @@ const server = (0, net_1.createServer)((socket) => {
87
87
  exclude: plugin.exclude,
88
88
  createNodesPattern: plugin.createNodes?.[0],
89
89
  hasCreateDependencies: 'createDependencies' in plugin && !!plugin.createDependencies,
90
- hasProcessProjectGraph: 'processProjectGraph' in plugin && !!plugin.processProjectGraph,
90
+ hasProcessProjectGraph: 'processProjectGraph' in plugin &&
91
+ !!plugin.processProjectGraph,
91
92
  hasCreateMetadata: 'createMetadata' in plugin && !!plugin.createMetadata,
92
93
  hasPreTasksExecution: 'preTasksExecution' in plugin && !!plugin.preTasksExecution,
93
94
  hasPostTasksExecution: 'postTasksExecution' in plugin && !!plugin.postTasksExecution,
@@ -118,6 +119,13 @@ const server = (0, net_1.createServer)((socket) => {
118
119
  }
119
120
  }),
120
121
  });
122
+ }, (err) => {
123
+ // The host link cannot resynchronize. Exit so the host's 'exit' handler
124
+ // rejects the pending hooks instead of waiting on a reply that the
125
+ // framing failure guarantees will never arrive.
126
+ console.error(err.message);
127
+ socket.destroy();
128
+ process.exit(1);
121
129
  }));
122
130
  // When the host disconnects, clean up and exit.
123
131
  socket.on('end', () => {
@@ -104,14 +104,14 @@ async function buildProjectGraphAndSourceMapsWithoutDaemon() {
104
104
  const { projects, externalNodes, sourceMaps, projectRootMap } = configurationResult;
105
105
  perf_hooks_1.performance.mark('retrieve-project-configurations:end');
106
106
  perf_hooks_1.performance.mark('retrieve-workspace-files:start');
107
- const { allWorkspaceFiles, fileMap, rustReferences } = await (0, retrieve_workspace_files_1.retrieveWorkspaceFiles)(workspace_root_1.workspaceRoot, projectRootMap);
107
+ const { fileMap, rustReferences } = await (0, retrieve_workspace_files_1.retrieveWorkspaceFiles)(workspace_root_1.workspaceRoot, projectRootMap);
108
108
  perf_hooks_1.performance.mark('retrieve-workspace-files:end');
109
109
  const cacheEnabled = process.env.NX_CACHE_PROJECT_GRAPH !== 'false';
110
110
  perf_hooks_1.performance.mark('build-project-graph-using-project-file-map:start');
111
111
  let projectGraphError;
112
112
  let projectGraphResult;
113
113
  try {
114
- projectGraphResult = await (0, build_project_graph_1.buildProjectGraphUsingProjectFileMap)(projects, externalNodes, fileMap, allWorkspaceFiles, rustReferences, cacheEnabled ? (0, nx_deps_cache_1.readFileMapCache)() : null, plugins, sourceMaps);
114
+ projectGraphResult = await (0, build_project_graph_1.buildProjectGraphUsingProjectFileMap)(projects, externalNodes, fileMap, rustReferences, cacheEnabled ? (0, nx_deps_cache_1.readFileMapCache)() : null, plugins, sourceMaps);
115
115
  }
116
116
  catch (e) {
117
117
  if ((0, error_types_1.isAggregateProjectGraphError)(e)) {
@@ -180,8 +180,8 @@ async function readCachedGraphAndHydrateFileMap(minimumComputedAt) {
180
180
  data.root,
181
181
  project,
182
182
  ]));
183
- const { allWorkspaceFiles, fileMap, rustReferences } = await (0, retrieve_workspace_files_1.retrieveWorkspaceFiles)(workspace_root_1.workspaceRoot, projectRootMap);
184
- (0, build_project_graph_1.hydrateFileMap)(fileMap, allWorkspaceFiles, rustReferences);
183
+ const { fileMap, rustReferences } = await (0, retrieve_workspace_files_1.retrieveWorkspaceFiles)(workspace_root_1.workspaceRoot, projectRootMap);
184
+ (0, build_project_graph_1.hydrateFileMap)(fileMap, rustReferences);
185
185
  return graph;
186
186
  }
187
187
  /**
@@ -3,13 +3,12 @@ import { NxJsonConfiguration } from '../../config/nx-json';
3
3
  import { ConfigurationResult } from './project-configuration-utils';
4
4
  import type { LoadedNxPlugin } from '../plugins/loaded-nx-plugin';
5
5
  /**
6
- * Walks the workspace directory to create the `projectFileMap`, `ProjectConfigurations` and `allWorkspaceFiles`
6
+ * Walks the workspace directory to create the `projectFileMap` and `ProjectConfigurations`
7
7
  * @throws
8
8
  * @param workspaceRoot
9
9
  * @param nxJson
10
10
  */
11
11
  export declare function retrieveWorkspaceFiles(workspaceRoot: string, projectRootMap: Record<string, string>): Promise<{
12
- allWorkspaceFiles: import("../file-utils").FileData[];
13
12
  fileMap: {
14
13
  projectFileMap: Record<string, import("../../native").FileData[]>;
15
14
  nonProjectFiles: import("../../native").FileData[];
@@ -11,11 +11,10 @@ const angular_json_1 = require("../../adapter/angular-json");
11
11
  const nx_json_1 = require("../../config/nx-json");
12
12
  const project_configuration_utils_1 = require("./project-configuration-utils");
13
13
  const workspace_context_1 = require("../../utils/workspace-context");
14
- const build_all_workspace_files_1 = require("./build-all-workspace-files");
15
14
  const path_1 = require("path");
16
15
  const get_plugins_1 = require("../plugins/get-plugins");
17
16
  /**
18
- * Walks the workspace directory to create the `projectFileMap`, `ProjectConfigurations` and `allWorkspaceFiles`
17
+ * Walks the workspace directory to create the `projectFileMap` and `ProjectConfigurations`
19
18
  * @throws
20
19
  * @param workspaceRoot
21
20
  * @param nxJson
@@ -29,7 +28,6 @@ async function retrieveWorkspaceFiles(workspaceRoot, projectRootMap) {
29
28
  perf_hooks_1.performance.mark('get-workspace-files:end');
30
29
  perf_hooks_1.performance.measure('get-workspace-files', 'get-workspace-files:start', 'get-workspace-files:end');
31
30
  return {
32
- allWorkspaceFiles: (0, build_all_workspace_files_1.buildAllWorkspaceFiles)(projectFileMap, globalFiles),
33
31
  fileMap: {
34
32
  projectFileMap,
35
33
  nonProjectFiles: globalFiles,
@@ -11,6 +11,8 @@ export declare class ForkedProcessTaskRunner {
11
11
  cliPath: string;
12
12
  private readonly verbose;
13
13
  private processes;
14
+ private processMessageHandler;
15
+ private processExitHandler;
14
16
  private finishedProcesses;
15
17
  private pseudoTerminals;
16
18
  constructor(options: DefaultTasksRunnerOptions, tuiEnabled: boolean);
@@ -39,4 +41,5 @@ export declare class ForkedProcessTaskRunner {
39
41
  private writeTerminalOutput;
40
42
  cleanup(signal?: NodeJS.Signals): void;
41
43
  private setupProcessEventListeners;
44
+ removeProcessEventListeners(): void;
42
45
  }
@@ -271,7 +271,7 @@ class ForkedProcessTaskRunner {
271
271
  this.cleanUpBatchProcesses();
272
272
  }
273
273
  setupProcessEventListeners() {
274
- const messageHandler = (message) => {
274
+ this.processMessageHandler = (message) => {
275
275
  this.pseudoTerminals.forEach((p) => {
276
276
  p.sendMessageToChildren(message);
277
277
  });
@@ -281,14 +281,23 @@ class ForkedProcessTaskRunner {
281
281
  }
282
282
  });
283
283
  };
284
+ this.processExitHandler = () => {
285
+ this.cleanup();
286
+ process.off('message', this.processMessageHandler);
287
+ };
284
288
  // When the nx process gets a message, it will be sent into the task's process
285
- process.on('message', messageHandler);
289
+ process.on('message', this.processMessageHandler);
286
290
  // Terminate any task processes on exit
287
- process.once('exit', () => {
288
- this.cleanup();
289
- process.off('message', messageHandler);
290
- });
291
+ process.once('exit', this.processExitHandler);
291
292
  // Signal handlers removed - TaskOrchestrator owns signal handling
292
293
  }
294
+ // Long-lived processes (Nx Cloud agents) create a runner per invocation;
295
+ // without this each instance stays reachable from `process` forever.
296
+ // Call only after cleanup(): the exit handler is the last-resort child
297
+ // kill, so it must not be removed while children may still be alive.
298
+ removeProcessEventListeners() {
299
+ process.off('message', this.processMessageHandler);
300
+ process.off('exit', this.processExitHandler);
301
+ }
293
302
  }
294
303
  exports.ForkedProcessTaskRunner = ForkedProcessTaskRunner;
@@ -14,6 +14,7 @@ const life_cycle_1 = require("./life-cycle");
14
14
  const task_orchestrator_1 = require("./task-orchestrator");
15
15
  const create_task_hasher_1 = require("../hasher/create-task-hasher");
16
16
  const client_1 = require("../daemon/client/client");
17
+ const shared_running_task_1 = require("./running-tasks/shared-running-task");
17
18
  const task_results_life_cycle_1 = require("./life-cycles/task-results-life-cycle");
18
19
  /**
19
20
  * This function is deprecated. Do not use this
@@ -110,6 +111,11 @@ async function createOrchestrator(tasks, projectGraph, taskGraphForHashing, nxJs
110
111
  orchestrator.processAllScheduledTasks();
111
112
  return orchestrator;
112
113
  }
114
+ // Nothing awaits the dispose chains below, so an unhandled rejection would take
115
+ // down a long-lived agent process.
116
+ function logDisposeFailure(e) {
117
+ console.error('Failed to dispose the task orchestrator:', e);
118
+ }
113
119
  async function runDiscreteTasks(tasks, projectGraph, taskGraphForHashing, nxJson, lifeCycle) {
114
120
  const orchestrator = await createOrchestrator(tasks, projectGraph, taskGraphForHashing, nxJson, lifeCycle);
115
121
  let groupId = 0;
@@ -138,12 +144,36 @@ async function runDiscreteTasks(tasks, projectGraph, taskGraphForHashing, nxJson
138
144
  return [hit];
139
145
  return [await orchestrator.runTaskDirectly(true, task, groupId++)];
140
146
  });
141
- return [...batchResults, ...taskResults];
147
+ const results = [...batchResults, ...taskResults];
148
+ // Callers like Nx Cloud agents create an orchestrator per invocation in a
149
+ // long-lived process; release its process-level listeners once all tasks
150
+ // settle, otherwise every invocation's orchestrator stays reachable forever.
151
+ // Not awaited, so callers keep consuming handles as they settle; the forked
152
+ // runner's exit handler still reaps children until dispose() runs.
153
+ Promise.allSettled(results)
154
+ .then(() => orchestrator.dispose())
155
+ .catch(logDisposeFailure);
156
+ return results;
142
157
  }
143
158
  async function runContinuousTasks(tasks, projectGraph, taskGraphForHashing, nxJson, lifeCycle) {
144
159
  const orchestrator = await createOrchestrator(tasks, projectGraph, taskGraphForHashing, nxJson, lifeCycle);
145
- return tasks.reduce((current, task, index) => {
160
+ const runningTasks = tasks.reduce((current, task, index) => {
146
161
  current[task.id] = orchestrator.startContinuousTask(task, index);
147
162
  return current;
148
163
  }, {});
164
+ // Unlike runDiscreteTasks, this must resolve at task start: callers keep
165
+ // the RunningTask handles to kill later, so disposal has to be deferred
166
+ // until every task actually exits.
167
+ Promise.allSettled(Object.entries(runningTasks).map(async ([taskId, promise]) => {
168
+ const runningTask = await promise;
169
+ // A shared task is owned by another nx process; this orchestrator has
170
+ // no child to protect for it, so disposal does not wait on it.
171
+ if (runningTask instanceof shared_running_task_1.SharedRunningTask) {
172
+ return;
173
+ }
174
+ await orchestrator.waitForContinuousTaskExit(taskId);
175
+ }))
176
+ .then(() => orchestrator.dispose())
177
+ .catch(logDisposeFailure);
178
+ return runningTasks;
149
179
  }
@@ -22,6 +22,8 @@ function getTasksHistoryLifeCycle() {
22
22
  class TaskHistoryLifeCycle {
23
23
  constructor() {
24
24
  this.startTimings = {};
25
+ // Holds no terminalOutput: this singleton outlives every run in the
26
+ // process, and agents complete thousands of tasks in one process.
25
27
  this.pendingResults = new Map();
26
28
  this.taskRuns = new Map();
27
29
  this.taskHistory = (0, task_history_1.getTaskHistory)();
@@ -36,25 +38,28 @@ class TaskHistoryLifeCycle {
36
38
  }
37
39
  }
38
40
  async endTasks(taskResults) {
39
- for (const taskResult of taskResults) {
40
- this.pendingResults.set(taskResult.task.id, taskResult);
41
+ for (const { task, code, status } of taskResults) {
42
+ this.pendingResults.set(task.id, { task, code, status });
41
43
  }
42
44
  }
43
45
  async endCommand() {
46
+ const pendingResults = Array.from(this.pendingResults.values());
47
+ this.pendingResults.clear();
44
48
  if (!this.taskHistory) {
45
49
  return;
46
50
  }
47
51
  // Build TaskRun objects now — task.hash is guaranteed to be set by this point
48
- for (const [, taskResult] of this.pendingResults) {
49
- this.taskRuns.set(taskResult.task.hash, {
50
- hash: taskResult.task.hash,
51
- target: taskResult.task.target,
52
- code: taskResult.code,
53
- status: taskResult.status,
54
- start: taskResult.task.startTime ?? this.startTimings[taskResult.task.id],
55
- end: taskResult.task.endTime ?? Date.now(),
56
- cacheable: taskResult.task.cache === true,
52
+ for (const { task, code, status } of pendingResults) {
53
+ this.taskRuns.set(task.hash, {
54
+ hash: task.hash,
55
+ target: task.target,
56
+ code,
57
+ status,
58
+ start: task.startTime ?? this.startTimings[task.id],
59
+ end: task.endTime ?? Date.now(),
60
+ cacheable: task.cache === true,
57
61
  });
62
+ delete this.startTimings[task.id];
58
63
  }
59
64
  const runs = [];
60
65
  // Only check for flaky tasks among cacheable tasks
@@ -77,6 +77,14 @@ class PseudoIPCServer {
77
77
  this.childReadyMap.get(childId)();
78
78
  }
79
79
  }
80
+ },
81
+ // Nothing resynchronizes a length-prefixed stream, so report it the
82
+ // same way a socket error is reported and let the peer see the close.
83
+ (err) => {
84
+ for (const childMessage of this.childMessages) {
85
+ childMessage.onError?.(err);
86
+ }
87
+ socket.destroy();
80
88
  }));
81
89
  socket.on('close', () => {
82
90
  for (const childMessage of this.childMessages) {
@@ -91,15 +99,12 @@ class PseudoIPCServer {
91
99
  }
92
100
  sendMessageToChildren(message) {
93
101
  this.sockets.forEach((socket) => {
94
- socket.write((0, socket_utils_1.serialize)({ type: 'TO_CHILDREN_FROM_PARENT', message }));
95
- // send EOT to indicate that the message has been fully written
96
- socket.write(consume_messages_from_socket_1.MESSAGE_END_SEQ);
102
+ (0, socket_utils_1.sendMessage)(socket, { type: 'TO_CHILDREN_FROM_PARENT', message });
97
103
  });
98
104
  }
99
105
  sendMessageToChild(id, message) {
100
106
  this.sockets.forEach((socket) => {
101
- socket.write((0, socket_utils_1.serialize)({ type: 'TO_CHILDREN_FROM_PARENT', id, message }));
102
- socket.write(consume_messages_from_socket_1.MESSAGE_END_SEQ);
107
+ (0, socket_utils_1.sendMessage)(socket, { type: 'TO_CHILDREN_FROM_PARENT', id, message });
103
108
  });
104
109
  }
105
110
  onMessageFromChildren(onMessage, onClose = () => { }, onError = (err) => { }) {
@@ -121,17 +126,13 @@ class PseudoIPCClient {
121
126
  this.socket = (0, net_1.connect)(this.path);
122
127
  }
123
128
  sendMessageToParent(message) {
124
- this.socket.write((0, socket_utils_1.serialize)({ type: 'TO_PARENT_FROM_CHILDREN', message }));
125
- // send EOT to indicate that the message has been fully written
126
- this.socket.write(consume_messages_from_socket_1.MESSAGE_END_SEQ);
129
+ (0, socket_utils_1.sendMessage)(this.socket, { type: 'TO_PARENT_FROM_CHILDREN', message });
127
130
  }
128
131
  notifyChildIsReady(id) {
129
- this.socket.write((0, socket_utils_1.serialize)({
132
+ (0, socket_utils_1.sendMessage)(this.socket, {
130
133
  type: 'CHILD_READY',
131
134
  message: id,
132
- }));
133
- // send EOT to indicate that the message has been fully written
134
- this.socket.write(consume_messages_from_socket_1.MESSAGE_END_SEQ);
135
+ });
135
136
  }
136
137
  onMessageFromParent(forkId, onMessage, onClose = () => { }, onError = (err) => { }) {
137
138
  this.socket.on('data', (0, consume_messages_from_socket_1.consumeMessagesFromSocket)(async (rawMessage) => {
@@ -144,7 +145,7 @@ class PseudoIPCClient {
144
145
  onMessage(message);
145
146
  }
146
147
  }
147
- }));
148
+ }, onError));
148
149
  this.socket.on('close', onClose);
149
150
  this.socket.on('error', onError);
150
151
  return this;
@@ -82,6 +82,9 @@ class BatchProcess {
82
82
  onOutput(cb) {
83
83
  this.outputCallbacks.push(cb);
84
84
  }
85
+ // Resolves on the CompleteBatchExecution message, not process exit: the
86
+ // child can outlive its results (open handles keep it alive), so callers
87
+ // must not treat a settled batch as an exited process.
85
88
  async getResults() {
86
89
  return Promise.race([
87
90
  new Promise((_, rej) => {
@@ -3,6 +3,7 @@ import { RunningTasksService } from '../../native';
3
3
  export declare class SharedRunningTask implements RunningTask {
4
4
  private runningTasksService;
5
5
  private exitCallbacks;
6
+ private detached;
6
7
  constructor(runningTasksService: RunningTasksService, taskId: string);
7
8
  getResults(): Promise<{
8
9
  code: number;
@@ -5,7 +5,11 @@ class SharedRunningTask {
5
5
  constructor(runningTasksService, taskId) {
6
6
  this.runningTasksService = runningTasksService;
7
7
  this.exitCallbacks = [];
8
+ this.detached = false;
8
9
  this.waitForTaskToFinish(taskId).then(() => {
10
+ if (this.detached) {
11
+ return;
12
+ }
9
13
  // notify exit callbacks
10
14
  this.exitCallbacks.forEach((cb) => cb(0));
11
15
  });
@@ -13,8 +17,14 @@ class SharedRunningTask {
13
17
  async getResults() {
14
18
  throw new Error('Results cannot be retrieved from a shared task');
15
19
  }
20
+ // Detaches from the shared task; the owning process keeps running it.
16
21
  kill() {
22
+ if (this.detached) {
23
+ return;
24
+ }
25
+ this.detached = true;
17
26
  this.exitCallbacks.forEach((cb) => cb(0));
27
+ this.exitCallbacks = [];
18
28
  }
19
29
  onExit(cb) {
20
30
  this.exitCallbacks.push(cb);
@@ -24,7 +34,8 @@ class SharedRunningTask {
24
34
  // wait for the running task to finish
25
35
  do {
26
36
  await new Promise((resolve) => setTimeout(resolve, 100));
27
- } while (this.runningTasksService.getRunningTasks([taskId]).length);
37
+ } while (!this.detached &&
38
+ this.runningTasksService.getRunningTasks([taskId]).length);
28
39
  }
29
40
  }
30
41
  exports.SharedRunningTask = SharedRunningTask;