I would like to know more about how NPM scripts work.
For example:
package.json
"scripts": {
"build": "set NODE_ENV=production&& webpack --config webpack.config.js",
}
When I execute npm run build:
What happens? I know a Node process will be initiated somewhere and webpack binary file will be called, right? But what are the steps before that? Does that depend whether I'm on Windows, Linux, PowerShell or Git bash? How does that process relate to the OS and the CMD/CLI that is being used?
The npm source code helpfully has the whole run-script functionality separated into its own module and repository so you can review the source code if the documentation does not answer your questions.
Speaking of your questions:
I know a Node process will be initiated somewhere and webpack binary file will be called, right?
The webpack executable will be run. And since webpack is a Node.js script, it will be run with node.
If, however, your "build" value consisted of shell/CLI commands, those commands would be run. Node.js is not necessarily invoked.
But what are the steps before that?
The "steps before that" include certain lifecycle scripts that might also be defined. In particular, if there is a "prebuild" script, it will run before the "build" script.
Does that depend whether I'm on Windows, Linux, PowerShell or Git bash? How does that process relate to the OS and the CMD/CLI that is being used?
npm (and node) make efforts to make Windows and Linux experiences comparable. Differences occur for sure, but without more details, I'm not sure I'd want to speculate about what specifics might be of interest to you beyond that.
Powershell and GitBash: Again, npm will make efforts to smooth out differences, but I'm sure they come up. One thing to be aware of is that your PATH (and other environment variables) might be set differently and that may affect behavior. (It may especially impact which version of node gets executed, if you have more than one version installed.)
I am not a Windows expert, but I have seen a lot of npm scripts that assume a UNIX-like environment. So, if given the choice and all else being equal (which it never is), a bash-like environment is probably going to be a bit smoother.
The answer I was looking for was something like this:
When you call npm run, npm will initiate a shell to run those instructions.
And which shell npm will use is defined in your npm settings.
You can see your npm config by running:
>>> npm config ls
In the example above, npm will run the script on git-bash.
You can change the npm shell by calling:
npm config set script-shell "C:\\Program Files\\git\\bin\\bash.exe"
I'm trying to pass environment variables to npm scripts as arguments
with no success.
export ENVIRONMENT=test.proxy.json
npm run test
I'm trying to do something like this in package.json
npm run test --proxy-config-file $ENVIRONMENT
When you do this:
export ENVIRONMENT=test.proxy.json
npm run test
or this:
ENVIRONMENT=test.proxy.json npm run test
then you will pass the "test.proxy.json" string as a value of the environment variable named ENVIRONMENT.
If you want to pass arguments to npm scripts then you may need to use:
npm run test -- --proxy-config-file $ENVIRONMENT
Keep in mind that if you pass the argument to the npm script, it doesn't necessarily mean that it will be passed to other scripts that this script is executing. With environment variables it's the other way around - by default they should be passed from one script to the other but there is still no guarantee as the caller may decide what environment variables to pass, if any.
But it's hard to tell from your question what is your real problem here - the phrase "with no success" is too general to know what is the problem here.
I currently have a npm script for running a linter. Obviously there will be some errors being outputted, but the npm script fails rather than just show me errors and move on.
This is terrible especially when I have something else calling the script as it marvellously breaks everything. I can always run npm run lint --force to avoid errors but I don't have that --force luxury all the time (for example, with a git-hook).
How can I setup my script to output errors without causing a mess?
Found the answer:
Simply adding: exit 0 to the end of the command did it!
There are a few other options which are cross platform too:
eslint || true
For this to work on Windows, you'll need to npm install cash-true first
exitzero eslint
You'll need to npm install exitzero first
I guess solving that in npm scripts will always be a bit limited. There is a micro module runjs which is a kind of small enhancement for scripts. Thing is that you can run cli commands and handle errors in JS. So you could do something like this (runfile.js):
import { run } from 'runjs'
export function lint () {
try {
run('eslint .')
} catch (e) {
// handle error how you want, you can silently just do nothing or re-throw it by: throw e.stack
}
}
then in the console:
$ run lint
Inside my composer.json, there's a postinstall hook setup like the following:
"scripts" : {
"dist" : "node dist; node_modules/.bin/doccoh src/package.js",
"postinstall" : "node_modules/.bin/grunt setup || true; node_modules/.bin/bower install",
"start" : "node server.js"
}
Whenever I run it (on Win from Git/Gnu Bash CLI), I end with
command not found. either the command was written wrong or couldn't be found
Rough translation from German CLI error.
I tried splitting it into multiple ;/semicolon separated parts and first cd into that directory, but it simply ends up with the same error message. Replacing the whole postinstall command set with a simple ls does work. So I guess the problem might be the semicolon separation or a wrong usage of commands. But overall I got no idea what's wrong.
Note: I got grunt-cli version 0.1.9 and grunt version 0.4.1 installed globally.
I'm a bit late to answer, but if you're on Windows, multiple commands on a single line are executed with the use of &&
postinstall: "some command && some other -c"
I ran into this looking for something and thought this may help other people. I have found it easier to move to postinstall.js files as things get a little complicated. This makes it easier to deal with moving forward.
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!");
});
(Or of course, as a compound script in package.js, e.g. "redo": "run-s clean install" and then using runAll(["redo"]))
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.
Use full path for the process, like:
var cmd = require('child_process').spawn("C:\\windows\\system32\\cmd.exe");