tl;dr
On Windows, npm
creates the wrapper batch file (*.cmd
) based on whatever shell/interpreter is specified in the script file's shebang line.
What you're seeing is npm's emulation of the Unix shebang mechanism for Windows, where the very 1st line of an executable plain-text file (script), if it starts with magic characters #!
, tells the OS what interpreter/shell to pass the script to for execution (see this answer of mine for more information).
Since Windows doesn't support shebang lines, npm
creates a wrapper *.cmd
(batch) file, that explicitly invokes the "binary" file (the script file specified in the bin
key of package.json
) with whatever executable is specified in the script file's shebang line.
In other words: npm
parses the script's shebang line to determine what shell/interpreter to invoke, and creates the wrapper batch script accordingly.
If we peek at ./bin/jade.js
inside the jade
package, we see #!/usr/bin/env node
as the shebang line, which is why the jade.cmd
wrapper file invokes node.exe
.
This is the typical case: a script written in JavaScript that must be executed by the Node.js engine.
However, any other shell/interpreter may be specified too, but doing so only makes sense if a given shell/interpreter is also available on Windows; for node
, that is a given, but, as the contents of mocha-casper.cmd
shows, it makes no sense with the Unix default shell, /bin/sh
(the ./bin/mocha-casperjs
file from package mocha-casper.js
contains shebang line #!/bin/sh
).
The makers of the mocha-casperjs
have chosen to roll their own Windows integration by re-implementing the Unix shell script for cmd.exe
as mocha-casperjs.bat
(also a batch file) - however, since npm
is unaware of this, this batch file is not placed in the PATH (global) / not discoverable by name only (when calling CLIs in a project context).
More generally, for scripts to also work on Windows, it doesn't make sense to use shebang lines with absolute, POSIX-style paths - EXCEPT if the target shell/interpreter is specified by indirect invocation via /usr/bin/env
, which signals to npm
that it should be looked for in the PATH (again, see this answer of mine for more information).
(Additionally, the wrapper batch files also look in their own directory for the executable, but that is rarely helpful, given that npm
wouldn't copy them there even if you explicitly added them as an entry in your package.json
's bin
key.)
As an aside: recent versions of npm
also create extension-less wrapper scripts for Unix environments, notably Cygwin, and Bash on Ubuntu on Windows.
When creating your own packages, for CLIs ("binaries") - directly executable scripts that act like command-line utilities - that are part of an npm package to work on Windows too, you must:
Add a shebang line to your scripts - even if you only ever intend to run them on Windows.
Define that shebang line as #!/usr/bin/env <interpreter-executable-filename-without-extension>
; typically - if your script is written in JavaScript and must be executed with node
- use:
#!/usr/bin/env node
In your package.json
file's bin
key, define the script's key without extension., because .cmd
is directly appended to the key name when the wrapper batch file is created (on Unix, a symlink is created named for the key as-is); e.g.:
"bin": { "foo": "./bin/fooj.js" }
Caveat: If the script pointed to by the bin
key in package.json
has extension .js
but has no shebang line, the wrapper script will invoke the .js
file directly, which will by default execute it with WSH (specifically, with JScript, its JavaScript engine) rather than Node.js, which won't work as intended - see this question for what symptoms you would see.