nx 19.4.0-canary.20240626-3a2e8d4 → 19.4.0-canary.20240628-336d371

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 (39) hide show
  1. package/package.json +14 -14
  2. package/src/command-line/connect/connect-to-nx-cloud.d.ts +1 -1
  3. package/src/command-line/connect/connect-to-nx-cloud.js +16 -25
  4. package/src/command-line/graph/graph.d.ts +1 -0
  5. package/src/command-line/graph/graph.js +46 -2
  6. package/src/command-line/run/command-object.js +2 -1
  7. package/src/config/workspace-json-project-json.d.ts +8 -0
  8. package/src/core/graph/main.js +1 -1
  9. package/src/core/graph/styles.css +1 -1
  10. package/src/daemon/socket-utils.d.ts +1 -0
  11. package/src/daemon/socket-utils.js +6 -1
  12. package/src/executors/run-commands/run-commands.impl.d.ts +1 -1
  13. package/src/executors/run-commands/run-commands.impl.js +26 -15
  14. package/src/executors/run-commands/schema.json +14 -0
  15. package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.d.ts +1 -0
  16. package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.js +68 -33
  17. package/src/nx-cloud/generators/connect-to-nx-cloud/schema.json +5 -0
  18. package/src/nx-cloud/utilities/url-shorten.d.ts +2 -1
  19. package/src/nx-cloud/utilities/url-shorten.js +42 -14
  20. package/src/plugins/js/index.js +2 -0
  21. package/src/plugins/js/package-json/create-package-json.js +2 -2
  22. package/src/plugins/js/utils/register.js +38 -0
  23. package/src/plugins/project-json/build-nodes/package-json-next-to-project-json.js +9 -2
  24. package/src/plugins/target-defaults/target-defaults-plugin.d.ts +40 -0
  25. package/src/project-graph/plugins/internal-api.js +1 -1
  26. package/src/project-graph/plugins/isolation/index.d.ts +1 -1
  27. package/src/project-graph/plugins/isolation/index.js +3 -15
  28. package/src/project-graph/plugins/isolation/messaging.d.ts +6 -3
  29. package/src/project-graph/plugins/isolation/messaging.js +9 -3
  30. package/src/project-graph/plugins/isolation/plugin-pool.d.ts +1 -1
  31. package/src/project-graph/plugins/isolation/plugin-pool.js +126 -50
  32. package/src/project-graph/plugins/isolation/plugin-worker.js +128 -107
  33. package/src/tasks-runner/task-env.d.ts +13 -0
  34. package/src/tasks-runner/task-env.js +41 -26
  35. package/src/utils/git-utils.d.ts +1 -1
  36. package/src/utils/git-utils.js +13 -2
  37. package/src/utils/is-ci.js +1 -0
  38. package/src/utils/nx-cloud-utils.d.ts +1 -1
  39. package/src/utils/nx-cloud-utils.js +1 -1
@@ -7,7 +7,45 @@ const swcNodeInstalled = packageIsInstalled('@swc-node/register');
7
7
  const tsNodeInstalled = packageIsInstalled('ts-node/register');
8
8
  let ts;
9
9
  let isTsEsmLoaderRegistered = false;
10
+ /**
11
+ * tsx is a utility to run TypeScript files in node which is growing in popularity:
12
+ * https://tsx.is
13
+ *
14
+ * Behind the scenes it is invoking node with relevant --require and --import flags.
15
+ *
16
+ * If the user is invoking Nx via a script which is being invoked via tsx, then we
17
+ * do not need to register any transpiler at all as the environment will have already
18
+ * been configured by tsx. In fact, registering a transpiler such as ts-node or swc
19
+ * in this case causes issues.
20
+ *
21
+ * Because node is being invoked by tsx, the tsx binary does not end up in the final
22
+ * process.argv and so we need to check a few possible things to account for usage
23
+ * via different package managers (e.g. pnpm does not set process._ to tsx, but rather
24
+ * pnpm itself, modern yarn does not set process._ at all etc.).
25
+ */
26
+ const isInvokedByTsx = (() => {
27
+ if (process.env._?.endsWith(`${path_1.sep}tsx`)) {
28
+ return true;
29
+ }
30
+ const requireArgs = [];
31
+ const importArgs = [];
32
+ (process.execArgv ?? []).forEach((arg, i) => {
33
+ if (arg === '-r' || arg === '--require') {
34
+ requireArgs.push(process.execArgv[i + 1]);
35
+ }
36
+ if (arg === '--import') {
37
+ importArgs.push(process.execArgv[i + 1]);
38
+ }
39
+ });
40
+ const isTsxPath = (p) => p.includes(`${path_1.sep}tsx${path_1.sep}`);
41
+ return (requireArgs.some((a) => isTsxPath(a)) ||
42
+ importArgs.some((a) => isTsxPath(a)));
43
+ })();
10
44
  function registerTsProject(path, configFilename) {
45
+ // See explanation alongside isInvokedByTsx declaration
46
+ if (isInvokedByTsx) {
47
+ return () => { };
48
+ }
11
49
  const tsConfigPath = configFilename ? (0, path_1.join)(path, configFilename) : path;
12
50
  const compilerOptions = readCompilerOptions(tsConfigPath);
13
51
  const cleanupFunctions = [
@@ -5,6 +5,7 @@ const path_1 = require("path");
5
5
  const fs_1 = require("fs");
6
6
  const fileutils_1 = require("../../../utils/fileutils");
7
7
  const package_json_1 = require("../../../utils/package-json");
8
+ const package_json_workspaces_1 = require("../../package-json-workspaces");
8
9
  // TODO: Remove this one day, this should not need to be done.
9
10
  exports.PackageJsonProjectsNextToProjectJsonPlugin = {
10
11
  // Its not a problem if plugins happen to have same name, and this
@@ -27,8 +28,14 @@ exports.PackageJsonProjectsNextToProjectJsonPlugin = {
27
28
  exports.default = exports.PackageJsonProjectsNextToProjectJsonPlugin;
28
29
  function createProjectFromPackageJsonNextToProjectJson(projectJsonPath, workspaceRoot) {
29
30
  const root = (0, path_1.dirname)(projectJsonPath);
30
- const packageJsonPath = (0, path_1.join)(workspaceRoot, root, 'package.json');
31
- if (!(0, fs_1.existsSync)(packageJsonPath)) {
31
+ const relativePackageJsonPath = (0, path_1.join)(root, 'package.json');
32
+ const packageJsonPath = (0, path_1.join)(workspaceRoot, relativePackageJsonPath);
33
+ const readJson = (f) => (0, fileutils_1.readJsonFile)((0, path_1.join)(workspaceRoot, f));
34
+ // Do not create projects for package.json files
35
+ // that are part of the package manager workspaces
36
+ // Those package.json files will be processed later on
37
+ const matcher = (0, package_json_workspaces_1.buildPackageJsonWorkspacesMatcher)(workspaceRoot, readJson);
38
+ if (!(0, fs_1.existsSync)(packageJsonPath) || matcher(relativePackageJsonPath)) {
32
39
  return null;
33
40
  }
34
41
  try {
@@ -17,6 +17,14 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
17
17
  [x: string]: any;
18
18
  description?: string;
19
19
  technologies?: string[];
20
+ nonAtomizedTarget?: string;
21
+ help?: {
22
+ command: string;
23
+ example: {
24
+ options?: Record<string, unknown>;
25
+ args?: string[];
26
+ };
27
+ };
20
28
  };
21
29
  executor?: undefined;
22
30
  options?: undefined;
@@ -31,6 +39,14 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
31
39
  [x: string]: any;
32
40
  description?: string;
33
41
  technologies?: string[];
42
+ nonAtomizedTarget?: string;
43
+ help?: {
44
+ command: string;
45
+ example: {
46
+ options?: Record<string, unknown>;
47
+ args?: string[];
48
+ };
49
+ };
34
50
  };
35
51
  command?: undefined;
36
52
  } | {
@@ -44,6 +60,14 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
44
60
  [x: string]: any;
45
61
  description?: string;
46
62
  technologies?: string[];
63
+ nonAtomizedTarget?: string;
64
+ help?: {
65
+ command: string;
66
+ example: {
67
+ options?: Record<string, unknown>;
68
+ args?: string[];
69
+ };
70
+ };
47
71
  };
48
72
  command?: undefined;
49
73
  } | {
@@ -52,6 +76,14 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
52
76
  [x: string]: any;
53
77
  description?: string;
54
78
  technologies?: string[];
79
+ nonAtomizedTarget?: string;
80
+ help?: {
81
+ command: string;
82
+ example: {
83
+ options?: Record<string, unknown>;
84
+ args?: string[];
85
+ };
86
+ };
55
87
  };
56
88
  command?: undefined;
57
89
  options?: undefined;
@@ -66,6 +98,14 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
66
98
  [x: string]: any;
67
99
  description?: string;
68
100
  technologies?: string[];
101
+ nonAtomizedTarget?: string;
102
+ help?: {
103
+ command: string;
104
+ example: {
105
+ options?: Record<string, unknown>;
106
+ args?: string[];
107
+ };
108
+ };
69
109
  };
70
110
  command?: undefined;
71
111
  } | {
@@ -76,7 +76,7 @@ async function loadNxPlugins(plugins, root = workspace_root_1.workspaceRoot) {
76
76
  plugins = await normalizePlugins(plugins, root);
77
77
  const cleanupFunctions = [];
78
78
  for (const plugin of plugins) {
79
- const [loadedPluginPromise, cleanup] = loadingMethod(plugin, root);
79
+ const [loadedPluginPromise, cleanup] = await loadingMethod(plugin, root);
80
80
  result.push(loadedPluginPromise);
81
81
  cleanupFunctions.push(cleanup);
82
82
  }
@@ -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): readonly [Promise<LoadedNxPlugin>, () => void];
3
+ export declare function loadNxPluginInIsolation(plugin: PluginConfiguration, root?: string): Promise<readonly [Promise<LoadedNxPlugin>, () => void]>;
@@ -3,25 +3,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadNxPluginInIsolation = void 0;
4
4
  const workspace_root_1 = require("../../../utils/workspace-root");
5
5
  const plugin_pool_1 = require("./plugin-pool");
6
- /**
7
- * Used to ensure 1 plugin : 1 worker
8
- */
9
- const remotePluginCache = new Map();
10
- function loadNxPluginInIsolation(plugin, root = workspace_root_1.workspaceRoot) {
11
- const cacheKey = JSON.stringify(plugin);
12
- if (remotePluginCache.has(cacheKey)) {
13
- return remotePluginCache.get(cacheKey);
14
- }
15
- const [loadingPlugin, cleanup] = (0, plugin_pool_1.loadRemoteNxPlugin)(plugin, root);
16
- // We clean up plugin workers when Nx process completes.
17
- const val = [
6
+ async function loadNxPluginInIsolation(plugin, root = workspace_root_1.workspaceRoot) {
7
+ const [loadingPlugin, cleanup] = await (0, plugin_pool_1.loadRemoteNxPlugin)(plugin, root);
8
+ return [
18
9
  loadingPlugin,
19
10
  () => {
20
11
  cleanup();
21
- remotePluginCache.delete(cacheKey);
22
12
  },
23
13
  ];
24
- remotePluginCache.set(cacheKey, val);
25
- return val;
26
14
  }
27
15
  exports.loadNxPluginInIsolation = loadNxPluginInIsolation;
@@ -1,9 +1,11 @@
1
1
  /// <reference types="node" />
2
+ /// <reference types="node" />
2
3
  import { ProjectGraph, ProjectGraphProcessorContext } from '../../../config/project-graph';
3
4
  import { PluginConfiguration } from '../../../config/nx-json';
4
- import { CreateDependenciesContext, CreateMetadataContext, CreateNodesContext } from '../public-api';
5
+ import { CreateDependenciesContext, CreateMetadataContext, CreateNodesContextV2 } from '../public-api';
5
6
  import { LoadedNxPlugin } from '../internal-api';
6
7
  import { Serializable } from 'child_process';
8
+ import { Socket } from 'net';
7
9
  export interface PluginWorkerLoadMessage {
8
10
  type: 'load';
9
11
  payload: {
@@ -31,7 +33,7 @@ export interface PluginWorkerCreateNodesMessage {
31
33
  type: 'createNodes';
32
34
  payload: {
33
35
  configFiles: string[];
34
- context: CreateNodesContext;
36
+ context: CreateNodesContextV2;
35
37
  tx: string;
36
38
  };
37
39
  }
@@ -112,9 +114,10 @@ export declare function isPluginWorkerMessage(message: Serializable): message is
112
114
  export declare function isPluginWorkerResult(message: Serializable): message is PluginWorkerResult;
113
115
  type MaybePromise<T> = T | Promise<T>;
114
116
  type MessageHandlerReturn<T extends PluginWorkerMessage | PluginWorkerResult> = T extends PluginWorkerResult ? MaybePromise<PluginWorkerMessage | void> : MaybePromise<PluginWorkerResult | void>;
115
- export declare function consumeMessage<T extends PluginWorkerMessage | PluginWorkerResult>(raw: T, handlers: {
117
+ export declare function consumeMessage<T extends PluginWorkerMessage | PluginWorkerResult>(socket: Socket, raw: T, handlers: {
116
118
  [K in T['type']]: (payload: Extract<T, {
117
119
  type: K;
118
120
  }>['payload']) => MessageHandlerReturn<T>;
119
121
  }): Promise<void>;
122
+ export declare function sendMessageOverSocket(socket: Socket, message: PluginWorkerMessage | PluginWorkerResult): void;
120
123
  export {};
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.consumeMessage = exports.isPluginWorkerResult = exports.isPluginWorkerMessage = void 0;
3
+ exports.sendMessageOverSocket = exports.consumeMessage = exports.isPluginWorkerResult = exports.isPluginWorkerMessage = void 0;
4
4
  function isPluginWorkerMessage(message) {
5
5
  return (typeof message === 'object' &&
6
6
  'type' in message &&
@@ -10,6 +10,7 @@ function isPluginWorkerMessage(message) {
10
10
  'createNodes',
11
11
  'createDependencies',
12
12
  'processProjectGraph',
13
+ 'createMetadata',
13
14
  ].includes(message.type));
14
15
  }
15
16
  exports.isPluginWorkerMessage = isPluginWorkerMessage;
@@ -22,19 +23,24 @@ function isPluginWorkerResult(message) {
22
23
  'createNodesResult',
23
24
  'createDependenciesResult',
24
25
  'processProjectGraphResult',
26
+ 'createMetadataResult',
25
27
  ].includes(message.type));
26
28
  }
27
29
  exports.isPluginWorkerResult = isPluginWorkerResult;
28
30
  // Takes a message and a map of handlers and calls the appropriate handler
29
31
  // type safe and requires all handlers to be handled
30
- async function consumeMessage(raw, handlers) {
32
+ async function consumeMessage(socket, raw, handlers) {
31
33
  const message = raw;
32
34
  const handler = handlers[message.type];
33
35
  if (handler) {
34
36
  const response = await handler(message.payload);
35
37
  if (response) {
36
- process.send(response);
38
+ sendMessageOverSocket(socket, response);
37
39
  }
38
40
  }
39
41
  }
40
42
  exports.consumeMessage = consumeMessage;
43
+ function sendMessageOverSocket(socket, message) {
44
+ socket.write(JSON.stringify(message) + String.fromCodePoint(4));
45
+ }
46
+ exports.sendMessageOverSocket = sendMessageOverSocket;
@@ -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>, () => void];
3
+ export declare function loadRemoteNxPlugin(plugin: PluginConfiguration, root: string): Promise<[Promise<LoadedNxPlugin>, () => void]>;
@@ -3,55 +3,50 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.loadRemoteNxPlugin = void 0;
4
4
  const child_process_1 = require("child_process");
5
5
  const path = require("path");
6
+ const net_1 = require("net");
6
7
  // TODO (@AgentEnder): After scoped verbose logging is implemented, re-add verbose logs here.
7
8
  // import { logger } from '../../utils/logger';
8
9
  const internal_api_1 = require("../internal-api");
10
+ const socket_utils_1 = require("../../../daemon/socket-utils");
11
+ const consume_messages_from_socket_1 = require("../../../utils/consume-messages-from-socket");
12
+ const exit_codes_1 = require("../../../utils/exit-codes");
9
13
  const messaging_1 = require("./messaging");
10
14
  const cleanupFunctions = new Set();
11
15
  const pluginNames = new Map();
12
- function loadRemoteNxPlugin(plugin, root) {
13
- // this should only really be true when running unit tests within
14
- // the Nx repo. We still need to start the worker in this case,
15
- // but its typescript.
16
- const isWorkerTypescript = path.extname(__filename) === '.ts';
17
- const workerPath = path.join(__dirname, 'plugin-worker');
18
- const env = {
19
- ...process.env,
20
- ...(isWorkerTypescript
21
- ? {
22
- // Ensures that the worker uses the same tsconfig as the main process
23
- TS_NODE_PROJECT: path.join(__dirname, '../../../../tsconfig.lib.json'),
24
- }
25
- : {}),
26
- };
27
- const worker = (0, child_process_1.fork)(workerPath, [], {
28
- stdio: ['ignore', 'inherit', 'inherit', 'ipc'],
29
- env,
30
- execArgv: [
31
- ...process.execArgv,
32
- // If the worker is typescript, we need to register ts-node
33
- ...(isWorkerTypescript ? ['-r', 'ts-node/register'] : []),
34
- ],
35
- });
36
- worker.send({ type: 'load', payload: { plugin, root } });
37
- // logger.verbose(`[plugin-worker] started worker: ${worker.pid}`);
16
+ const MAX_MESSAGE_WAIT = 1000 * 60 * 5; // 5 minutes
17
+ const nxPluginWorkerCache = (global['nxPluginWorkerCache'] ??= new Map());
18
+ async function loadRemoteNxPlugin(plugin, root) {
19
+ const cacheKey = JSON.stringify({ plugin, root });
20
+ if (nxPluginWorkerCache.has(cacheKey)) {
21
+ return [nxPluginWorkerCache.get(cacheKey), () => { }];
22
+ }
23
+ const { worker, socket } = await startPluginWorker();
38
24
  const pendingPromises = new Map();
39
25
  const exitHandler = createWorkerExitHandler(worker, pendingPromises);
40
26
  const cleanupFunction = () => {
41
27
  worker.off('exit', exitHandler);
28
+ socket.destroy();
42
29
  shutdownPluginWorker(worker);
30
+ nxPluginWorkerCache.delete(cacheKey);
43
31
  };
44
32
  cleanupFunctions.add(cleanupFunction);
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
- ];
33
+ const pluginPromise = new Promise((res, rej) => {
34
+ (0, messaging_1.sendMessageOverSocket)(socket, {
35
+ type: 'load',
36
+ payload: { plugin, root },
37
+ });
38
+ // logger.verbose(`[plugin-worker] started worker: ${worker.pid}`);
39
+ const loadTimeout = setTimeout(() => {
40
+ rej(new Error('Plugin worker timed out when loading plugin:' + plugin));
41
+ }, MAX_MESSAGE_WAIT);
42
+ socket.on('data', (0, consume_messages_from_socket_1.consumeMessagesFromSocket)(createWorkerHandler(worker, pendingPromises, (val) => {
43
+ clearTimeout(loadTimeout);
44
+ res(val);
45
+ }, rej, socket)));
46
+ worker.on('exit', exitHandler);
47
+ });
48
+ nxPluginWorkerCache.set(cacheKey, pluginPromise);
49
+ return [pluginPromise, cleanupFunction];
55
50
  }
56
51
  exports.loadRemoteNxPlugin = loadRemoteNxPlugin;
57
52
  function shutdownPluginWorker(worker) {
@@ -68,13 +63,15 @@ function shutdownPluginWorker(worker) {
68
63
  * @param onloadError Rejecter for RemotePlugin promise
69
64
  * @returns Function to handle messages from the worker
70
65
  */
71
- function createWorkerHandler(worker, pending, onload, onloadError) {
66
+ function createWorkerHandler(worker, pending, onload, onloadError, socket) {
72
67
  let pluginName;
73
- return function (message) {
68
+ let txId = 0;
69
+ return function (raw) {
70
+ const message = JSON.parse(raw);
74
71
  if (!(0, messaging_1.isPluginWorkerResult)(message)) {
75
72
  return;
76
73
  }
77
- return (0, messaging_1.consumeMessage)(message, {
74
+ return (0, messaging_1.consumeMessage)(socket, message, {
78
75
  'load-result': (result) => {
79
76
  if (result.success) {
80
77
  const { name, createNodesPattern, include, exclude } = result;
@@ -88,9 +85,9 @@ function createWorkerHandler(worker, pending, onload, onloadError) {
88
85
  ? [
89
86
  createNodesPattern,
90
87
  (configFiles, ctx) => {
91
- const tx = pluginName + ':createNodes:' + performance.now();
88
+ const tx = pluginName + worker.pid + ':createNodes:' + txId++;
92
89
  return registerPendingPromise(tx, pending, () => {
93
- worker.send({
90
+ (0, messaging_1.sendMessageOverSocket)(socket, {
94
91
  type: 'createNodes',
95
92
  payload: { configFiles, context: ctx, tx },
96
93
  });
@@ -100,9 +97,9 @@ function createWorkerHandler(worker, pending, onload, onloadError) {
100
97
  : undefined,
101
98
  createDependencies: result.hasCreateDependencies
102
99
  ? (ctx) => {
103
- const tx = pluginName + ':createDependencies:' + performance.now();
100
+ const tx = pluginName + worker.pid + ':createDependencies:' + txId++;
104
101
  return registerPendingPromise(tx, pending, () => {
105
- worker.send({
102
+ (0, messaging_1.sendMessageOverSocket)(socket, {
106
103
  type: 'createDependencies',
107
104
  payload: { context: ctx, tx },
108
105
  });
@@ -111,9 +108,9 @@ function createWorkerHandler(worker, pending, onload, onloadError) {
111
108
  : undefined,
112
109
  processProjectGraph: result.hasProcessProjectGraph
113
110
  ? (graph, ctx) => {
114
- const tx = pluginName + ':processProjectGraph:' + performance.now();
111
+ const tx = pluginName + worker.pid + ':processProjectGraph:' + txId++;
115
112
  return registerPendingPromise(tx, pending, () => {
116
- worker.send({
113
+ (0, messaging_1.sendMessageOverSocket)(socket, {
117
114
  type: 'processProjectGraph',
118
115
  payload: { graph, ctx, tx },
119
116
  });
@@ -122,9 +119,9 @@ function createWorkerHandler(worker, pending, onload, onloadError) {
122
119
  : undefined,
123
120
  createMetadata: result.hasCreateMetadata
124
121
  ? (graph, ctx) => {
125
- const tx = pluginName + ':createMetadata:' + performance.now();
122
+ const tx = pluginName + worker.pid + ':createMetadata:' + txId++;
126
123
  return registerPendingPromise(tx, pending, () => {
127
- worker.send({
124
+ (0, messaging_1.sendMessageOverSocket)(socket, {
128
125
  type: 'createMetadata',
129
126
  payload: { graph, context: ctx, tx },
130
127
  });
@@ -183,19 +180,31 @@ function createWorkerExitHandler(worker, pendingPromises) {
183
180
  }
184
181
  };
185
182
  }
186
- process.on('exit', () => {
183
+ let cleanedUp = false;
184
+ const exitHandler = () => {
187
185
  for (const fn of cleanupFunctions) {
188
186
  fn();
189
187
  }
188
+ cleanedUp = true;
189
+ };
190
+ process.on('exit', exitHandler);
191
+ process.on('SIGINT', () => {
192
+ exitHandler();
193
+ process.exit((0, exit_codes_1.signalToCode)('SIGINT'));
190
194
  });
195
+ process.on('SIGTERM', exitHandler);
191
196
  function registerPendingPromise(tx, pending, callback) {
192
- let resolver, rejector;
197
+ let resolver, rejector, timeout;
193
198
  const promise = new Promise((res, rej) => {
194
- resolver = res;
195
199
  rejector = rej;
200
+ resolver = res;
201
+ timeout = setTimeout(() => {
202
+ rej(new Error(`Plugin worker timed out when processing message ${tx}`));
203
+ }, MAX_MESSAGE_WAIT);
196
204
  callback();
197
205
  }).finally(() => {
198
206
  pending.delete(tx);
207
+ clearTimeout(timeout);
199
208
  });
200
209
  pending.set(tx, {
201
210
  promise,
@@ -204,3 +213,70 @@ function registerPendingPromise(tx, pending, callback) {
204
213
  });
205
214
  return promise;
206
215
  }
216
+ global.nxPluginWorkerCount ??= 0;
217
+ async function startPluginWorker() {
218
+ // this should only really be true when running unit tests within
219
+ // the Nx repo. We still need to start the worker in this case,
220
+ // but its typescript.
221
+ const isWorkerTypescript = path.extname(__filename) === '.ts';
222
+ const workerPath = path.join(__dirname, 'plugin-worker');
223
+ const env = {
224
+ ...process.env,
225
+ ...(isWorkerTypescript
226
+ ? {
227
+ // Ensures that the worker uses the same tsconfig as the main process
228
+ TS_NODE_PROJECT: path.join(__dirname, '../../../../tsconfig.lib.json'),
229
+ }
230
+ : {}),
231
+ };
232
+ const ipcPath = (0, socket_utils_1.getPluginOsSocketPath)([process.pid, global.nxPluginWorkerCount++].join('-'));
233
+ const worker = (0, child_process_1.spawn)(process.execPath, [
234
+ ...(isWorkerTypescript ? ['--require', 'ts-node/register'] : []),
235
+ workerPath,
236
+ ipcPath,
237
+ ], {
238
+ stdio: process.stdout.isTTY ? 'inherit' : 'ignore',
239
+ env,
240
+ detached: true,
241
+ shell: false,
242
+ windowsHide: true,
243
+ });
244
+ worker.unref();
245
+ let attempts = 0;
246
+ return new Promise((resolve, reject) => {
247
+ const id = setInterval(async () => {
248
+ const socket = await isServerAvailable(ipcPath);
249
+ if (socket) {
250
+ socket.unref();
251
+ clearInterval(id);
252
+ resolve({
253
+ worker,
254
+ socket,
255
+ });
256
+ }
257
+ else if (attempts > 1000) {
258
+ // daemon fails to start, the process probably exited
259
+ // we print the logs and exit the client
260
+ reject('Failed to start plugin worker.');
261
+ }
262
+ else {
263
+ attempts++;
264
+ }
265
+ }, 10);
266
+ });
267
+ }
268
+ function isServerAvailable(ipcPath) {
269
+ return new Promise((resolve) => {
270
+ try {
271
+ const socket = (0, net_1.connect)(ipcPath, () => {
272
+ resolve(socket);
273
+ });
274
+ socket.once('error', () => {
275
+ resolve(false);
276
+ });
277
+ }
278
+ catch (err) {
279
+ resolve(false);
280
+ }
281
+ });
282
+ }