nano-spawn

repository·main·Indexed 20 days ago

https://github.com/sindresorhus/nano-spawn

A tiny, dependency-free Promise-based wrapper around Node.js's child_process module. It provides a human-friendly API for executing commands, supporting async iteration over stdout and stderr lines, command piping via .pipe(), and support for local binaries. It is designed for environments where package size is a primary concern, such as libraries or serverless functions.

Tokens
2.1K
Snippets
8
Records
15
Agent score
70%

What's inside nano-spawn

  1. Compare nano-spawn with Execa

    main

    If you need a more feature-rich subprocess execution tool, consider using Execa.

    When to use Execa:

    • In scripts, servers, or applications where package size is not a primary concern.
    • When you need advanced features like template string syntax, synchronous execution, file/binary I/O, advanced piping, graceful/forceful termination, or IPC.

    When to use nano-spawn:

    • When your environment requires small packages (e.g., when building a library or a serverless function).
  2. Iterate over output lines

    main

    You can iterate over the output lines of a subprocess using for await...of syntax.

    • Use subprocess.stdout to iterate over standard output lines.
    • Use subprocess.stderr to iterate over standard error lines.
    • Use subprocess[Symbol.asyncIterator]() to iterate over interleaved stdout and stderr lines (similar to terminal output).

    Iteration automatically waits for the subprocess to end and will throw a SubprocessError if the process fails.

    for await (const line of spawn('ls', ['--oneline'])) {
    	console.log(line);
    }
  3. Configure spawn options

    main

    The spawn() function accepts an options object. It supports all standard node:child_process options (like shell, timeout, cwd, env, etc.).

    Key specific options include:

    • env: An object to override specific environment variables. Other variables are inherited from process.env.
    • preferLocal: A boolean that, when true, allows executing binaries installed locally via npm or yarn without needing npx.
    • stdin, stdout, stderr: Defines how standard streams are handled. Common values include:
      • 'pipe' (default): Returns output via result.stdout, result.stderr, and result.output.
      • 'inherit': Uses the current process's streams (useful for terminal interaction).
      • 'ignore': Discards the stream.
      • Stream: Redirects to/from a Node.js stream.
      • {string: '...'}: Passes a specific string as stdin input.
  4. Run a command using node:child_process

    main

    If you choose to use the native Node.js module instead of nano-spawn, you can use promisify with execFile to execute commands using async/await syntax.

    import {execFile} from 'node:child_process';
    import {promisify} from 'node:util';
    
    const pExecFile = promisify(execFile);
    
    const result = await pExecFile('npm', ['run', 'build']);
  5. Run basic commands with spawn()

    main

    Use the default export spawn(file, arguments?, options?) to execute a command. It returns a Subprocess object which is a Promise that resolves to a Result object upon successful completion.

    If file is 'node', the current Node.js version and flags are automatically inherited.

    import spawn from 'nano-spawn';
    
    const result = await spawn('echo', ['🦄']);
    
    console.log(result.output);
    //=> '🦄'
  6. Access the underlying Node.js ChildProcess

    main
    If you need low-level control, you can access the raw Node.js ChildProcess instance via the nodeChildProcess property on the Subprocess object. This allows you to use methods like .kill() or .send() for IPC.
  7. Pipe commands together

    main

    Use the .pipe(file, arguments?, options?) method to pipe the stdout of one subprocess into the stdin of another, similar to the | operator in shells. You can chain multiple .pipe() calls. The final result resolves with the result of the last subprocess in the chain.

    const result = await spawn('npm', ['run', 'build'])
    	.pipe('sort')
    	.pipe('head', ['-n', '2']);
  8. Handle SubprocessError

    main

    If a subprocess fails (non-zero exit code or terminated by a signal), the promise will reject with a SubprocessError. You can check for this using instanceof SubprocessError.

    SubprocessError properties:

    • exitCode: The numeric exit code (undefined if the process couldn't start or was killed by a signal).
    • signalName: The name of the signal (e.g., SIGTERM) that terminated the process.
    • isCanceled: true if the subprocess was canceled using the signal option.
  9. Understand the Result object

    main

    When a subprocess succeeds, the resolved Result object contains:

    • stdout: The standard output string (trailing newlines are stripped).
    • stderr: The standard error string (trailing newlines are stripped).
    • output: Interleaved stdout and stderr as a single string.
    • command: The command and arguments executed (for logging/debugging).
    • durationMs: The execution duration in milliseconds.
    • pipedFrom: If .pipe() was used, this contains the Result or SubprocessError from the preceding subprocess in the chain.
  10. Iterate over subprocess output lines

    main

    A Subprocess is an AsyncIterable. You can use for await...of to iterate over each line of stdout or stderr as soon as it becomes available. The iteration automatically waits for the subprocess to end and will throw if the subprocess fails.

    import spawn from 'nano-spawn';
    
    for await (const line of spawn('ls', ['--oneline'])) {
    	console.log(line);
    }