34

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...

Mike 'Pomax' Kamermans
  • 40,046
  • 14
  • 84
  • 126

3 Answers3

70

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!");
    });
Mike 'Pomax' Kamermans
  • 40,046
  • 14
  • 84
  • 126
  • 1
    A `.cmd` file is [almost the same thing](http://stackoverflow.com/questions/148968/windows-batch-files-bat-vs-cmd) but not quite. – tadman Jul 09 '13 at 20:51
  • 1
    You could have used `exec("npm install ...")` which actually works on windows where `spawn` fails. Although `exec` won't give you live stdout/stderr data (AFAIK). – huysentruitw Oct 02 '13 at 08:23
  • 2
    no I couldn't have; as you point out, exec doesn't have stdout and stderr handling, and an npm install without the log is pretty useless. – Mike 'Pomax' Kamermans Oct 02 '13 at 14:36
  • (Just another note to `.cmd` vs `.bat`: `.cmd` has actually been _preferred_ since `CMD.EXE` (Windows NT) superseded the obsolete `COMMAND.COM` (DOS) shell interpreter (like 20 years ago). This better reflects the intent of being a "new-generation" shell script, in addition to the subtle differences (mentioned by @tadman), which make using the .CMD extension more "predictable". That being said, almost any other shell scripting choice would still be a lot more preferred to either .CMD or .BAT... ;) ) – Sz. Feb 15 '14 at 12:09
  • 3
    Try doing `child = spawn('cmd', ['/c', 'rm -rf node_modules'], {env:process.env});` on windows. – Nick Sotiros May 18 '14 at 07:24
  • can you explain the `env:process.env` bit? – Mike 'Pomax' Kamermans May 18 '14 at 16:33
  • @NickSotiros the concept's good, but shouldn't you still separate the parameters of `rm`? – Camilo Martin Dec 20 '14 at 14:27
  • No you do not need to split 'rm -rf node_modules' into 'rm', '-rf', 'node_modules' because the entire entry is being passed as the 2nd argument to cmd. cmd will parse and split it for you. {env:process.env} tells spawn to execute with the parents environment variables inherited, i.e. 'PATH' will be set so cmd can find 'rm'. – Nick Sotiros Dec 21 '14 at 18:31
  • @NickSotiros, according to the docs: [The third argument is used to specify additional options, with these defaults: {cwd: undefined, env: process.env}](https://nodejs.org/dist/latest-v4.x/docs/api/child_process.html#child_process_child_process_spawn_command_args_options) – Lucas Jan 19 '16 at 22:02
  • 1
    @Mike'Pomax'Kamermans, this is very useful, had me stumped for hours. Too bad the npm folks decided _intentionally_ not to support an API... – Lucas Jan 19 '16 at 22:03
  • I doubt that's what happened. Windows does some magic when it comes to executing .bat and .cmd scripts, it's entirely possible `npm` simply can't hook into that without considerable work. – Mike 'Pomax' Kamermans Jan 19 '16 at 22:55
  • It's worth noting as well that, unlike exec(), calling spawn('npm.cmd start') will still fail. Make sure to split these up with the second parameter like this: spawn('npm.cmd', ['start']); – Artif3x Mar 09 '17 at 15:21
-1

I think this may be some sort of cygwin gotcha. I'm running Ubuntu 12.04 and tried to duplicate your problem, but it works just fine for me. In short, I don't see anything you are doing wrong.

If it is complaining about the option, maybe split it up into multiple options like so:

child = spawn("rm", ["-r", "-f", "node_modules"]);

That's kind of a hail mary, but that works on my Ubuntu 12.04 as well. You might try to just delete a single file and see if you get the same thing.

child = spawn("rm", ["/home/username/Desktop/TestFile"]);

If that still fails, then you know you are working against some crazy stuff.

You could even try to just execute a command with no parameters like so:

child = spawn("ls");

If that still fails, you aren't likely to get spawn to work at all would be my guess and be grateful that at least exec is working.

Not much in the realm of answers for you, but like I said, I can't see anything you are doing incorrectly.

Furthermore, I don't see how your npm command is going to work because you aren't specifying what to install, but that being said, it fails in a different way than I'm seeing it fail here if I use the same command. . . I see lots of stderr output, not an overall error.

BTW, I'm running node v0.8.21. You can query that by node -v. If you are running another version, maybe give 0.8.21 a try.

Brian
  • 2,916
  • 3
  • 27
  • 41
-2

Use full path for the process, like:

var cmd = require('child_process').spawn("C:\\windows\\system32\\cmd.exe");
  • 2
    that only works if you already know the extension, and location, of the executable. Hardcoding those instantly stops your project from working on another person's computer, let alone working cross-platform. – Mike 'Pomax' Kamermans Jan 11 '16 at 05:33