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

@antora/run-command-helper

v1.0.0

Published

Provides a helper function to run an external command uniformly across platforms with numerous options to control how stdio is handled.

Downloads

5,602

Readme

@antora/run-command-helper

A Node.js module that provides a helper function to run an external command consistently across platforms. This function is a Promise-based wrapper around the spawn function in Node.js. It’s goal is to eliminate the boilerplate code needed to use the spawn function correctly. Developed for use in Antora.

Install

$ npm i @antora/run-command-helper

API

function runCommand (cmd[, args][, opts])

Runs the specified command. If the command exits with a zero status, the function will return an object containing information from the subprocess (e.g., exit status, stdout data, etc). If the command exits with a non-zero status, or the command fails to start, an Error is thrown.

  • cmd <string> - The external command to run. This parameter is required.
  • args <string[]> - List of string arguments to pass to command. This parameter is optional. If this parameter is not provided, the arguments list is parsed from the value of the cmd argument (unless the parse option is false). If any arguments are parsed from the value of the cmd argument, this list of arguments gets appended to those. Default: undefined.
  • opts <Object> - Named parameters to control how the command is run. All named parameters are optional. Default: {}.
    • cwd <string> - The directory in which the subprocess will be run. This value also controls the search path for the command if local (or any bare command on Windows, regardless). Default: process.cwd().
    • shell <boolean> | <string> - Specifies whether to run the external command in a shell (uses '/bin/sh' on Unix and process.env.ComSpec on Windows). If the shell option is false or not set, the command only has to be quoted if it’s being parsed. Otherwise, the command and its arguments must be properly escaped for use in a shell, regardless of how they are specified. In this case, the command and its arguments will also be subject to shell expansion as well. On Unix, you can use a string value to specify the shell explicitly (e.g., ’/bin/bash') When using a shell, environment variable references in the form $NAMEwill be resolved in the command and its arguments, even on Windows. **Default:**false`.
    • parse <boolean> - Specifies whether the value of the cmd argument should be parsed as a command string (meaning the list of arguments are extracted from the command). If the value of the cmd argument is parsed, then the base call must be properly quoted (e.g., it must be quoted if it contains spaces). Default: false if the args parameter is specified, true otherwise.
    • local <boolean> - Specifies whether to look for the command locally instead of on the PATH. Setting this option to true is equivalent to prepending ./ to the command. This option is only relevant for a bare command (i.e., a command with no directory separators). If the command (i.e., the base call) contains directory separators, the value true is implied. Default: false.
    • stdin <string> | <Buffer> | <Stream.Readable> - Input to pass to the stdin of the process, if specified and not null, false, or undefined. If an unknown value type is specified, it will be coerced to a string. Default: undefined.
    • stdout <string> - Specifies how to handle the stdout of the subprocess. A value of ’print' will pass through the data to the stdout stream of the current process. A value of ’ignore' will discard the stdout data. A value of ’buffer'will cause the function to return the stdout data as aStringif theencoding option is set and not ’buffer', otherwise a Buffer. All other values (such as false) are the equivalent of ’ignore'. **Default:** ’buffer'
    • stderr <string> - Specifies how to handle the stderr stream of the subprocess. A value of ’print' will pass through the data to the stderr of the current process. A value of ’ignore' will discard the stderr data. A value of ’buffer' will append the stderr data to the end of the message of the Error if the subprocess fails, otherwise it’s ignored. A value of ’stdout will redirect the stderr data to the stdout destination. All other values (such as false) are the equivalent of ’ignore'. **Default:** ’buffer'
    • encoding <string> - The encoding used for all stdio outputs in the return value. If set and not ’buffer', each stdio output buffer will be encoded to a string using the specified encoding. If the value of the stdout option is ’buffer', the return value of the function will change from a Buffer to a String. Note that this option does not impact the data type passed to the data event handler on the process. Default: ’buffer'`
  • Returns: <Object> | <Buffer> - Process information. If the stdout option is set to buffer, this object will be built on a copy of the stdout object for convenience.
    • stdout <Buffer> | <null> - The stdout data as a Buffer (or String if the encoding option is not buffer) if the stdout option is set to buffer, otherwise null.
    • stderr <Buffer> | <null> - The stderr data as a Buffer (or String if the encoding option is not buffer) if the stderr option is set to buffer, otherwise null.
    • status <number> | <null> - The exit status of the subprocess or null if the subprocess was terminated by a signal.
    • signal <string> | <null> - The exit signal of the subprocess if the subprocess was terminated by a signal, otherwise null.

All other options, including env, are passed through to the underlying spawn function call.

Windows considerations

On Windows, the command may use forward slashes, but the resolved command will always have backslashes. Also on Windows, the command can be resolved in the specified cwd even if the local option is false or not set (matching the native behavior on Windows).

On Windows, when the shell option is not set, the function will attempt to resolve the command without a file extension using the .com, .exe, .bat, and .cmd file extensions, in that order. As a result, the base call shown in messages will have a file extension appended even though it was not originally specified. This modification is necessary in order to ensure the spawn function invokes the correct command. (The spawn function in Node.js does not resolve the base call using the PATHEXT environment variable when the shell option is not true). If a match is not found, the function will throw a command not found error.

📌 NOTE
This function does its own PATHEXT resolution on Windows. It never passes an extensionless command to spawn to ensure its behavior is predictable and within the documented parameters.

On Windows, the shell will be used under the covers (even when not specified) to run batch scripts due to a security restriction in Node.js. The function hides this detail by automatically escaping the arguments and thus does not impose additional escaping requirements when the shell option is false or not set. The function escapes the following characters using a double quote enclosure: " <>&%^|.

🔥 CAUTION
There’s still a risk that a batch script cannot read argument values correctly. This is due to limitations in how a batch script processes arguments. For example, a batch script could cause a child batch script to not be able to handle the sequence ="& depending on how arguments are forwarded to the child script.

On Windows, builtins like dir and set can only be used when shell: true (this is a limitation of Node.js’ spawn function).

In order to use non-ASCII characters in argument values on Windows (particularly when using the shell), it may be necessary to enable UTF-8 support. Here’s an example to test whether UTF-8 support is properly configured:

const runCommand = require('@antora/run-command-helper')

;(async () => {
  await runCommand('echo', ['ACME\u2122'], { stdout: 'print', stderr: 'print', shell: true })
})()
//=> ACME™

If that still don’t work, try setting chcp 65001 as well.

Usage

Here’s an example of running a command that routes the stdout to the stdout stream of the current process.

const runCommand = require('@antora/run-command-helper')

;(async () => {
  await runCommand('echo hi', { stdout: 'print', stderr: 'print' })
})()
//=> hi

Here’s an example of running a command that returns the stdout data as a Buffer.

const runCommand = require('@antora/run-command-helper')

;(async () => {
  const whoiam = await runCommand('whoami')
  process.stdout.write(whoiam)
  // or console.log(whoiam.stdout.toString().trimEnd())
})()
//=> myusername

Here’s an example of running a command that returns the stdout data as a String.

const runCommand = require('@antora/run-command-helper')

;(async () => {
  const whoiam = await runCommand('whoami', { encoding: 'utf8' })
  console.log(whoiam.trimEnd())
  // or process.stdout.write(Buffer.from(whoami))
})()
//=> myusername

Here’s an example of how to pass in the list of arguments instead of relying on them being parsed from the command string:

const runCommand = require('@antora/run-command-helper')

;(async () => {
  await runCommand(process.execPath, ['-p', '1'], { stdout: 'print' })
})()
//=> 1

Here’s an example of how to specify a local command instead of looking for the command on the PATH:

const runCommand = require('@antora/run-command-helper')

;(async () => {
  await runCommand('gradlew --status', { local: true, stdout: 'print', stderr: 'print' })
})()
//=> No Gradle daemons are running.

Here’s an example of how to pass input to the stdin stream of the command:

const runCommand = require('@antora/run-command-helper')
const { EOL } = require('node:os')

;(async () => {
  const cat = process.platform === 'win32' ? 'findstr x*' : 'cat'
  await runCommand(cat, { stdin: 'hi there!' + EOL, stdout: 'print' })
})()
//=> hi there!

Stdio Stream Handling

When the value of the stdout or stderr option is ’print'`, this function routes the respective stream directly to the current process (i.e., parent, invoking process). This is accomplished by passing the process.stdout and process.stderr streams to the respective stdio option provided by the spawn function. (This is roughly equivalent to using the inherit option, except it allows the tests to intercept stream writes). In other words, this function does not attempt to capture the stdout and stderr streams and emit them to the current process in this case.

If the function were to capture and emit the data from the two streams, it would alter how the lines are interleaved in the terminal. The reason that happens is because the stdout buffer is much larger than the stderr buffer, which results in the stderr data being emitted earlier relative to the stdout data than as received. (See https://stackoverflow.com/questions/57046930/node-js-spawn-keep-stdout-and-stderr-in-the-original-order). This function avoids that problem by connecting the streams directly to the current process when configured to print them.

This behavior is only relevant when both streams are printed. Otherwise, how the streams are wired does not matter.

Copyright and License

Copyright (C) 2024-present by OpenDevise Inc. and the individual contributors to Antora.

Use of this software is granted under the terms of the Mozilla Public License Version 2.0 (MPL-2.0).