From 9cc3c79ea84e71b04c5a15ff258f946c77fa85e4 Mon Sep 17 00:00:00 2001 From: "Kristoffer K." Date: Thu, 18 Jul 2024 21:25:33 +0200 Subject: [PATCH] fix(cli): ensure an empty event loop counts as an error (#6399) **What's the problem this PR addresses?** If the event loop unexpectedly becomes empty `yarn` will terminate with exit code 0 even though the command didn't complete successfully. Ref https://github.com/nodejs/node/issues/53902 Ref https://github.com/yarnpkg/berry/issues/6398 where that happens and the install is incomplete when `yarn` terminates with exit code 0. It's caught in the following `yarn build` step which can't find the install state. **How did you fix it?** Set `process.exitCode` to an error code before executing the CLI so an unexpected empty event loop counts as an error. **Checklist** - [x] I have read the [Contributing Guide](https://yarnpkg.com/advanced/contributing). - [x] I have set the packages that need to be released for my changes to be effective. - [x] I will check that all automated PR checks pass before the PR gets reviewed. --- .yarn/versions/3a4fece1.yml | 23 +++++++++++++++++ .../sources/commands/install.test.ts | 25 +++++++++++++++++++ packages/yarnpkg-cli/sources/lib.ts | 12 +++++++++ 3 files changed, 60 insertions(+) create mode 100644 .yarn/versions/3a4fece1.yml diff --git a/.yarn/versions/3a4fece1.yml b/.yarn/versions/3a4fece1.yml new file mode 100644 index 000000000000..86b828aea491 --- /dev/null +++ b/.yarn/versions/3a4fece1.yml @@ -0,0 +1,23 @@ +releases: + "@yarnpkg/cli": patch + +declined: + - "@yarnpkg/plugin-compat" + - "@yarnpkg/plugin-constraints" + - "@yarnpkg/plugin-dlx" + - "@yarnpkg/plugin-essentials" + - "@yarnpkg/plugin-init" + - "@yarnpkg/plugin-interactive-tools" + - "@yarnpkg/plugin-nm" + - "@yarnpkg/plugin-npm-cli" + - "@yarnpkg/plugin-pack" + - "@yarnpkg/plugin-patch" + - "@yarnpkg/plugin-pnp" + - "@yarnpkg/plugin-pnpm" + - "@yarnpkg/plugin-stage" + - "@yarnpkg/plugin-typescript" + - "@yarnpkg/plugin-version" + - "@yarnpkg/plugin-workspace-tools" + - "@yarnpkg/builder" + - "@yarnpkg/core" + - "@yarnpkg/doctor" diff --git a/packages/acceptance-tests/pkg-tests-specs/sources/commands/install.test.ts b/packages/acceptance-tests/pkg-tests-specs/sources/commands/install.test.ts index f9bbf062f888..345eb98a2545 100644 --- a/packages/acceptance-tests/pkg-tests-specs/sources/commands/install.test.ts +++ b/packages/acceptance-tests/pkg-tests-specs/sources/commands/install.test.ts @@ -900,5 +900,30 @@ describe(`Commands`, () => { }); }), ); + + test(`it should exit with an error code after an unexpected empty event loop`, + makeTemporaryEnv({}, async ({path, run}) => { + await xfs.writeFilePromise(ppath.join(path, `plugin.cjs`), ` +module.exports = { + name: 'test', + factory() { + return { + hooks: { + afterAllInstalled: () => new Promise(() => {}), + }, + }; + }, +}; +`); + await xfs.writeJsonPromise(ppath.join(path, Filename.rc), { + plugins: [`./plugin.cjs`], + }); + + await expect(run(`install`)).rejects.toMatchObject({ + code: 42, + stdout: expect.stringContaining(`Yarn is terminating due to an unexpected empty event loop`), + }); + }), + ); }); }); diff --git a/packages/yarnpkg-cli/sources/lib.ts b/packages/yarnpkg-cli/sources/lib.ts index 281d5f3d9344..2ff6c8148025 100644 --- a/packages/yarnpkg-cli/sources/lib.ts +++ b/packages/yarnpkg-cli/sources/lib.ts @@ -186,12 +186,24 @@ export async function getCli({cwd = ppath.cwd(), pluginConfiguration = getPlugin export async function runExit(argv: Array, {cwd = ppath.cwd(), selfPath, pluginConfiguration}: {cwd: PortablePath, selfPath: PortablePath | null, pluginConfiguration: PluginConfiguration}) { const cli = getBaseCli({cwd, pluginConfiguration}); + function unexpectedTerminationHandler() { + Cli.defaultContext.stdout.write(`ERROR: Yarn is terminating due to an unexpected empty event loop.\nPlease report this issue at https://github.com/yarnpkg/berry/issues.`); + } + + process.once(`beforeExit`, unexpectedTerminationHandler); + try { + // The exit code is set to an error code before the CLI runs so that + // if the event loop becomes empty and node terminates without + // finishing the execution of this function it counts as an error. + // https://github.com/yarnpkg/berry/issues/6398 + process.exitCode = 42; process.exitCode = await run(cli, argv, {selfPath, pluginConfiguration}); } catch (error) { Cli.defaultContext.stdout.write(cli.error(error)); process.exitCode = 1; } finally { + process.off(`beforeExit`, unexpectedTerminationHandler); await xfs.rmtempPromise(); } }