fix: preserve signal exit codes in CLI child process handling (#6039)
A child process killed by a signal reports
code=nullon itsexitevent. Three CLIs mishandled that:
egg-scripts startin foreground mode treated a signal-killed server as success (if (!code) return), andthis.exit()threw an ExitError inside the event callback where nothing catches it, so any child failure exited 1 with a stack trace. The foreground branch now awaits the child insiderun()and exits with the child’s code (128 + signal numberfor signal deaths) through oclif’s normal exit path, socatch/finallylifecycle still applies.egg-binforkNode()rejected with “exit with code null” and the CLI always exited 1.ForkErrornow extends oclif’sCLIErrorcarrying the child’s real exit code, rejects with “was killed by signal X” for signal deaths, and setsskipOclifErrorHandlingon fork failures so the long command line prints verbatim instead of being word-wrapped by oclif’s pretty-printer.create-egg‘s custom command path ranprocess.exit(status ?? 0), masking a signal death as success. The path is latent (no template definescustomCommandyet); it now uses a non-exportedtoExitCode()helper, same fix as the upstream create-vite pattern needs.The
128 + signal numbermapping is the same verbatimtoExitCode()helper in all three packages; a shared@eggjs/utilsexport was considered and skipped to avoid widening a published API for a 4-line convention (create-egg deliberately has no workspace deps).Each fix is covered by a reproducing test written first: a mocked-spawn unit test driving the start command’s public surface, an egg-bin dev e2e with a fixture framework that SIGTERMs itself (expects “was killed by signal SIGTERM” and exit code 143, skipped on Windows), and unit tests for
toExitCode. Full egg-bin dev+test suites pass (41 tests, built first as the test-egg-bin CI job does); egg-scriptsstop.test.tsandshow helpfailures on macOS reproduce identically onnextwithout this change.Behavior note: egg-bin now exits with the forked child’s actual exit code instead of flattening every failure to 1.
Summary by CodeRabbit
Bug Fixes
Improved CLI handling when child processes fail or are terminated by signals.
Preserved nonzero exit codes and converted signal termination into standard shell exit codes.
Improved error reporting when development servers or generated applications stop unexpectedly.
Documentation
- Updated local CI guidance for running CLI tests.
Tests
Added coverage for exit codes, signal termination, and process-start failures.
Improved test reliability for slower application startup and platform-specific CI environments.
API note:
ForkError(exported from@eggjs/bin/baseCommand) now extends oclif’sCLIError; its numeric child status moved fromcodetoexitCode(CLIErrorreservescodeas a string display slot) and the constructor takesnumberinstead ofnumber | null.
版权所有:中国计算机学会技术支持:开源发展技术委员会
京ICP备13000930号-9
京公网安备 11010802047560号
English | 简体中文
Features
Quickstart
Follow the commands listed below.
Monorepo Structure
This project is structured as a utoo monorepo with the following packages:
packages/egg- Main Eggjs frameworkexamples/helloworld-commonjs- CommonJS example applicationexamples/helloworld-typescript- TypeScript example applicationsite- Documentation websiteThe monorepo uses utoo catalog mode for centralized dependency management, ensuring consistent versions across all packages.
Development Commands
Local External Services
Some DAL, ORM, Redis, and ecosystem benchmark paths need local MySQL and Redis services. Start the repository-aligned Docker services before running those tests on a clean machine:
This starts MySQL 8 and Redis 7, matching the main CI service versions, and creates the databases used by local DAL/ORM/e2e fixtures:
test,apple,banana,test_runtime_datasource,test_runtime_dao,test_dal_plugin,test_dal_standalone,cnpmcore, andcnpmcore_unittest.Useful commands:
The default host ports are
127.0.0.1:3306for MySQL and127.0.0.1:6379for Redis. If either port is already used, the start command stops before changing containers. Keep using the existing service if it is compatible with CI, or stop it and run the command again. You can change Docker host ports withEGG_DEV_SERVICES_MYSQL_PORTandEGG_DEV_SERVICES_REDIS_PORT; however, the full DAL/ORM/Redis local test path still expects the default host ports.Image overrides are available for compatibility checks:
Run
ut run dev:services:resetbefore switching MySQL image families, for example between MySQL 8 and MySQL 5.7, because MySQL data directories are not downgrade-compatible across major versions.Current hard-coded service assumptions:
plugins/redis/test/fixtures/apps/**/config.*use127.0.0.1:6379; skipped Redis plugin tests become runnable when that port is available.plugins/session/test/fixtures/redis-session/config/config.default.jsuse127.0.0.1:6379.tegg/core/dal-runtime/test/DataSource.test.tsandtegg/core/dal-runtime/test/DAO.test.tsuse local MySQL on port3306.tegg/plugin/dal/test/fixtures/apps/dal-app/modules/dal/module.ymlandtegg/standalone/standalone/test/fixtures/dal-*/module.ymluse local MySQL on port3306.tegg/plugin/orm/test/fixtures/prepare.jsandtegg/plugin/orm/test/fixtures/apps/orm-app/config/config.default.tsuse local MySQL on port3306.Documentations
Contributors
How to Contribute
Please let us know how can we help. Do check out issues for bug reports or suggestions first.
To become a contributor, please follow our contributing guide, and review the repository guidelines for day-to-day development tips.
Sponsors and Backers
License
MIT