I'm trying to get spawn
to effect an rm -rf node_modules
followed by npm install
(on windows 7; nx commands courtesy of a transparently installed CygWin. All nx commands resolve on a commandline just fine).
I initially had this using exec
, but wanted to catch the stdout/stderr information as it occurred, so I figured I'd use spawn
, and rewrote the code to use that. However, that breaks everything.
The rm
command, rewritten, became this:
var spawn = require("child_process").spawn,
child = spawn("rm", ["-rf", "node_modules"]);
child.stdout.on('data', function (data) { console.log(data.toString()); });
child.stderr.on('data', function (data) { console.log(data.toString()); });
child.on('error', function() { console.log(arguments); });
However, running this generates the following error:
rm: unknown option -- ,
Try `rm --help' for more information.
The npm
command, rewritten, became this:
var spawn = require("child_process").spawn,
child = spawn("npm", ["install"]);
child.stdout.on('data', function (data) { console.log(data.toString()); });
child.stderr.on('data', function (data) { console.log(data.toString()); });
child.on('error', function() { console.log(arguments); });
However, running this generates the following error:
{
'0': {
[Error: spawn ENOENT]
code: 'ENOENT',
errno: 'ENOENT',
syscall: 'spawn'
}
}
How do I make spawn run the same commands that worked fine using exec
without it throwing up errors all over the place? And why does this not work? Reading the API, http://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options, seems to suggest this is precisely how one is supposed to use spawn...
After lots of trying different things, I finally had a look at what "npm" actually is on windows, and it turns out to be a bash script called npm
, as well as a windows-native batch script called npm.cmd
(no idea why it's .cmd, that should be .bat, but there you have it). Windows's command resolver will see npm
, notice that it's not an executable, see npm.cmd
, and then notice that IS an executable, and will then use that instead. This is helpful when you're in a terminal, but spawn()
will not do any such resolution: passing it npm
will make it fail because it's not an executable. Passing it npm.cmd
as command, however, works just fine.
(Also, not sure why rm
was failing earlier, since that actually works correctly without any changes that I can tell. Probably misread that as part of the problem when in fact it wasn't.)
So: if you run into spawn
saying ENOENT in windows, when the command you're trying to trigger works in a plain command prompt, find out if the command you're calling is a true executable, or whether there's a .bat
/.cmd
file that the command prompt will "helpfully" run for you instead. If so, spawn that.
edit
since this post is still getting upvotes, a good way to ensure the command always works is to bootstrap it based on process.platform
, which will be win32
for windows.
var npm = (process.platform === "win32" ? "npm.cmd" : "npm"),
child = spawn(npm, ["install", ...]);
...
edit specific to the use-case that triggered this error
since posting this question (and its answer), several packages have been released that allow running npm
tasks without having to rely on exec or spawn, and you should use them instead.
Probably the most popular is npm-run-all which doesn't just give you the power to run any npm
task from other npm scripts as well as from Node, but also adds commands to run multiple npm scripts in series or in parallel, with or without wildcards.
In the context of the original question, where the error got thrown because I was trying to run npm
as an exec/spawn in order to effect a cleanup and reinstall, the modern solution is to have a dedicated cleaning task in package.json:
{
...
"scripts": {
"clean": "rimraf ./node_modules",
...
},
...
}
And to then invoke that clean
task followed by the install command on the command line as
> npm run clean && npm install
Or, from inside some Node script, using:
const runAll = require("npm-run-all");
...
runAll(["clean", "install"])
.then(() => {
console.log("done!");
})
.catch(err => {
console.log("failed!");
});