Why you should always quote your globs in NPM scripts.

I’ve spent the last weekend fighting with dozens of issues with NPM lifecycle scripts in several projects. Results from ESLint, TSLint, Mocha and other tools were inconsistent across our development, staging and testing environments. What seemed like misconfiguration ended up in a few a-ha moments.

NPM run-scripts don’t use the shell of the user who runs the command.

NPM lifecycle uses `sh -s` to run commands on OS X/Linux and `cmd /d /s /c` on Windows. This means that local `/bin/sh` is used or whatever shell it points to (/bin/sh in many cases is just a symlink). By default this is the Bash shell on OS X and many Linux distributions.

This leads to not-so-obvious consequences.

OS X ships with bash-3.2 but globstar (** → globstar matches zero or more directories and subdirectories) is supported starting from Bash version 4.0. Windows command shell also doesn’t support wildcard expansion.

If you are using zsh, some other modern shell or have upgraded your bash to version 4, you might be used to executing commands like `eslint src/**/*.js`. What’s important here is that when you run it like this:

eslint src/**/*.js

it’s your shell that expands the glob. But when you use this as a NPM script, NPM is using /bin/sh (so Bash 3.2 on OS X) that doesn’t recognize the ** expression and treats the glob as `src/*/*.js`. No top-level directory or subdirectories.

Best solution is to use node-glob package and most popular tools like ESLint already relies on it. The trick is to quote the glob pattern. This way it’s not the shell that expands the glob but ESLint (or some other tool) using node-glob.

eslint 'src/**/*.js'

or as a NPM script

...
"scripts": {
"lint": "eslint 'src/**/*.js'"
},
...

Do NPM scripts run cross-platform?

As I’ve mentioned earlier it’s your operating system’s command line that runs your NPM scripts. So on Linux and OSX, your NPM scripts run on a Unix command line and on Windows, NPM scripts run on the Windows command line. If you want your build scripts to support all platforms, you need to handle both Unix and Windows.

This can be easily overlooked but solving this is quite simple. You can:

Summary

Those findings might be common knowledge, but it took me some time to connect all the dots.

For consistent experience with NPM lifecycle scripts you should:

  • Always quote the globs in your NPM scripts when using ESLint, TSLint, Mocha or other tools that use node-glob,
  • Use cross-platform commands or node packages instead of commands,
  • Double-check if a command or expression is well supported across different shells/shell versions/operating systems (e.g. globstar),

Example

...
"scripts": {
"clean": "rimraf .nyc_output coverage",
"test": "mocha 'tests/**/*spec.js'",
"lint": "eslint '{src,test}/**/*.js'"
},
...

Found issues or errors? Do you know a better or easier method? Let me know!

Thanks for reading! Please get in touch with me on Twitter if you run into any issues. And, please share this with others that you think would find it useful.

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.