From 6ef01d7272df99adf03deb00053a27e28ae502e8 Mon Sep 17 00:00:00 2001 From: Craigory Coppola Date: Wed, 19 Nov 2025 14:27:49 -0500 Subject: [PATCH] fix(core): daemon command should exit at end --- .../src/command-line/daemon/command-object.ts | 20 +++- .../command-line/yargs-utils/arguments-of.ts | 24 ++++ .../src/project-graph/plugins/get-plugins.ts | 109 +++++++++--------- .../plugins/isolation/plugin-pool.ts | 5 +- 4 files changed, 96 insertions(+), 62 deletions(-) create mode 100644 packages/nx/src/command-line/yargs-utils/arguments-of.ts diff --git a/packages/nx/src/command-line/daemon/command-object.ts b/packages/nx/src/command-line/daemon/command-object.ts index 3ee5ccb713672..b3614d1052308 100644 --- a/packages/nx/src/command-line/daemon/command-object.ts +++ b/packages/nx/src/command-line/daemon/command-object.ts @@ -1,14 +1,24 @@ import { CommandModule, Argv } from 'yargs'; import { linkToNxDevAndExamples } from '../yargs-utils/documentation'; +import { handleErrors } from '../../utils/handle-errors'; +import { withVerbose } from '../yargs-utils/shared-options'; +import { makeCommandModule } from '../yargs-utils/arguments-of'; -export const yargsDaemonCommand: CommandModule = { +const builder = (yargs: Argv) => + linkToNxDevAndExamples(withVerbose(withDaemonOptions(yargs)), 'daemon'); + +export const yargsDaemonCommand = makeCommandModule({ command: 'daemon', describe: 'Prints information about the Nx Daemon process or starts a daemon process.', - builder: (yargs) => - linkToNxDevAndExamples(withDaemonOptions(yargs), 'daemon'), - handler: async (args) => (await import('./daemon')).daemonHandler(args), -}; + builder, + handler: async (args) => { + const exitCode = await handleErrors(args.verbose, async () => + (await import('./daemon')).daemonHandler(args) + ); + process.exit(exitCode); + }, +}); function withDaemonOptions(yargs: Argv): Argv { return yargs diff --git a/packages/nx/src/command-line/yargs-utils/arguments-of.ts b/packages/nx/src/command-line/yargs-utils/arguments-of.ts new file mode 100644 index 0000000000000..e5a5a3063f146 --- /dev/null +++ b/packages/nx/src/command-line/yargs-utils/arguments-of.ts @@ -0,0 +1,24 @@ +import type { Argv, CommandModule as YargsCommandModule } from 'yargs'; + +export type Builder = ( + yargs: Argv +) => Argv; + +export type Handler> = ( + args: Awaited['argv']> +) => void | Promise; + +export interface CommandModule + extends Omit, 'handler'> { + builder: Builder; + handler: Handler>; +} + +/** + * Helper function to define a Yargs CommandModule with proper typing and not sacrifice inference. + */ +export function makeCommandModule( + module: CommandModule +): CommandModule { + return module; +} diff --git a/packages/nx/src/project-graph/plugins/get-plugins.ts b/packages/nx/src/project-graph/plugins/get-plugins.ts index a5ca79caca47f..9eaf6361eac2a 100644 --- a/packages/nx/src/project-graph/plugins/get-plugins.ts +++ b/packages/nx/src/project-graph/plugins/get-plugins.ts @@ -20,9 +20,7 @@ import { isIsolationEnabled } from './isolation/enabled'; */ let currentPluginsConfigurationHash: string; let loadedPlugins: LoadedNxPlugin[]; -let pendingPluginsPromise: - | Promise void]> - | undefined; +let pendingPluginsPromise: Promise | undefined; let cleanupSpecifiedPlugins: () => void | undefined; const loadingMethod = ( @@ -48,24 +46,16 @@ export async function getPlugins( return loadedPlugins; } - // Cleanup current plugins before loading new ones - cleanupSpecifiedPlugins?.(); - - pendingPluginsPromise ??= loadSpecifiedNxPlugins(pluginsConfiguration, root); - currentPluginsConfigurationHash = pluginsConfigurationHash; - const [[result, cleanupFn], defaultPlugins] = await Promise.all([ - pendingPluginsPromise, + const [defaultPlugins, specifiedPlugins] = await Promise.all([ getOnlyDefaultPlugins(root), + (pendingPluginsPromise ??= loadSpecifiedNxPlugins( + pluginsConfiguration, + root + )), ]); - cleanupSpecifiedPlugins = () => { - loadedPlugins = undefined; - pendingPluginsPromise = undefined; - cleanupFn(); - }; - - loadedPlugins = result.concat(defaultPlugins); + loadedPlugins = specifiedPlugins.concat(defaultPlugins); return loadedPlugins; } @@ -111,6 +101,8 @@ export async function getOnlyDefaultPlugins(root = workspaceRoot) { export function cleanupPlugins() { cleanupSpecifiedPlugins?.(); cleanupDefaultPlugins?.(); + pendingPluginsPromise = undefined; + pendingDefaultPluginPromise = undefined; } /** @@ -164,55 +156,62 @@ async function loadDefaultNxPlugins(root = workspaceRoot) { } async function loadSpecifiedNxPlugins( - plugins: PluginConfiguration[], + pluginsConfigurations: PluginConfiguration[], root = workspaceRoot -): Promise void]> { +): Promise { + // Returning existing plugins is handled by getPlugins, + // so, if we are here and there are existing plugins, they are stale + if (cleanupSpecifiedPlugins) { + cleanupSpecifiedPlugins(); + } + performance.mark('loadSpecifiedNxPlugins:start'); - plugins ??= []; + pluginsConfigurations ??= []; const cleanupFunctions: Array<() => void> = []; - const ret = [ - await Promise.all( - plugins.map(async (plugin, index) => { - const pluginPath = typeof plugin === 'string' ? plugin : plugin.plugin; - performance.mark(`Load Nx Plugin: ${pluginPath} - start`); - - const [loadedPluginPromise, cleanup] = await loadingMethod( - plugin, - root, - index - ); - - cleanupFunctions.push(cleanup); - const res = await loadedPluginPromise; - res.index = index; - performance.mark(`Load Nx Plugin: ${pluginPath} - end`); - performance.measure( - `Load Nx Plugin: ${pluginPath}`, - `Load Nx Plugin: ${pluginPath} - start`, - `Load Nx Plugin: ${pluginPath} - end` - ); - - return res; - }) - ), - () => { - for (const fn of cleanupFunctions) { - fn(); - } - if (pluginTranspilerIsRegistered()) { - cleanupPluginTSTranspiler(); - } - }, - ] as const; + const plugins = await Promise.all( + pluginsConfigurations.map(async (plugin, index) => { + const pluginPath = typeof plugin === 'string' ? plugin : plugin.plugin; + performance.mark(`Load Nx Plugin: ${pluginPath} - start`); + + const [loadedPluginPromise, cleanup] = await loadingMethod( + plugin, + root, + index + ); + + cleanupFunctions.push(cleanup); + const res = await loadedPluginPromise; + res.index = index; + performance.mark(`Load Nx Plugin: ${pluginPath} - end`); + performance.measure( + `Load Nx Plugin: ${pluginPath}`, + `Load Nx Plugin: ${pluginPath} - start`, + `Load Nx Plugin: ${pluginPath} - end` + ); + + return res; + }) + ); performance.mark('loadSpecifiedNxPlugins:end'); performance.measure( 'loadSpecifiedNxPlugins', 'loadSpecifiedNxPlugins:start', 'loadSpecifiedNxPlugins:end' ); - return ret; + + cleanupSpecifiedPlugins = () => { + for (const fn of cleanupFunctions) { + fn(); + } + if (pluginTranspilerIsRegistered()) { + cleanupPluginTSTranspiler(); + } + pendingPluginsPromise = undefined; + }; + + return plugins; } function getDefaultPlugins(root: string) { diff --git a/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts b/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts index 064a4ac959aa5..af0fe2444a0ea 100644 --- a/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts +++ b/packages/nx/src/project-graph/plugins/isolation/plugin-pool.ts @@ -67,7 +67,7 @@ export async function loadRemoteNxPlugin( const { name, pluginPath, shouldRegisterTSTranspiler } = await resolveNxPlugin(moduleName, root, getNxRequirePaths(root)); - const { worker, socket } = await startPluginWorker(); + const { worker, socket } = await startPluginWorker(name); // Register plugin worker as a subprocess of the main CLI // This allows metrics collection when the daemon is not used @@ -411,7 +411,7 @@ function registerPendingPromise( global.nxPluginWorkerCount ??= 0; -async function startPluginWorker() { +async function startPluginWorker(name: string) { // this should only really be true when running unit tests within // the Nx repo. We still need to start the worker in this case, // but its typescript. @@ -441,6 +441,7 @@ async function startPluginWorker() { ...(isWorkerTypescript ? ['--require', 'ts-node/register'] : []), workerPath, ipcPath, + name, ], { stdio: 'inherit',