What is spawn-wrap?
The spawn-wrap npm package is used to wrap child_process.spawn calls, allowing for modifications to the environment, arguments, and executable used in child processes. This is particularly useful for instrumenting child processes or modifying their behavior in a consistent way across a project.
What are spawn-wrap's main functionalities?
Environment Variable Modification
This feature allows you to prepend command line options to node processes, enabling features like harmony in all spawned child processes.
const wrap = require('spawn-wrap');
wrap(['--harmony']);
require('child_process').spawn('node', ['script.js']);
Executable Wrapping
This feature allows you to specify a custom executable (like a different version of Node.js) to be used for all child processes.
const wrap = require('spawn-wrap');
const wrappers = ['/path/to/custom/node'];
wrap(wrappers);
require('child_process').spawn('node', ['script.js']);
Other packages similar to spawn-wrap
cross-spawn
cross-spawn is similar to spawn-wrap in that it also enhances the functionality of child_process.spawn. However, cross-spawn focuses more on cross-platform compatibility, ensuring that spawned processes work similarly across different operating systems, unlike spawn-wrap which focuses on wrapping and modifying spawn calls.
forever-monitor
forever-monitor is a package that deals with managing and monitoring multiple child processes. While it provides some overlapping functionality with spawn-wrap in terms of managing child processes, its primary focus is on keeping these processes running continuously, which is different from the modification and instrumentation focus of spawn-wrap.
spawn-wrap
Wrap all spawned Node.js child processes by adding environs and
arguments ahead of the main JavaScript file argument.
Any child processes launched by that child process will also be
wrapped in a similar fashion.
This is a bit of a brutal hack, designed primarily to support code
coverage reporting in cases where tests or the system under test are
loaded via child processes rather than via require()
.
It can also be handy if you want to run your own mock executable
instead of some other thing when child procs call into it.
USAGE
var wrap = require('spawn-wrap')
var unwrap = wrap(['/path/to/my/main.js', 'foo=bar'], { FOO: 1 })
unwrap()
In this example, the /path/to/my/main.js
file will be used as the
"main" module, whenever any Node or io.js child process is started,
whether via a call to spawn
or exec
, whether node is invoked
directly as the command or as the result of a shebang #!
lookup.
In /path/to/my/main.js
, you can do whatever instrumentation or
environment manipulation you like. When you're done, and ready to run
the "real" main.js file (ie, the one that was spawned in the first
place), you can do this:
setupInstrumentationOrCoverageOrWhatever()
process.on('exit', function (code) {
storeCoverageInfoSynchronously()
})
require('spawn-wrap').runMain()
CONTRACTS and CAVEATS
The initial wrap call uses synchronous I/O. Probably you should not
be using this script in any production environments anyway.
Also, this will slow down child process execution by a lot, since
we're adding a few layers of indirection.
The contract which this library aims to uphold is:
- Wrapped processes behave identical to their unwrapped counterparts
for all intents and purposes. That means that the wrapper script
propagates all signals and exit codes.
- If you send a signal to the wrapper, the child gets the signal.
- If the child exits with a numeric status code, then the wrapper
exits with that code.
- If the child dies with a signal, then the wrapper dies with the
same signal.
- If you execute any Node child process, in any of the various ways
that such a thing can be done, it will be wrapped.
- Children of wrapped processes are also wrapped.
(Much of this made possible by
foreground-child.)
There are a few ways situations in which this contract cannot be
adhered to, despite best efforts:
- In order to handle cases where
node
is invoked in a shell script,
the PATH
environment variable is modified such that the the shim
will be run before the "real" node. However, since Windows does
not allow executing shebang scripts like regular programs, a
node.cmd
file is required. - Signal propagation through
dash
doesn't always work. So, if you
use child_process.exec()
on systems where /bin/sh
is actually
dash
, then the process may exit with a status code > 128 rather
than indicating that it received a signal. cmd.exe
is even stranger with how it propagates and interprets
unix signals. If you want your programs to be portable, then
probably you wanna not rely on signals too much.- It is possible to escape the wrapping, if you spawn a bash
script, and that script modifies the
PATH
, and then calls a
specific node
binary explicitly.