Hook (specifically postinstall) after any npm install command - node.js

Using NPM at the command line, is there some official hook that I can configure, which means that for any npm install command (including npm install x), a certain hook is run?
Right now, I see certain limitations -
if I run npm install x it will not run hooks for another dependency (obviously) but it also doesn't seem to run hooks for the main/parent package.
Plain old npm install will run preinstall / postintall hooks for the main/parent package.

If you are on npm v6.X it's possible to run scripts on npm install <package> by using Hook Scripts.
Unfortunately it seems the functionality has been removed in newer versions.
In order to do so you need to create a script inside node_modules/.hooks/{eventname} where {eventname} is the event you want to hook on.
For example to hook on postinstall you'll need node_modules/.hooks/postinstall
There's a catch though: it won't run on Windows, because it won't be able to recognize the file as an executable since it's lacking a file extension.
A not so pretty workaround is to create, for instance, a postinstall.cmd and soft (or hard /H) linking it with mklink postinstall postinstall.cmd
This will ensure that Windows recognizes the file as a .cmd executable to correctly run it.

Related

How to install extra bin files when `npm install` is given `-g` flag?

Is there some way to install only part of a NodeJS project when run via npm install, but to install additional features when npm install -g is used?
I have a library with a command-line interface, however the CLI is not useful to any downstream projects using my library. So when those projects pull in my library, I don't want them to pull down dependencies like chalk that are only used for the CLI they will never touch.
However if an end user decides to install my library globally on their system with npm install -g then I want the CLI installed, and via the bin section in package.json, placed in their path so they can run it like any other program.
I can't work out how to do this without splitting the CLI into a separate package. The options I have investigated are:
Put the CLI dependencies as devDependencies. This prevents chalk etc. from being installed in downstream projects, but the drawback here is that the user must npm install -g in development mode, which means they get the test framework and linting tools installed as well even though they will never use them.
Put the CLI as a separate NodeJS package/module. The drawback here is that it makes testing difficult (as often the CLI and library are modified at the same time and used for testing new features), and developers wanting to contribute to the library will have to stuff around with linking the two packages so although it would work, it's less than ideal from a workflow perspective.
Put the CLI in a folder inside the main package, and create another package.json in there just for the CLI, pulling in the main project via npm install ... This works until you get to the install point, when you realise that there is no way to install the CLI once the package has been published. npm install #my/library will only install from the package.json in the project root, there's no way to say "oh also install the package in the cli subdirectory too."
Ideally what I would like is this:
npm install #my/library - run by a developer wanting to use the library in their project. Adds the library only to their project's dependencies, ignores both CLI and any dependencies the CLI needs.
npm install -g #my/library - run by an end-user, installs library and CLI globally on their system, including the CLI dependencies, and adds the CLI to the user's path via the package.json bin section.
npm install --dev - used by developer contributing to the library to install the test framework so they can run the unit tests before submitting their code for inclusion.
Not having to split the CLI into a separate project.
Is this possible?
Is there some way to install only part of a NodeJS project when run via npm install, but to install additional features when npm install -g is used?
You can write a postinstall script that uses is-globally-installed (or another similar package) to check if the module is installed globally, and then run whatever is appropriate to install the CLI (perhaps npm install -g for a separate package that just has the CLI).
Well I came up with a workaround.
I put the CLI back to development-only (with its requirements in devDependencies so it could be used by local devs, but they wouldn't get pulled in to downstream projects using the library). I then created another package for the CLI, but all it was is a package.json file and nothing else.
What this package does is depend on the main library, plus only the dependencies needed by the CLI. Then it uses the bin section to point to the CLI inside the main library, so no code is needed in this package - it is literally just one file. Like so:
{
"name": "#my/library-cli", // new module to install the CLI
"bin": {
"myprog": "./node_modules/#my/library/bin/myprog.js" // the command is inside the dependency
},
"dependencies": {
"#my/library": "*", // require the library itself where the CLI code sits
"command-line-args": "*" // dependency needed by the CLI
}
}
The only drawback with this is that this new package's dependencies need to be kept in sync with the main library's devDependencies (at least the deps required by the CLI) but as they won't change often in my case I can live with that.
I did try the postinstall hook as #Trott suggested but it didn't seem to work:
"scripts": {
"postinstall": "[ $npm_config_global == 'true' ] && (cd cli && npm install -g)"
}
For some reason it was incredibly slow and seemed to get stuck in a loop, trying and failing to install over and over. I'm also not sure it would've respected being launched via a command like npm install -g --prefix /my/install/folder as that non-standard prefix may not get passed through to the child npm process.

Why is npm running prepare script after npm install, and how can I stop it?

Whenever I run npm install <package> it installs the package alright, but then it automatically runs the prepare script.
It's worth mentioning that I've already checked that there is no postinstall script in the package.json.
From https://docs.npmjs.com/misc/scripts:
prepare: Run both BEFORE the package is packed and published, and on
local npm install without any arguments (See below). This is run AFTER
prepublish, but BEFORE prepublishOnly.
Since NPM v5, prepare script is executed when you run npm install
The other answers are fine, but for some additional context, this is to support a workflow where you can use devDependencies to build assets or other generated content for your project.
For example, say you want to use node-sass (CSS preprocessor). You add "node-sass" as a devDependency, then you run the sass command in your "prepare" script, which generates your CSS.
So, when you run npm install, the following happens:
dependencies and devDependencies get installed
your "prepare" script generates your CSS
your project is ready to go with all necessary CSS
And when you run npm publish, something similar happens:
your "prepare" script generates your CSS
your code and generated CSS are published to the npm repo
So now when someone comes along and installs your package, they don't need node-sass or any of your devDependencies. They only need to runtime deps.
The prepare script runs on local install and when installing git dependencies:
prepare: Run both BEFORE the package is packed and published, on local npm install without any arguments, and when installing git dependencies (See below). This is run AFTER prepublish, but BEFORE prepublishOnly.
https://docs.npmjs.com/misc/scripts
You can avoid it with the --ignore-scripts flag:
$ npm install <package> --ignore-scripts
source: https://docs.npmjs.com/cli/install
From the doc https://docs.npmjs.com/misc/scripts
prepare: Run both BEFORE the package is packed and published, and on local npm install without any arguments (See below). This is run AFTER prepublish, but BEFORE prepublishOnly.
prepare script run before publishing and after npm install.
Now if you make an npm install and one of the packages has a prepare script, like for building, and it fails the whole install will fail.
We have two options:
Ignore scripts
npm install --ignore-scripts
That will run the ignore to all packages, which might be not the desired behavior. Imagine a third party package that needs to run prepare and build. If you run with --ignore-scripts this will get skipped.
Make the script optional (better option)
Add a package to the optionalDependencies:
{
optionalDependencies: {
"myPackage": "^1.0.0"
}
}
If a dependency can be used, but you would like npm to proceed if it cannot be found or fails to install, then you may put it in the optionalDependencies object. This is a map of package name to version or url, just like the dependencies object. The difference is that build failures do not cause installation to fail.
Entries in optionalDependencies will override entries of the same name in dependencies, so it's usually best to only put in one place.
Check the doc:
https://docs.npmjs.com/cli/v7/configuring-npm/package-json#optionaldependencies
Note: With this, only the chosen package is concerned. And if it fails the installation will continue. That's usually what you want.
Go with optionalDependencies
As per this answer in this thread:
https://github.com/npm/npm/issues/2817#issuecomment-368661749
the problem with --ignore-scripts is that is ignores all scripts. I just need to be able to ignore a script(s) for a particular package (the one where a build fails to compile on certain platforms). This option usually breaks my code because it has ignored ALL scripts in other packages that actually do need to run.
Anyway, to make this work like the OP I make the offending package optional. Then do a regular install, then a second install with --ignore-scripts. That way I get the scripts of other packages run first before ignoring them all (including the intended) the second time which then "fetches" the source of that package.
It's generally better to go with optionalDependencies. That will most likely suit your needs.

Attempt to install Amber.js on OS X fails because grunt client not installed?

I have installed node.js and npm on my OS X box running 10.11.5. But, following the instructions at http://docs.amber-lang.net/getting-started.html, when I type:
npm install -g amber-cli
in the Terminal, I get the following result:
npm WARN amber-cli#0.100.2 requires a peer of grunt-cli#^0.1.13 but none was installed.
And the installation halts. Since this is only a warning from npm, I wonder if I really need grunt installed.
FWIW, it does appear I have a version of grunt in my npm directory because using locate to find it produces a billion lines, one of which is:
/Users/me/.npm/grunt
In fact, it looks like I have a bunch of grunt installs (most version 0.4.0). Which makes me reluctant to install grunt again since it doesn't seem to work anyway.
Try the following command
npm install -g grunt-cli#0 grunt-init bower amber-cli
More than likely, it's a problem with your path.
If nothing returns when you execute which grunt, it means that you need to add the location of grunt to your path.
If you installed grunt using npm install -g, you'll need to add /usr/local/bin to your path.
Since you found your grunt cli under ~/.npm, you will need to add that to your path.
You should also add ./node_modules/bin to your path which will cause your shell to check for npm-installed modules in the node_modules/bin directory in your current directory.
You can always run grunt directly, irrespective of its location, by typing: npx grunt using the tool included with npm for running npm-installed commands.
It's good practice to add all of the paths above, to catch all possible npm-installed commands.
You can update your path in the ~/.*rc file for your shell (~/.bashrc for bash, etc.), by adding this line to the end of your rc file:
export PATH=/usr/local/bin:~/.npm:./node_modules/bin:$PATH

Node.js command-line utilities : how to insure module accessibility?

So, in writing command-line utilities with node, if my command requires certain modules, should I include a package.json file to list those dependencies? Obviously this will require running > npm install on the package file.
Without doing so, packages are not found, even though I've installed them globally. How are others distributing these types of cli scripts?
if my command requires certain modules, should I include a package.json file to list those dependencies?
Yes. This is standard.
Obviously this will require running > npm install on the package file.
This isn't exactly the case. You have to run npm install during development to initialize your environment. However, if someone installs your script through npm (e.g. npm install -g your-script-name), the dependencies for your script will automatically be installed. This is a recursive operation, meaning the dependencies of your dependencies will be installed as well, and so on.

How to edit global file installation

I just ran this line of code
npm install -g csslint
CSSlint seems great but I want to add the ability to prettify my css by putting in custom code. The goal is to have a command line interface where I can format my css before pushing the code to the server.
"-g" stand for installing globally correct?
Where does this library get installed to and how do I access it? I thought it would be in my user/local/bin but I couldn't find it.
Thanks!
use npm root -g to see where modules get installed. If you want to use it in your code using require() then install it locally i.e. without using -g option. More details here https://npmjs.org/doc/faq.html
I believe it's going to depend on your npm settings, but my global npm binaries are in /usr/local/share/npm/bin

Resources