nx 19.4.0-beta.0 → 19.4.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.
Files changed (67) hide show
  1. package/package.json +14 -14
  2. package/release/changelog-renderer/index.d.ts +6 -0
  3. package/release/changelog-renderer/index.js +1 -1
  4. package/src/command-line/connect/connect-to-nx-cloud.d.ts +1 -1
  5. package/src/command-line/connect/connect-to-nx-cloud.js +16 -25
  6. package/src/command-line/graph/graph.d.ts +1 -0
  7. package/src/command-line/graph/graph.js +12 -1
  8. package/src/command-line/init/implementation/dot-nx/add-nx-scripts.js +5 -6
  9. package/src/command-line/release/changelog.js +6 -1
  10. package/src/command-line/release/config/config.js +3 -0
  11. package/src/command-line/run/command-object.js +2 -1
  12. package/src/config/workspace-json-project-json.d.ts +2 -0
  13. package/src/core/graph/main.js +1 -1
  14. package/src/core/graph/styles.css +1 -1
  15. package/src/daemon/client/client.d.ts +5 -0
  16. package/src/daemon/client/client.js +14 -0
  17. package/src/daemon/message-types/task-history.d.ts +13 -0
  18. package/src/daemon/message-types/task-history.js +19 -0
  19. package/src/daemon/server/handle-get-task-history.d.ts +4 -0
  20. package/src/daemon/server/handle-get-task-history.js +12 -0
  21. package/src/daemon/server/handle-write-task-runs-to-history.d.ts +5 -0
  22. package/src/daemon/server/handle-write-task-runs-to-history.js +12 -0
  23. package/src/daemon/server/plugins.js +12 -2
  24. package/src/daemon/server/server.js +9 -0
  25. package/src/daemon/socket-utils.d.ts +1 -0
  26. package/src/daemon/socket-utils.js +6 -1
  27. package/src/executors/run-commands/run-commands.impl.js +29 -20
  28. package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.d.ts +1 -0
  29. package/src/nx-cloud/generators/connect-to-nx-cloud/connect-to-nx-cloud.js +68 -33
  30. package/src/nx-cloud/generators/connect-to-nx-cloud/schema.json +5 -0
  31. package/src/nx-cloud/utilities/url-shorten.d.ts +2 -1
  32. package/src/nx-cloud/utilities/url-shorten.js +47 -11
  33. package/src/plugins/package-json-workspaces/create-nodes.js +12 -7
  34. package/src/plugins/project-json/build-nodes/package-json-next-to-project-json.js +10 -2
  35. package/src/plugins/target-defaults/target-defaults-plugin.d.ts +5 -0
  36. package/src/project-graph/plugins/internal-api.js +1 -1
  37. package/src/project-graph/plugins/isolation/index.d.ts +1 -1
  38. package/src/project-graph/plugins/isolation/index.js +8 -13
  39. package/src/project-graph/plugins/isolation/messaging.d.ts +6 -3
  40. package/src/project-graph/plugins/isolation/messaging.js +9 -3
  41. package/src/project-graph/plugins/isolation/plugin-pool.d.ts +1 -1
  42. package/src/project-graph/plugins/isolation/plugin-pool.js +123 -43
  43. package/src/project-graph/plugins/isolation/plugin-worker.js +128 -107
  44. package/src/project-graph/project-graph.js +7 -1
  45. package/src/project-graph/utils/normalize-project-nodes.d.ts +1 -5
  46. package/src/project-graph/utils/normalize-project-nodes.js +2 -17
  47. package/src/project-graph/utils/project-configuration-utils.js +14 -3
  48. package/src/project-graph/utils/retrieve-workspace-files.d.ts +3 -3
  49. package/src/tasks-runner/default-tasks-runner.js +2 -2
  50. package/src/tasks-runner/life-cycle.d.ts +10 -10
  51. package/src/tasks-runner/life-cycle.js +10 -10
  52. package/src/tasks-runner/life-cycles/task-history-life-cycle.d.ts +9 -0
  53. package/src/tasks-runner/life-cycles/task-history-life-cycle.js +54 -0
  54. package/src/tasks-runner/run-command.js +6 -0
  55. package/src/tasks-runner/task-env.d.ts +13 -0
  56. package/src/tasks-runner/task-env.js +41 -26
  57. package/src/tasks-runner/task-orchestrator.js +4 -4
  58. package/src/utils/git-utils.d.ts +1 -1
  59. package/src/utils/git-utils.js +13 -2
  60. package/src/utils/nx-cloud-utils.d.ts +1 -1
  61. package/src/utils/nx-cloud-utils.js +1 -1
  62. package/src/utils/package-json.d.ts +3 -0
  63. package/src/utils/package-json.js +15 -3
  64. package/src/utils/serialize-target.d.ts +1 -0
  65. package/src/utils/serialize-target.js +7 -0
  66. package/src/utils/task-history.d.ts +8 -0
  67. package/src/utils/task-history.js +97 -0
@@ -1,16 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.shortenedCloudUrl = void 0;
3
+ exports.repoUsesGithub = exports.shortenedCloudUrl = void 0;
4
4
  const devkit_exports_1 = require("../../devkit-exports");
5
5
  const git_utils_1 = require("../../utils/git-utils");
6
- async function shortenedCloudUrl(installationSource, accessToken, github) {
6
+ const semver_1 = require("semver");
7
+ async function shortenedCloudUrl(installationSource, accessToken, usesGithub) {
7
8
  const githubSlug = (0, git_utils_1.getGithubSlugOrNull)();
8
9
  const apiUrl = removeTrailingSlash(process.env.NX_CLOUD_API || process.env.NRWL_API || `https://cloud.nx.app`);
9
- const installationSupportsGitHub = await getInstallationSupportsGitHub(apiUrl);
10
- const usesGithub = (githubSlug || github) &&
11
- (apiUrl.includes('cloud.nx.app') ||
12
- apiUrl.includes('eu.nx.app') ||
13
- installationSupportsGitHub);
10
+ const version = await getNxCloudVersion(apiUrl);
11
+ if (version && (0, semver_1.lt)(truncateToSemver(version), '2406.11.5')) {
12
+ return apiUrl;
13
+ }
14
14
  const source = getSource(installationSource);
15
15
  try {
16
16
  const response = await require('axios').post(`${apiUrl}/nx-cloud/onboarding`, {
@@ -27,10 +27,20 @@ async function shortenedCloudUrl(installationSource, accessToken, github) {
27
27
  catch (e) {
28
28
  devkit_exports_1.logger.verbose(`Failed to shorten Nx Cloud URL.
29
29
  ${e}`);
30
- return getURLifShortenFailed(usesGithub, githubSlug, apiUrl, accessToken, source);
30
+ return getURLifShortenFailed(usesGithub, githubSlug, apiUrl, source, accessToken);
31
31
  }
32
32
  }
33
33
  exports.shortenedCloudUrl = shortenedCloudUrl;
34
+ async function repoUsesGithub(github) {
35
+ const githubSlug = (0, git_utils_1.getGithubSlugOrNull)();
36
+ const apiUrl = removeTrailingSlash(process.env.NX_CLOUD_API || process.env.NRWL_API || `https://cloud.nx.app`);
37
+ const installationSupportsGitHub = await getInstallationSupportsGitHub(apiUrl);
38
+ return ((githubSlug || github) &&
39
+ (apiUrl.includes('cloud.nx.app') ||
40
+ apiUrl.includes('eu.nx.app') ||
41
+ installationSupportsGitHub));
42
+ }
43
+ exports.repoUsesGithub = repoUsesGithub;
34
44
  function removeTrailingSlash(apiUrl) {
35
45
  return apiUrl[apiUrl.length - 1] === '/' ? apiUrl.slice(0, -1) : apiUrl;
36
46
  }
@@ -48,13 +58,13 @@ function getSource(installationSource) {
48
58
  return 'other';
49
59
  }
50
60
  }
51
- function getURLifShortenFailed(usesGithub, githubSlug, apiUrl, accessToken, source) {
61
+ function getURLifShortenFailed(usesGithub, githubSlug, apiUrl, source, accessToken) {
52
62
  if (usesGithub) {
53
63
  if (githubSlug) {
54
- return `${apiUrl}/setup/connect-workspace/vcs?provider=GITHUB&selectedRepositoryName=${encodeURIComponent(githubSlug)}&source=${source}`;
64
+ return `${apiUrl}/setup/connect-workspace/github/connect?name=${encodeURIComponent(githubSlug)}&source=${source}`;
55
65
  }
56
66
  else {
57
- return `${apiUrl}/setup/connect-workspace/vcs?provider=GITHUB&source=${source}`;
67
+ return `${apiUrl}/setup/connect-workspace/github/select&source=${source}`;
58
68
  }
59
69
  }
60
70
  return `${apiUrl}/setup/connect-workspace/manual?accessToken=${accessToken}&source=${source}`;
@@ -75,3 +85,29 @@ async function getInstallationSupportsGitHub(apiUrl) {
75
85
  return false;
76
86
  }
77
87
  }
88
+ async function getNxCloudVersion(apiUrl) {
89
+ try {
90
+ const response = await require('axios').get(`${apiUrl}/version`, {
91
+ responseType: 'document',
92
+ });
93
+ const version = extractVersion(response.data);
94
+ if (!version) {
95
+ throw new Error('Failed to extract version from response.');
96
+ }
97
+ return version;
98
+ }
99
+ catch (e) {
100
+ devkit_exports_1.logger.verbose(`Failed to get version of Nx Cloud.
101
+ ${e}`);
102
+ }
103
+ }
104
+ function extractVersion(htmlString) {
105
+ // The pattern assumes 'Version' is inside an h1 tag and the version number is the next span's content
106
+ const regex = /<h1[^>]*>Version<\/h1>\s*<div[^>]*><div[^>]*><div[^>]*><span[^>]*>([^<]+)<\/span>/;
107
+ const match = htmlString.match(regex);
108
+ return match ? match[1].trim() : null;
109
+ }
110
+ function truncateToSemver(versionString) {
111
+ // version may be something like 2406.13.5.hotfix2
112
+ return versionString.split(/[\.-]/).slice(0, 3).join('.');
113
+ }
@@ -73,20 +73,25 @@ function buildProjectConfigurationFromPackageJson(packageJson, workspaceRoot, pa
73
73
  throw new Error('Nx requires the root package.json to specify a name if it is being used as an Nx project.');
74
74
  }
75
75
  let name = packageJson.name ?? (0, to_project_name_1.toProjectName)(normalizedPath);
76
- const projectType = nxJson?.workspaceLayout?.appsDir != nxJson?.workspaceLayout?.libsDir &&
77
- nxJson?.workspaceLayout?.appsDir &&
78
- projectRoot.startsWith(nxJson.workspaceLayout.appsDir)
79
- ? 'application'
80
- : 'library';
81
- return {
76
+ const projectConfiguration = {
82
77
  root: projectRoot,
83
78
  sourceRoot: projectRoot,
84
79
  name,
85
- projectType,
86
80
  ...packageJson.nx,
87
81
  targets: (0, package_json_1.readTargetsFromPackageJson)(packageJson),
82
+ tags: (0, package_json_1.getTagsFromPackageJson)(packageJson),
88
83
  metadata: (0, package_json_1.getMetadataFromPackageJson)(packageJson),
89
84
  };
85
+ if (nxJson?.workspaceLayout?.appsDir != nxJson?.workspaceLayout?.libsDir &&
86
+ nxJson?.workspaceLayout?.appsDir &&
87
+ projectRoot.startsWith(nxJson.workspaceLayout.appsDir)) {
88
+ projectConfiguration.projectType = 'application';
89
+ }
90
+ else if (typeof nxJson?.workspaceLayout?.libsDir !== 'undefined' &&
91
+ projectRoot.startsWith(nxJson.workspaceLayout.libsDir)) {
92
+ projectConfiguration.projectType = 'library';
93
+ }
94
+ return projectConfiguration;
90
95
  }
91
96
  exports.buildProjectConfigurationFromPackageJson = buildProjectConfigurationFromPackageJson;
92
97
  /**
@@ -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 {
@@ -40,6 +47,7 @@ function createProjectFromPackageJsonNextToProjectJson(projectJsonPath, workspac
40
47
  root,
41
48
  targets: (0, package_json_1.readTargetsFromPackageJson)(packageJson),
42
49
  metadata: (0, package_json_1.getMetadataFromPackageJson)(packageJson),
50
+ tags: (0, package_json_1.getTagsFromPackageJson)(packageJson),
43
51
  };
44
52
  }
45
53
  catch (e) {
@@ -17,6 +17,7 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
17
17
  [x: string]: any;
18
18
  description?: string;
19
19
  technologies?: string[];
20
+ nonAtomizedTarget?: string;
20
21
  };
21
22
  executor?: undefined;
22
23
  options?: undefined;
@@ -31,6 +32,7 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
31
32
  [x: string]: any;
32
33
  description?: string;
33
34
  technologies?: string[];
35
+ nonAtomizedTarget?: string;
34
36
  };
35
37
  command?: undefined;
36
38
  } | {
@@ -44,6 +46,7 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
44
46
  [x: string]: any;
45
47
  description?: string;
46
48
  technologies?: string[];
49
+ nonAtomizedTarget?: string;
47
50
  };
48
51
  command?: undefined;
49
52
  } | {
@@ -52,6 +55,7 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
52
55
  [x: string]: any;
53
56
  description?: string;
54
57
  technologies?: string[];
58
+ nonAtomizedTarget?: string;
55
59
  };
56
60
  command?: undefined;
57
61
  options?: undefined;
@@ -66,6 +70,7 @@ export declare function getTargetInfo(target: string, projectJsonTargets: Record
66
70
  [x: string]: any;
67
71
  description?: string;
68
72
  technologies?: string[];
73
+ nonAtomizedTarget?: string;
69
74
  };
70
75
  command?: undefined;
71
76
  } | {
@@ -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): [Promise<LoadedNxPlugin>, () => void];
3
+ export declare function loadNxPluginInIsolation(plugin: PluginConfiguration, root?: string): Promise<readonly [Promise<LoadedNxPlugin>, () => void]>;
@@ -3,18 +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 = (0, plugin_pool_1.loadRemoteNxPlugin)(plugin, root);
16
- remotePluginCache.set(cacheKey, loadingPlugin);
17
- // We clean up plugin workers when Nx process completes.
18
- return [loadingPlugin, () => { }];
6
+ async function loadNxPluginInIsolation(plugin, root = workspace_root_1.workspaceRoot) {
7
+ const [loadingPlugin, cleanup] = await (0, plugin_pool_1.loadRemoteNxPlugin)(plugin, root);
8
+ return [
9
+ loadingPlugin,
10
+ () => {
11
+ cleanup();
12
+ },
13
+ ];
19
14
  }
20
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>;
3
+ export declare function loadRemoteNxPlugin(plugin: PluginConfiguration, root: string): Promise<[Promise<LoadedNxPlugin>, () => void]>;
@@ -6,46 +6,46 @@ const path = require("path");
6
6
  // TODO (@AgentEnder): After scoped verbose logging is implemented, re-add verbose logs here.
7
7
  // import { logger } from '../../utils/logger';
8
8
  const internal_api_1 = require("../internal-api");
9
+ const socket_utils_1 = require("../../../daemon/socket-utils");
10
+ const consume_messages_from_socket_1 = require("../../../utils/consume-messages-from-socket");
9
11
  const messaging_1 = require("./messaging");
12
+ const net_1 = require("net");
10
13
  const cleanupFunctions = new Set();
11
14
  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}`);
15
+ const MAX_MESSAGE_WAIT = 1000 * 60 * 5; // 5 minutes
16
+ const nxPluginWorkerCache = (global['nxPluginWorkerCache'] ??= new Map());
17
+ async function loadRemoteNxPlugin(plugin, root) {
18
+ const cacheKey = JSON.stringify({ plugin, root });
19
+ if (nxPluginWorkerCache.has(cacheKey)) {
20
+ return [nxPluginWorkerCache.get(cacheKey), () => { }];
21
+ }
22
+ const { worker, socket } = await startPluginWorker();
38
23
  const pendingPromises = new Map();
39
24
  const exitHandler = createWorkerExitHandler(worker, pendingPromises);
40
25
  const cleanupFunction = () => {
41
26
  worker.off('exit', exitHandler);
27
+ socket.destroy();
42
28
  shutdownPluginWorker(worker);
29
+ nxPluginWorkerCache.delete(cacheKey);
43
30
  };
44
31
  cleanupFunctions.add(cleanupFunction);
45
- return new Promise((res, rej) => {
46
- worker.on('message', createWorkerHandler(worker, pendingPromises, res, rej));
32
+ const pluginPromise = new Promise((res, rej) => {
33
+ (0, messaging_1.sendMessageOverSocket)(socket, {
34
+ type: 'load',
35
+ payload: { plugin, root },
36
+ });
37
+ // logger.verbose(`[plugin-worker] started worker: ${worker.pid}`);
38
+ const loadTimeout = setTimeout(() => {
39
+ rej(new Error('Plugin worker timed out when loading plugin:' + plugin));
40
+ }, MAX_MESSAGE_WAIT);
41
+ socket.on('data', (0, consume_messages_from_socket_1.consumeMessagesFromSocket)(createWorkerHandler(worker, pendingPromises, (val) => {
42
+ clearTimeout(loadTimeout);
43
+ res(val);
44
+ }, rej, socket)));
47
45
  worker.on('exit', exitHandler);
48
46
  });
47
+ nxPluginWorkerCache.set(cacheKey, pluginPromise);
48
+ return [pluginPromise, cleanupFunction];
49
49
  }
50
50
  exports.loadRemoteNxPlugin = loadRemoteNxPlugin;
51
51
  function shutdownPluginWorker(worker) {
@@ -62,13 +62,15 @@ function shutdownPluginWorker(worker) {
62
62
  * @param onloadError Rejecter for RemotePlugin promise
63
63
  * @returns Function to handle messages from the worker
64
64
  */
65
- function createWorkerHandler(worker, pending, onload, onloadError) {
65
+ function createWorkerHandler(worker, pending, onload, onloadError, socket) {
66
66
  let pluginName;
67
- return function (message) {
67
+ let txId = 0;
68
+ return function (raw) {
69
+ const message = JSON.parse(raw);
68
70
  if (!(0, messaging_1.isPluginWorkerResult)(message)) {
69
71
  return;
70
72
  }
71
- return (0, messaging_1.consumeMessage)(message, {
73
+ return (0, messaging_1.consumeMessage)(socket, message, {
72
74
  'load-result': (result) => {
73
75
  if (result.success) {
74
76
  const { name, createNodesPattern, include, exclude } = result;
@@ -82,9 +84,9 @@ function createWorkerHandler(worker, pending, onload, onloadError) {
82
84
  ? [
83
85
  createNodesPattern,
84
86
  (configFiles, ctx) => {
85
- const tx = pluginName + ':createNodes:' + performance.now();
87
+ const tx = pluginName + worker.pid + ':createNodes:' + txId++;
86
88
  return registerPendingPromise(tx, pending, () => {
87
- worker.send({
89
+ (0, messaging_1.sendMessageOverSocket)(socket, {
88
90
  type: 'createNodes',
89
91
  payload: { configFiles, context: ctx, tx },
90
92
  });
@@ -94,9 +96,9 @@ function createWorkerHandler(worker, pending, onload, onloadError) {
94
96
  : undefined,
95
97
  createDependencies: result.hasCreateDependencies
96
98
  ? (ctx) => {
97
- const tx = pluginName + ':createDependencies:' + performance.now();
99
+ const tx = pluginName + worker.pid + ':createDependencies:' + txId++;
98
100
  return registerPendingPromise(tx, pending, () => {
99
- worker.send({
101
+ (0, messaging_1.sendMessageOverSocket)(socket, {
100
102
  type: 'createDependencies',
101
103
  payload: { context: ctx, tx },
102
104
  });
@@ -105,9 +107,9 @@ function createWorkerHandler(worker, pending, onload, onloadError) {
105
107
  : undefined,
106
108
  processProjectGraph: result.hasProcessProjectGraph
107
109
  ? (graph, ctx) => {
108
- const tx = pluginName + ':processProjectGraph:' + performance.now();
110
+ const tx = pluginName + worker.pid + ':processProjectGraph:' + txId++;
109
111
  return registerPendingPromise(tx, pending, () => {
110
- worker.send({
112
+ (0, messaging_1.sendMessageOverSocket)(socket, {
111
113
  type: 'processProjectGraph',
112
114
  payload: { graph, ctx, tx },
113
115
  });
@@ -116,9 +118,9 @@ function createWorkerHandler(worker, pending, onload, onloadError) {
116
118
  : undefined,
117
119
  createMetadata: result.hasCreateMetadata
118
120
  ? (graph, ctx) => {
119
- const tx = pluginName + ':createMetadata:' + performance.now();
121
+ const tx = pluginName + worker.pid + ':createMetadata:' + txId++;
120
122
  return registerPendingPromise(tx, pending, () => {
121
- worker.send({
123
+ (0, messaging_1.sendMessageOverSocket)(socket, {
122
124
  type: 'createMetadata',
123
125
  payload: { graph, context: ctx, tx },
124
126
  });
@@ -177,19 +179,30 @@ function createWorkerExitHandler(worker, pendingPromises) {
177
179
  }
178
180
  };
179
181
  }
180
- process.on('exit', () => {
182
+ let cleanedUp = false;
183
+ const exitHandler = () => {
184
+ if (cleanedUp)
185
+ return;
181
186
  for (const fn of cleanupFunctions) {
182
187
  fn();
183
188
  }
184
- });
189
+ cleanedUp = true;
190
+ };
191
+ process.on('exit', exitHandler);
192
+ process.on('SIGINT', exitHandler);
193
+ process.on('SIGTERM', exitHandler);
185
194
  function registerPendingPromise(tx, pending, callback) {
186
- let resolver, rejector;
195
+ let resolver, rejector, timeout;
187
196
  const promise = new Promise((res, rej) => {
188
- resolver = res;
189
197
  rejector = rej;
198
+ resolver = res;
199
+ timeout = setTimeout(() => {
200
+ rej(new Error(`Plugin worker timed out when processing message ${tx}`));
201
+ }, MAX_MESSAGE_WAIT);
190
202
  callback();
191
203
  }).finally(() => {
192
204
  pending.delete(tx);
205
+ clearTimeout(timeout);
193
206
  });
194
207
  pending.set(tx, {
195
208
  promise,
@@ -198,3 +211,70 @@ function registerPendingPromise(tx, pending, callback) {
198
211
  });
199
212
  return promise;
200
213
  }
214
+ global.nxPluginWorkerCount ??= 0;
215
+ async function startPluginWorker() {
216
+ // this should only really be true when running unit tests within
217
+ // the Nx repo. We still need to start the worker in this case,
218
+ // but its typescript.
219
+ const isWorkerTypescript = path.extname(__filename) === '.ts';
220
+ const workerPath = path.join(__dirname, 'plugin-worker');
221
+ const env = {
222
+ ...process.env,
223
+ ...(isWorkerTypescript
224
+ ? {
225
+ // Ensures that the worker uses the same tsconfig as the main process
226
+ TS_NODE_PROJECT: path.join(__dirname, '../../../../tsconfig.lib.json'),
227
+ }
228
+ : {}),
229
+ };
230
+ const ipcPath = (0, socket_utils_1.getPluginOsSocketPath)([process.pid, global.nxPluginWorkerCount++].join('-'));
231
+ const worker = (0, child_process_1.fork)(workerPath, [ipcPath], {
232
+ stdio: process.stdout.isTTY ? 'inherit' : 'ignore',
233
+ env,
234
+ execArgv: [
235
+ ...process.execArgv,
236
+ // If the worker is typescript, we need to register ts-node
237
+ ...(isWorkerTypescript ? ['-r', 'ts-node/register'] : []),
238
+ ],
239
+ detached: true,
240
+ });
241
+ worker.disconnect();
242
+ worker.unref();
243
+ let attempts = 0;
244
+ return new Promise((resolve, reject) => {
245
+ const id = setInterval(async () => {
246
+ const socket = await isServerAvailable(ipcPath);
247
+ if (socket) {
248
+ socket.unref();
249
+ clearInterval(id);
250
+ resolve({
251
+ worker,
252
+ socket,
253
+ });
254
+ }
255
+ else if (attempts > 1000) {
256
+ // daemon fails to start, the process probably exited
257
+ // we print the logs and exit the client
258
+ reject('Failed to start plugin worker.');
259
+ }
260
+ else {
261
+ attempts++;
262
+ }
263
+ }, 10);
264
+ });
265
+ }
266
+ function isServerAvailable(ipcPath) {
267
+ return new Promise((resolve) => {
268
+ try {
269
+ const socket = (0, net_1.connect)(ipcPath, () => {
270
+ resolve(socket);
271
+ });
272
+ socket.once('error', () => {
273
+ resolve(false);
274
+ });
275
+ }
276
+ catch (err) {
277
+ resolve(false);
278
+ }
279
+ });
280
+ }