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!