fix(node): esbuild breaks and does not cleanup (#30469)

<!-- Please make sure you have read the submission guidelines before
posting an PR -->
<!--
https://github.com/nrwl/nx/blob/master/CONTRIBUTING.md#-submitting-a-pr
-->

<!-- Please make sure that your commit message follows our format -->
<!-- Example: `fix(nx): must begin with lowercase` -->

<!-- If this is a particularly complex change or feature addition, you
can request a dedicated Nx release for this pull request branch. Mention
someone from the Nx team or the `@nrwl/nx-pipelines-reviewers` and they
will confirm if the PR warrants its own release for testing purposes,
and generate it for you if appropriate. -->

## Current Behavior
<!-- This is the behavior we have today -->
esbuild breaks on function checks and improperly handles dangling
promises, preventing the program to exit successfully.

## Expected Behavior
<!-- This is the behavior we should expect with the changes in this PR
-->
The program should exit properly and no zombie processes kept running.

## Related Issue(s)
<!-- Please link the issue being fixed so it gets closed when this is
merged. -->

Fixes #
This commit is contained in:
Rui Lima 2025-06-09 10:50:57 +01:00 committed by GitHub
parent c9021b0e39
commit b38f966707
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 21 additions and 9 deletions

View File

@ -169,14 +169,21 @@ export async function* esbuildExecutor(
}); });
await ctx.watch(); await ctx.watch();
return () => ctx.dispose(); return async () => ctx.dispose();
}) })
); );
registerCleanupCallback(() => { registerCleanupCallback(() => {
assetsResult?.stop(); if (typeof assetsResult?.stop === 'function') assetsResult.stop();
packageJsonResult?.stop();
disposeFns.forEach((fn) => fn()); if (typeof packageJsonResult?.stop === 'function') {
packageJsonResult.stop();
}
disposeFns.forEach(async (fn) => {
await fn();
});
done(); // return from async iterable done(); // return from async iterable
}); });
} }

View File

@ -37,8 +37,6 @@ function debounce<T>(fn: () => Promise<T>, wait: number): () => Promise<T> {
let pendingPromise: Promise<T> | null = null; let pendingPromise: Promise<T> | null = null;
return () => { return () => {
clearTimeout(timeoutId);
if (!pendingPromise) { if (!pendingPromise) {
pendingPromise = new Promise<T>((resolve, reject) => { pendingPromise = new Promise<T>((resolve, reject) => {
timeoutId = setTimeout(() => { timeoutId = setTimeout(() => {
@ -50,6 +48,9 @@ function debounce<T>(fn: () => Promise<T>, wait: number): () => Promise<T> {
.catch((error) => { .catch((error) => {
pendingPromise = null; pendingPromise = null;
reject(error); reject(error);
})
.finally(() => {
clearTimeout(timeoutId);
}); });
}, wait); }, wait);
}); });
@ -197,7 +198,7 @@ export async function* nodeExecutor(
if (code !== 0) { if (code !== 0) {
error(new Error(`Process exited with code ${code}`)); error(new Error(`Process exited with code ${code}`));
} else { } else {
done(); resolve(done());
} }
} }
resolve(); resolve();
@ -226,8 +227,12 @@ export async function* nodeExecutor(
}; };
const stopAllTasks = async (signal: NodeJS.Signals = 'SIGTERM') => { const stopAllTasks = async (signal: NodeJS.Signals = 'SIGTERM') => {
additionalExitHandler?.(); if (typeof additionalExitHandler === 'function') {
await currentTask?.stop(signal); additionalExitHandler();
}
if (typeof currentTask?.stop === 'function') {
await currentTask.stop(signal);
}
for (const task of tasks) { for (const task of tasks) {
await task.stop(signal); await task.stop(signal);
} }