Get and use local executables through NPM or Yarn

  • report
    Disclaimer
    Click for Disclaimer
    This Post is over a year old (first published about 3 years ago). As such, please keep in mind that some of the information may no longer be accurate, best practice, or a reflection of how I would approach the same thing today.
  • infoFull Post Details
    info_outlineClick for Full Post Details
    Date Posted:
    Nov. 09, 2020
    Last Updated:
    Nov. 09, 2020
  • classTags
    classClick for Tags

Here is a quick post on a common question; how do you get the path (and/or execute) a local executable (or cmd alias) that is installed locally via yarn or npm?

Getting the Binary Folder

Both Yarn and NPM have a command to get the local binary directory:

# NPM
npm bin

# Yarn
yarn bin

# Sample output: `C:/temp/my-project/node_modules/.bin

However, this command comes with a huge disclaimer; it returns the path where executables will be installed (if called from current working directory), not necessarily where they are already installed in a nearest subdirectory.

For example, with this folder structure:

  • project/
    • README.md
    • src/
      • node_modules/
      • package.json

… running npm bin in project root will echo project/node_modules/.bin, instead of the existing path of project/src/node_modules/.bin. Thus, it is always a good idea to cd to the directory with your project.json file before trying to grab the path, or prefixing.

In general, the binary folder should always be $PWD/node_modules/.bin, so you can also just hard-code the path in shell scripts and/or JS scripts.

Running a Local Binary

NPX

Many devs already know about this feature, so I’ll be brief on it; you can use npx {command} to target a package installed locally through package.json.

By default, npx will try to use a local install first, and if it can’t find it, it will download and execute a remote source. If you want to stop this, and have it just fail if the local copy does not exist, you can use the --no-install flag.

Variable Substitution

If for some reason you can’t use NPX, another option for shell scripting is to use variable substitution to capture the binary path and use it.

# Executing binary directly
$(npm bin)/local-binary
# Can use just like normal
$(npm bin)/local-binary --flag moreArgs

# Passing in package name with `yarn bin`
$(yarn bin my-package)
$(yarn bin my-package) --flag moreArgs

If you are on Windows, and you don’t have git-bash or anything like that installed, this should work in cmd:

for /f %%i in ('npm bin') do set NPM_BIN_PATH=%%i
%NPM_BIN_PATH%/my-package

With both bash and CMD, you can always just hard-code the path of the binary folder and use it that way too.

With NodeJS child_process

If you are using NodeJS’s child_process to execute binaries from JavaScript code (e.g. with .exec()), you might be wondering how to get it to see locally installed binaries from node_modules.

One option is to use a library, such as execa, which makes this easier.

The vanilla approach is to combine the system PATH with the local binary path, and pass that combined string as part of the env object in child_process method options:

/**
 * Example is using `supports-color` package, which is installed locally
 * We are going to extract the help text and log it
 */

// Assumes script lives in same directory as package.json
const LOCAL_BIN_PATH = require('path').normalize(`${__dirname}/node_modules/.bin`);

// Execute a local binary / command
const helpText = require('child_process').execSync(`supports-color --help`,{
    env: {
        ...process.env,
        path: `${process.env.path};${LOCAL_BIN_PATH}`
    }
}).toString();
console.log(helpText);

Wrap-Up

I hope this helps someone out there! Do you know any other tricks for using local binaries? Feel free to share below in the comments!

Leave a Reply

Your email address will not be published.