How to detect when `prepublish` script is executed as a result of running `npm install` - node.js

https://docs.npmjs.com/misc/scripts
prepublish: Run BEFORE the package is published. (Also run on local npm install without any arguments.)
I want my script to execute only in case of user executing npm publish. However, NPM will execute "prepublish" script if user runs "npm install".

The only way I have figured out is using NPM internal ENV variables:
// NPM will run prepublish script after `npm install` (https://docs.npmjs.com/misc/scripts)
// This ensures that when script is executed using `npm *` it is run only when the command is `npm publish`.
if (process.env.npm_config_argv) {
let npmConfigArgv;
npmConfigArgv = JSON.parse(process.env.npm_config_argv);
if (npmConfigArgv.original[0] !== 'publish') {
console.log('`bundle-dependencies prepublish` will not execute. It appears that `prepublish` script has been run by `npm install`.');
return;
}
}
It appears that NPM stores the original command in process.env.npm_config_argv variable.
In case you are wondering, each NPM script is being run in different process. Therefore, something like setting a custom ENV variable in preinstall script does not work.

Another solution that's also worked for me (again from this thread) is using a prepublish.sh script like the following:
get_json_val() {
python -c "import json,sys;sys.stdout.write(json.dumps(json.load(sys.stdin)$1))";
}
get_npm_command() {
local temp=$(echo $npm_config_argv | get_json_val "['original'][0]")
echo "$temp" | tr -dc "[:alnum:]"
}
if [ $(get_npm_command) != "publish" ]; then
echo "Skipping prepublish script"
exit 0
fi
# else
echo "prepublish called"
# prepublish logic follows:
# ...
So if your package.json file is:
{
"name": "foo",
"version": "0.0.1",
"description": "",
"main": "lib/foo.js",
"scripts": {
"prepublish": "./prepublish.sh"
},
"dependencies": {
"lodash": "^4.10.0"
}
}
… then running npm install will only install the dependencies and the prepublish target will only be called when user types npm run publish.
Yet another way is to use the in-publish package (again mentioned in this thread). Place it in your development dependencies and then in your package.json have something like:
"prepublish": "(in-publish && npm run clean && flow check && npm run test && npm run build) || not-in-publish"

As of npm#4.0.0, the prepublish script is now deprecated. To run a script on both npm publish, and npm install without arguments (the bahaviour of prepublish), you should use prepare instead.
To run a script only on npm publish, you should use prepublishOnly.

Related

Distribute command-line arguments using npm as a build tool

I have an npm package that manages my github repository and has scripts that build my publishable npm package. Due to the nature of the package, the published npm package (the distribution) cannot match my repository's package (the source). I am moving closer to test-driven development and continuous integration, but am learning it step-by-step. Here is the current flow and it works flawlessly, so far:
From the command line:
> npm run patch
runs npm version patch
This invokes source's preversion,
cleans the distro: npm run clean
runs the tests: npm test
This invokes pretest,
npm run build
Runs distro's tests
Failure: Errors and process ends
Success: Branch complete,
invokes the source's postversion:
git push all commits and tags
runs distro's npm version patch
invokes distro's postversion
npm publish
(Source) package.json
{
...,
"scripts": {
"patch": "npm version patch && cd dist && npm version patch",
"minor": "npm version minor && cd dist && npm version minor",
"major": "npm version major && cd dist && npm version major",
"preversion": "npm run clean && npm test",
"clean": /* clean commands */,
"pretest": "npm run build",
"test": "cd dist && npm test",
"postversion": "git push origin --all && git push origin --tags"
},
...
}
(Distro) package.json
{
...,
"scripts": {
"test": /* test commands */,
"postversion": "npm publish"
},
...
}
I would like to have a simpler interface. As you can see, I have to npm run <major|minor|patch>. I would instead like to npm run dist with a commandline arg passed to both npm versions.
Example:
> npm run dist patch
--------------------
<<< npm version patch
<<< cd dist && npm version patch
> npm run dist major
--------------------
<<< npm version major
<<< cd dist && npm version major
Is it possible to pass commandline args down the tree? Or, better yet, can I distribute a commandline arg across commands in a single scripts entry? I can easily add an entry to scripts that accepts a commandline argument, but cannot seem to figure out how to share that argument more than once with writing a separate .js script.

Pass npm script command line arguments to a specific script inside it

I have a scenario where I have to run three npm script to achieve some result in my application. I combined them in one npm script. This is my package.json:
"scripts": {
"setup": "npm install && npm run some-script && npm install",
"some-script": "gulp some-other-script"
}
what I would like to do is to pass arguments to setup script from command line which will be passed further to the some-script script.
If I run npm run script -- --abc=123 the arguments are added at the end of the script but I would like to pass it to specific script (in this case to npm run some-script). I also tried to rewrite script definition like this:
"setup": "npm install && npm run some-script -- --sample=sample&& npm install" but with no luck.
I'm aware of shell functions (described here: Sending command line arguments to npm script and here https://github.com/npm/npm/issues/9627) but I need a solution which will work cross-platform.
Is there a way to do that?
Ok I found a work around.
1st goal was to be able to use some script with arguments, and cascade this to the calls of other scripts :
npm run main -- --arg1
"main": "npm run script1 && npm run script2"
Problem with this approach is that the cascading would only be done by adding the arg passed to npm run main at the END of the line "npm run script1 && npm run script2". I could find no way to pass it to the 1st element : npm run script1
Here is the work around I found :
1st you need to add in package.json:
"config": {
"arg1": "ARG_VALUE"
},
Then in your script you can add it to the call like this :
"main": "npm run script_no_arg1 && npm run scrip1 -- --%npm_package_config_arg1% && npm run script2 -- --%npm_package_config_component% && npm run script_no_arg2"
Finally, You don't need to call it with any arg : npm run main
But you need to modify ARG_VALUE before launc the script :)
Last thing : in my case I was calling gulp tasks :
"cleardown": "rimraf build lib",
"inline-build-templates": "gulp inline-build-templates",
"step1": "npm run cleardown && npm run inline-build-templates -- --%npm_package_config_arg1%",
It works you can get the argument into the gulp task that way :
gulp.task('inline-build-templates', function() {
let component = process.argv[3];
component = component.split('--')[1];
console.log('RUNNING GULP TASK : inline-build-templates for component ' + component);
// whatever task gulp
});
Hope it helps.

running 'npm install' from shell also runs the 'prepublish' script

This is a SSCCE.
Given the following package.json file:
{
"name": "foo",
"version": "1.0.0",
"description": "",
"scripts": {
"prepublish": "echo \"pre-publish script called\""
},
"devDependencies": {
},
"dependencies": {
"lodash": "^4.10.0"
}
}
Running: npm install from the shell, results in the prepublish script also being executed:
$ npm install
> foo#1.0.0 prepublish /tmp/so
> echo "pre-publish script called"
pre-publish script called
npm WARN foo#1.0.0 No description
npm WARN foo#1.0.0 No repository field.
npm WARN foo#1.0.0 No license field.
If the prepublish script gets renamed to e.g. prepublis it is no longer executed with npm install. Problem is, I need the prepublish script as I typically perform static type analysis and Mocha tests prior to publishing to npm.
Is this a bug or a feature and how do I get around it? There should be no need to run my Mocha tests when I simply wish to install the package.json dependencies.
My environment is:
$ npm --version && node --version
3.9.5
v6.2.2
As I said it's not a bug (although everyone wants this functionality to be fixed), but there is a solution. Check out https://www.npmjs.com/package/in-publish to help with this very situation.
Solution summary
(from the in-publish package page)
npm install --save in-publish
Then in package.json:
"scripts": {
"prepublish": "in-publish && thing-I-dont-want-on-dev-install || not-in-publish"
}
You can also use not-in-install and in-install instead of in-publish
"scripts": {
"prepublish": "not-in-install && thing-I-dont-want-on-dev-install || in-install"
}

how to add a custom NPM postinstall script?

whenever I run npm install it runs npm run postinstall. how can I add a custom post install script? so that after running npm install it runs npm run postinstall2?
You call multiple scripts from postinstall by chaining them with &&:
{
"scripts": {
"script1": "",
"script2": "",
"postinstall": "script1 && script2"
}
}
This should work on both Windows and Unix (Linux, macOS) terminals.

Silencing errors on failures for npm run-script

When you run npm test and it fails, you get the test outputs + a single error message, like so:
npm ERR! Test failed. See above for more details.
However, I made a custom script called lint, like so:
// package.json
{
// ...
"scripts": {
// ... definition for test ...
"lint": "./node_modules/jsxhint/cli.js src/",
}
}
Alright, simple enough. But when you run npm run lint and it fails, Rather than the nice looking error for npm test, you get a massive error message after the output of the linter:
npm ERR! Darwin 14.0.0
npm ERR! argv "node" "/usr/local/bin/npm" "run-script" "lint"
npm ERR! node v0.10.32
npm ERR! npm v2.1.7
npm ERR! code ELIFECYCLE
# and ~15 more lines...
Is there a way to silence all this junk so I can have a clean output like the npm test script? I see how they caught the error in the npm source code, but I don't think I can just add a custom command without forking npm like that... Hope I'm wrong!
But if I am, would I be better off just pushing off a task like this to a tool like Grunt? Thanks!
Use the npm run --silent option:
$ npm run --silent test
Even less typing if you define a shell alias:
$ alias run='npm run --silent'
$ run test
If you don't care about preserving the return code of the linter process, you can always configure your package.json like this:
{
// ...
"scripts": {
// ...
"lint": "eslint . || true",
}
}
I've just been trying to figure out the same. Not a perfect answer but it kind of worked to specify linting as a pretest script (docs) like so:
// package.json
{
// ...
"scripts": {
// ... definition for test ...
"pretest": "./node_modules/jsxhint/cli.js src/",
}
}
Then, when you type in npm test the first time, you will only get a single-line error from NPM. Obviously, that means you won't be able to run your tests if you haven't linted.
The other option is to use some kind of third party task runner like Make, Grunt or Gulp.
I've only used Make, and I think it's the most painless to set up (at least on OSX and Linux, not sure about Windows).
Create a Makefile in your root that looks like so:
lint:
./node_modules/.bin/jslint ./*.js # or whatever your lint command is
test:
./node_modules/.bin/mocha test/*.js # or whatever your test command is
.PHONY: lint test
Then type make test and make lint to run those commands.
You can silence the errors by redirecting the stderr to /dev/null. For example:
{
"test": "karma start" (package.json)
}
running:
$ npm run test 2> /dev/null
will now send all npm errors to /dev/null but normal input will still be visible in the console.
Because the error is thrown by npm, after karma exiting with a non-zero status, doing the following is not enough:
{
"test": "karma start 2> /dev/null"
}
but you can overcome it by creating another task that calls that task with stderr redirection:
{
"test": "karma start",
"test:silent": "npm run test 2> /dev/null"
}
this will ensure that the npm error messages are hidden

Resources