npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

forkexec

v1.1.1

Published

sane child process library

Downloads

27

Readme

forkexec: sane child process library

This library provides somewhat saner interfaces to Node's child_process module. It's still growing, and most of the interfaces there don't have analogs here yet.

The interfaces in this library conform to Joyent's Best Practices for Error Handling in Node.js. Most notably:

  • most arguments are passed via named properties of an "args" object, and
  • passing invalid arguments into the library results in thrown exceptions that should not be caught. Don't pass bad values in.

The only interfaces currently provided are:

  • forkExecWait(args, callback): like child_process.execFile, but all operational errors are emitted asynchronously, errors are more descriptive, and there's a crisper summary of exactly what happened.
  • interpretChildProcessResult(args): lower-level function for taking the result of one of Node's child_process functions and producing a normalized summary of what happened.

One of the biggest challenges in using Node's child_process interfaces is properly interpreting the result. When you kick off a child process (as with fork/exec), there are basically four possible outcomes:

  1. Node failed to fork or exec the child process at all. (error is non-null, status is null, and signal is null)
  2. The child process was successfully forked and exec'd, but terminated abnormally due to a signal. (error is non-null, status is null, and signal is non-null)
  3. The child process was successfully forked and exec'd and exited with a status code other than 0. (error is non-null, status is a non-zero integer, and signal is null).
  4. The child process was successfully forked and exec'd and exited with a status code of 0. (error is null, status is 0, and signal is null.)

Most code doesn't handle (1) at all, since it usually results in the child_process function throwing an exception rather than calling your callback. Most use-cases want to treat (1), (2), and (3) as failure cases and generate a descriptive error for them, but the built-in Errors are not very descriptive.

The interfaces here attempt to make the common case very easy (providing a descriptive, non-null Error in cases (1) through (3), but not (4)), while still allowing more complex callers to easily determine exactly what happened. These interfaces do this by taking the result of the underlying Node API function and producing an info object with properties:

  • error: null if the child process was created and terminated normally with exit_status 0. This is a non-null, descriptive error if the child process was not created at all, if the process was terminated abnormally by a signal, or if the process was terminated normally with a non-zero exit status.
  • status: the wait(2) numeric status code if the child process exited normally, and null otherwise.
  • signal: the name of the signal that terminated the child process if the
  • child process exited abnormally as a result of a signal, or null otherwise

forkExecWait

Like the built-in child_process.execFile, this function forks a child process, exec's the requested command, waits for it to exit, and captures the full stdout and stderr. The file should be an executable on the caller's PATH. It is not passed through bash -c as would happen with child_process.exec.

Arguments

forkExecWait(args, callback)

The main argument is:

  • argv (array of string): command-line arguments, including the command name itself. If you want to run "ls -l", this should be [ 'ls', '-l' ].

The following arguments have the same semantics as for Node's built-in child_process.execFile except where otherwise noted:

  • timeout (int: milliseconds): maximum time the child process may run before SIGKILL will be sent to it. If 0, then there is no timeout. (Note that Node lets you override the signal used and defaults to SIGTERM. This interface always uses SIGKILL.)
  • cwd (string): working directory
  • encoding (string): encoding for stdout and stderr
  • env (object): environment variables
  • maxBuffer (int): bytes of stdout and stderr that will be buffered
  • uid (int): uid for child process
  • gid (int): gid for child process
  • includeStderr (boolean): if the process exits with a non-zero status, the output of stderr will be trimmed and included in the error message. Defaults to false.

Return value

The return value is the same as child_process.execFile except when that function would throw an exception, in which case this function will return null and the error that would have been thrown is instead emitted to the callback (as you'd probably have expected Node to do).

Callback

The callback is invoked as callback(err, info), where info always has properties:

  • error, status, signal: see description of "info" object above. info.error is the same as the err argument.
  • stdout: the string contents of the command's stdout. This is unspecified if the process was not successfully exec'd.
  • stderr: the string contents of the command's stderr. This is unspecified if the process was not successfully exec'd.

Error handling

As described above, the interface throws on programmer errors, and these should not be handled. Operational errors are emitted asynchronously. See the four possible outcomes described above for what those are.

Examples

Normal command:

forkExecWait({
    'argv': [ 'echo', 'hello', 'world' ]
}, function (err, info) {
    console.log(info);
});
{ error: null,
  status: 0,
  signal: null,
  stdout: 'hello world\n',
  stderr: '' }

Successful fork/exec, command fails:

forkExecWait({
    'argv': [ 'grep', 'foobar' '/nonexistent_file' ]
}, function (err, info) {
    console.log(info);
});
{ error: 
   { [VError: exec "grep foobar /nonexistent_file": exited with status 2]
     jse_shortmsg: 'exec "grep foobar /nonexistent_file"',
     jse_summary: 'exec "grep foobar /nonexistent_file": exited with status 2',
     jse_cause: 
      { [VError: exited with status 2]
        jse_shortmsg: 'exited with status 2',
        jse_summary: 'exited with status 2',
        message: 'exited with status 2' },
     message: 'exec "grep foobar /nonexistent_file": exited with status 2' },
  status: 2,
  signal: null,
  stdout: '',
  stderr: 'grep: /nonexistent_file: No such file or directory\n' }

Failed fork/exec: command not found:

forkExecWait({
    'argv': [ 'nonexistent', 'command' ]
}, function (err, info) {
    console.log(info);
});
{ error: 
   { [VError: exec "nonexistent command": spawn nonexistent ENOENT]
     jse_shortmsg: 'exec "nonexistent command"',
     jse_summary: 'exec "nonexistent command": spawn nonexistent ENOENT',
     jse_cause: 
      { [Error: spawn nonexistent ENOENT]
        code: 'ENOENT',
        errno: 'ENOENT',
        syscall: 'spawn nonexistent',
        path: 'nonexistent',
        cmd: 'nonexistent command' },
     message: 'exec "nonexistent command": spawn nonexistent ENOENT' },
  status: null,
  signal: null,
  stdout: '',
  stderr: '' }

Failed fork/exec: command is not executable (note: Node throws on this, while this library emits an error asynchronously, since this is an operational error):

forkExecWait({
    'argv': [ '/dev/null' ]
}, function (err, info) {
    console.log(info);
});
{ error: 
   { [VError: exec "/dev/null": spawn EACCES]
     jse_shortmsg: 'exec "/dev/null"',
     jse_summary: 'exec "/dev/null": spawn EACCES',
     jse_cause: { [Error: spawn EACCES] code: 'EACCES', errno: 'EACCES', syscall: 'spawn' },
     message: 'exec "/dev/null": spawn EACCES' },
  status: null,
  signal: null,
  stdout: '',
  stderr: '' }

Command times out (killed by our SIGKILL after 3 seconds):

forkExecWait({
    'argv': [ 'sleep', '4' ],
    'timeout': 3000,
}, function (err, info) {
    console.log(info);
});
{ error: 
   { [VError: exec "sleep 2": unexpectedly terminated by signal SIGKILL]
     jse_shortmsg: 'exec "sleep 2"',
     jse_summary: 'exec "sleep 2": unexpectedly terminated by signal SIGKILL',
     jse_cause: 
      { [VError: unexpectedly terminated by signal SIGKILL]
        jse_shortmsg: 'unexpectedly terminated by signal SIGKILL',
        jse_summary: 'unexpectedly terminated by signal SIGKILL',
        message: 'unexpectedly terminated by signal SIGKILL' },
     message: 'exec "sleep 2": unexpectedly terminated by signal SIGKILL' },
  status: null,
  signal: 'SIGKILL',
  stdout: '',
  stderr: '' }

interpretChildProcessResult

This lower-level function takes the results of one of the child_process functions and produces the info object described above, including a more descriptive Error (if there was one).

Arguments

interpretChildProcessResult(args)

Named arguments are:

  • label (string): label for the child process. This can be just the command (e.g., "grep"), the full argument string (e.g., "grep foo /my/files"), a human-readable label (e.g., "grep subprocess"), or whatever else you want to report with an optional error.
  • error (optional Error): error object reported by one of Node's child_process functions. Per the Node docs, this should be either null or an instance of Error.

Return value

The return value is an info object with the error, status, and signal properties described above.

Error handling

As described above, the interface throws on programmer errors, and these should not be handled. There are no operational errors for this interface.

Contributions

Contributions welcome. Code should be "make prepush" clean. To run "make prepush", you'll need these tools:

  • https://github.com/davepacheco/jsstyle
  • https://github.com/davepacheco/javascriptlint

If you're changing something non-trivial or user-facing, you may want to submit an issue first.