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
Related
I just installed ESLint and I can successfully run it by doing this at the terminal window:
./node_modules/.bin/eslint app
Note: app is the root folder I want lint to inspect.
I then put that exact command in my package.json:
"scripts": {
"lint": "./node_modules/.bin/eslint app"
}
I expected to be able to run it in the terminal window like this:
npm run lint
Note: I know how to fix the no-undef error. My question is about the many errors lines after that.
It actually works, but it also produces a bunch of errors after showing me the correct output:
Why is that happening?
This is the default way of how the npm script runner handles script errors (i.e. non-zero exit codes). This will always happen, even if you only run a script like exit 1.
I'm not sure why this feature exists, it seems annoying and useless in most cases.
If you don't want to see this, you can add || true at the end of your script.
Example:
lint: "eslint app || true"
As you might've noticed, I've omitted the part to the eslint binary. The npm script runner already includes local binaries as part of the path when trying to run the script so there is no need to use the full path.
document is a global, so eslint thinks you are missing an import somewhere. For those cases, you can adapt your config so that the error is not reported, Something like this:
module.exports = {
"globals": {
"document": true
}
}
this should be saved as .eslintrc.js and be at the same level where your package.json is
I want to force using yarn install instead of npm install. I want to raise an error in npm install. What should I do in package.json?
UPDATE: Alexander's answer is the better solution and uses the same technique I describe here. I am leaving my answer in tact for posterity. The original point of my answer was to show that you can execute a small node script which should work on all platforms.
In your preinstall script you can run a mini node script which should work on all platforms, whereas things like pgrep (and other common *nix commands and operators) won't work on Windows until Windows 10 has received widespread adoption.
I tested the below script on Node v4.7.0 (npm v2.15.11) and Node v7.2.1 (npm v3.10.10). I assume it works on everything in between. It works by checking the environment variables on the currently running process - the npm_execpath is the path to the currently running "npm" script. In the case of yarn, it should point to /path/to/yarn/on/your/machine/yarn.js.
"scripts": {
"preinstall": "node -e \"if(process.env.npm_execpath.indexOf('yarn') === -1) throw new Error('You must use Yarn to install, not NPM')\""
}
You can read more about npm scripts here: https://docs.npmjs.com/misc/scripts
As far as the npm_execpath environment variable, while not documented I doubt that it will ever change. It's been around for multiple major releases of npm and it doesn't really pass the "there's a better name for this" test.
Most of the answers here involve hacky scripts but there's a built in way to achieve this which I posted over on the Yarn github issue. Unlike soe of the other ways, this works for any and all NPM commands -- actually a bug in npm means it blocks npm install but not npm install <package>. Hopefully though the developers suspicions would already be raised from doing an npm install.
You add a fake engine version like so in package.json (you may want to tweak the yarn and node entries):
"engines": {
"npm": "please-use-yarn",
"yarn": ">= 1.17.3",
"node": ">= 12.5.0"
}
Then you add an .npmrc file to the project root with this:
engine-strict = true
Running NPM then raises an error:
npm ERR! code ENOTSUP
npm ERR! notsup Unsupported engine for root#: wanted: {"npm":"please-use-yarn","yarn":">= 1.17.3","node":">= 12.5.0"} (current: {"node":"12.9.1","npm":"6.10.2"})
npm ERR! notsup Not compatible with your version of node/npm: root#
Like the other answers, I'd recommend using a preinstall script and checking your environment. For a portable solution that won't have false-positives if another npm process happens to be running, using node -e 'JS_CODE' is probably the best option.
In that JS code, you can check the package manager's path using the following:
process.env.npm_execpath
Yarn's binary is yarn.js, compared to npm-cli.js used by NPM. We can use a regex like the following to check that this string ends with yarn.js.
/yarn\.js$/
By using this regex, we can be sure it won't accidentally match somewhere earlier in the file system. Most-likely yarn won't appear in the file path, but you can never be too sure.
Here's a minimal example:
{
"name": "test",
"version": "1.0.0",
"scripts": {
"preinstall": "node -e 'if(!/yarn\\.js$/.test(process.env.npm_execpath))throw new Error(\"Use yarn\")'"
}
}
Of course, the user will still be able to get around this check be editing the JSON or using the --ignore-scripts options:
npm install --ignore-scripts
After trying these options and not being very satisfied, I recommend only-allow.
Just add:
{
"scripts": {
"preinstall": "npx only-allow yarn"
}
}
I like that it provides a clear warning message, and instructions how to install yarn:
Credit to Adam Thomas' answer for providing the thread recommending this.
You can use the preinstall hook along with some shell script to achieve this.
sample package.json:
"scripts": {
"preinstall": "pgrep npm && exit 1"
}
I've just released a module that includes a CLI for this (useful for npm preinstall scripts): https://github.com/adjohnson916/use-yarn
Also, I've just released a helper for Danger to check for missing yarn.lock changes on CI:
https://github.com/adjohnson916/danger-yarn-lock
See also discussion here:
https://github.com/yarnpkg/yarn/issues/1732
https://github.com/alexanderwallin/use-yarn-instead/issues/1
If you want to simply test whether packages are being installed under yarn or npm, I tweaked Alexander O'Mara's answer slightly since it worked for me on OS X:
"scripts": {
"preinstall": "if node -e \"process.exitCode=!/yarn\\.js$/.test(process.env.npm_execpath)\" ; then echo yarn ; else echo npm ; fi",
"postinstall": ""
}
There are quite a few concepts happening in this short snippet:
The \\. portion is escaped so that \\ becomes \ and results in a properly escaped \. to detect a period in the regex.
process.exitCode= can be used to set the process's exit code and is safer than calling process.exit(N) due to the asynchronous nature of Node.js.
In Alexander's example, throw new Error(\"Use yarn\") caused node to exit with code 1 and print the stack trace to stderr. You can try running these on the console to see how that works: node -e 'throw new Error("Oops")' and node -e 'throw new Error("Oops")' 2> /dev/null (which directs the stderr stream to /dev/null). Then you can verify that the exit code was 1 with echo $? (which prints the last exit code).
The shell's if XXXX ; then YYYY ; else ZZZZ ; fi conditional logic checks the exit code of XXXX and goes to the then case for 0 (any other value goes to the else case). So if the regex detects yarn.js at the end of process.env.npm_execpath then it returns true. This must be negated so that the node process exits with code 0 and satisfies the if.
You could also console.log() the regex result and compare the output in the shell (this is just a little more verbose). Here are some examples of how to do that: https://unix.stackexchange.com/a/52801 and https://superuser.com/a/688902
You can append true ; or false ; to any shell statement to set the exit code manually. For example you can try true ; echo $? or false ; echo $?.
You can also leave off the else echo npm ; portion entirely if you don't need it.
With all of that out of the way, you can substitute the echo yarn and echo npm portions with other commands. For example, you could put multiple commands in a subshell like (echo yarn) or echo $(echo yarn).
In my case, I needed to work around an issue where one of the packages installed but had bugs under yarn so I had to run an npm install --ignore-scripts in the success case. Note that this should probably never be done in production, but can be a lifesaver if you just need to get something done or don't have control over which package manager will be used down the road.
I haven't tried this on Windows, so if someone can test the syntax there I will update my answer with what works. It would be best if the preinstall script is identical under both Windows and the Mac/Linux shell.
Found an alternate solution on Reddit. I added this to the end of my .zshenv file:
NPM_PATH=$(which npm)
npm () {
if [ -e yarn.lock ]
then
echo "Please use yarn with this project"
else
$NPM_PATH "$#"
fi
}
It now stops me from absentmindedly running commands like npm i on any yarn project on my Mac.
As some answers have already showed, you can use the only-allow package like so:
{
"scripts": {
"preinstall": "npx only-allow [npm|cnpm|pnpm|yarn]"
}
}
However, NodeJS v16.9.0 and v14.19.0 support a new experimental packageManager field in the package.json file.
Type: <string>
{
"packageManager": "<package manager name>#<version>"
}
The "packageManager" field defines which package manager is expected to be used when working on the current project. It can be set to any of the supported package managers, and will ensure that your teams use the exact same package manager versions without having to install anything else other than Node.js.
This field is currently experimental and needs to be opted-in; check the Corepack page for details about the procedure.
Is there a way to measure the performance of an npm scripts similar to the way time-grunt works?
I am moving some of my critical build tasks to use npm instead of Grunt as writing my own build script is more flexible than using some of the Grunt plugins like grunt-browserify for example.
I have tried using console.time() but it finishes before the script is done, I assume because the methods are asynchronous. I also tried running the npm script as a Grunt task like this:
grunt.registerTask('collectify', function () {
grunt.util.spawn({
cmd: 'npm',
args: ['run', 'collectify:app']
});
});
But the output is different than if I run npm run collectify:app from the command line, perhaps because of pwd issues.
Coloured bars would be nice but at the very least I'd like to see the time in numbers.
Have you tried prepending the time command before your npm run command ?
So if your command is:
npm run collectify:app
It becomes:
time npm run collectify:app
And it'll output 3 lines e.g.
real 0m11.580s
user 0m7.400s
sys 0m1.304s
Let me know if it helps!
Your best option is likely pre[foo] and post[foo] scripts.
So if I have a NPM script called "foobar" then I can create a script called "preboofar" and "postfoobar" and they will be executed automatically before and after "foobar" is executed.
So in "pre" you can touch a file with a timestamp and in "post" you can read that file and calculate the difference.
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");