running 'npm install' from shell also runs the 'prepublish' script - node.js

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"
}

Related

Find the package.json file from within an npm script that runs on preinstall

So I need to read the package.json before installing a new package via npm.
Why reading package.json in the first place?
I am using npm for CSS components that are individually versioned and can have inter dependencies. (No javascript is delivered)
Looking for version conflicts for a bunch of dependencies I need to detect when package A requires package C#1.0.0 and package B requires package C#2.0.0 and deal with it.
Npm (as of version 3) deals with these issues by nesting a conflicting module deeper inside the tree. You now end up with both versions of the same module. CSS has a global namespace and a mixin (in Sasss case) would then overwrite each other and break your CSS.
This flat dependency issue is perfectly outlined in the npm blog: http://blog.npmjs.org/post/101775448305/npm-and-front-end-packaging
Even not considering our specific use case it strikes me as odd that you don't have access to the package.json in preinstall and postinstall scripts. They seem to be just for that use case.
What I tried
My package.json of the package I'm installing looks like this:
{
"name": "testmodule",
"version": "0.3.6",
"description": "TODO",
"scripts": {
"preinstall": "npm i some-script && some-script",
},
"author": "TODO",
"license": "MIT"
}
Inside that some-script package I run:
console.log( process.cwd() );
console.log( __dirname );
I then run:
~/path/to/folder $ npm i testmodule
This will result in:
$ npm i testmodule
> testmodule#0.3.6 preinstall /path/to/folder/node_modules/.staging/testmodule-5cc9d333
> some-script
/path/to/folder/node_modules/.staging/test-module-5cc9d333
/path/to/folder/node_modules/.staging/test-module-5cc9d333/node_modules/some-script
Now I totally get that I can't really access the root of where npm i was ran because my script was run by a subprocess of npm and has an entirely different root.
I then thought npm root should keep track where the actual root was for me and passed that as a parameter to my script from inside the testmodule package.json:
{
"name": "testmodule",
"version": "0.3.6",
"description": "TODO",
"scripts": {
"preinstall": "npm i some-script && some-script \"$(npm root)\"",
},
"author": "TODO",
"license": "MIT"
}
Unfortunately that also defaults back to a staging path:
/path/to/folder/node_modules/.staging/testmodule-5cc9d333/node_modules
I filed an issue with the registry but not holding my hopes up for them to get to that in time. Also my script needs to work on older npm installations.
In the meantime I came up with something like that inside my some-script:
let pgkPath = process.cwd().split('/node_modules/')[0];
That will return /path/to/folder/ which is correct but it makes the assumption no-one runs an npm i inside a folder incidentally named node_modules... Seems hacky.
Question
How can I access the path to the package.json from inside an npm script that is run via preinstall? To me that seems like something not too outrageous to ask for?
I don't understand your use-case entirely, but to answer your specific question of finding a parent package.json from a preinstall script:
Pass $(cd .. && npm prefix) as an argument to your script, then load ./package.json.
npm prefix will return the closest parent directory to contain a package.json file, which when invoked from the .. directory, should return the parent npm package's path.
{
"name": "testmodule",
"version": "0.3.6",
"description": "TODO",
"scripts": {
"preinstall": "npm i some-script && some-script \"$(cd .. && npm prefix)\"",
},
"author": "TODO",
"license": "MIT"
}

Prepublish not working as expected

I am testing on npm scripts to build my project dependency.
My idea comes from https://github.com/ParsePlatform/parse-server which impressed me by code in repository doesn't mean code in node_modules after npm install.
Below is my testmodule structure
src/index.js
package.json
and this is my package.json content
{
"name": "testmodule",
"version": "1.0.0",
"description": "",
"main": "lib/index.js",
"scripts": {
"build": "babel src/ -d lib/",
"prepublish": "npm run build"
},
"devDependencies": {
"babel-cli": "^6.18.0",
"babel-core": "^6.18.2"
}
}
and this is structure I expect after run npm install testmodule
node_modules/testmodule/lib/index.js
node_modules/testmodule/package.json
which is src folder should not be here.
But after I run npm install, it is exactly the same as when I push to my git repository.
Please take note that I am using GitLab in my own server.
So my questions are:
Is there anything that i'm missing to make prepublish run?
Which part of parse-server code makes the src folder and other files not there after install?
How are you running npm install?
According to the documentation on npm scripts, the prepublish script is run "BEFORE the package is published. (Also run on local npm install without any arguments.)". It seems clear that the prepublish script is only run on npm publish or npm install <local directory>.
If you are trying to install directly from your local gitlab server via a URL, this will not work - the script will not be run. The solution would be to install locally unless you're willing to open source your package & push it to the npm repository or pay for a private npm repository. This is what I have done during development of packages before they're ready to be made public.

Execute script after package installation

Now, I there is postinstall to run a script after
npm install
However, I am looking for a way to run a script after installing a single package
npm install jquery --save
Is this possible? If so, does this work on Windows and is there a way to get the name of the installed package (jquery in the given example)?
I'm don't see this on the package.json features.
It is therefore possible to do in the prestart for instance but jquery won't be in the devDependencies that way:
{
...
"devDependencies": {
"bower": "1.3.x",
"uglifyjs": "2.4.10",
... your other dependencies
},
"scripts": {
"prestart": "npm install jquery##.#.# ; <yourcommand> ; npm install",
...
}
}

NPM install doesn't trigger babel build if dependencies not coming from NPM

For example, if in my package.json, i have this:
"dependencies": {
"cacheman": "2.1.0" }
it works and it will trigger the building script inside cacheman when I do npm install.
however, if i do this:
"dependencies": {
"cacheman": "https://github.com/cayasso/cacheman.git" }
it won't work. npm install will not trigger the build process for cacheman.
why is that?
The script you are referring is pre-publish script which runs before publishing the npm module to npm registry. Check here package.json#L9
Extract shown here
"scripts": {
"test": "make test",
"prepublish": "make"
}
When you install it from github there is no publish step so the script is not run.
If you want to install from github only and have the script run, you can add it as postinstall script of cacheman (you will have to fork the repo to make changes if you are not owner of cacheman).
"scripts": {
"test": "make test",
"prepublish": "make",
"postinstall": "make"//Added postinstall
}
Check examples in npm scripts documentation for more details.

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

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.

Resources