63

I have npm module with following package.json

{
  "name": "my-app",
  "version": "0.0.0",
  "scripts": {
    "prepublish": "bower install",
    "build": "gulp"
  },
  "dependencies": {
    "express": "~4.0.0",
    "body-parser": "~1.0.1"
  },
  "devDependencies": {
    "gulp": "~3.6.0",
    "bower": "~1.3.2"
  }
}

When I deploy my app to production, I don't want install devDependecies, so, I run npm install --production. But in this case, prepublish script is called, but it doesn't need to, because I use CDN links in production.

How to call postinstall script only after npm install but not after npm install --production?

Christos Lytras
  • 36,310
  • 4
  • 80
  • 113
just-boris
  • 9,468
  • 5
  • 48
  • 84

8 Answers8

41

Newer npm (& Yarn) versions include support for the prepare script that is run after each install run but only in development mode. Also, the prepublish is deprecated. This should be enough:

{
  scripts: {
    "prepare": "bower install"
  }
}

Docs: https://docs.npmjs.com/misc/scripts

mgol
  • 1,362
  • 15
  • 20
26

I think you cannot choose what scripts are run based on the --production argument. What you can do, however, is supply a script which tests the NODE_ENV variable and only runs bower install if it's not "production".

If you are always in a unix-y environment, you can do it like this:

{ 
  scripts: {
    "prepublish": "[ \"$NODE_ENV\" = production ] && exit 0; bower install"
  }
}
Nik Sumeiko
  • 8,263
  • 8
  • 50
  • 53
Sam Mikes
  • 10,438
  • 3
  • 42
  • 47
  • nice answer! what if i want to also include "beta" enviroment? how can i add the "production OR beta" condition? – maephisto Apr 17 '15 at 09:20
  • Then it starts to get complicated -- but you can do `"[ \"$NODE_ENV\" = production ] || [ \"$NODE_ENV\" = beta ] || bower install"` – Sam Mikes Apr 17 '15 at 12:37
  • 6
    My NPM fails if I have this script and run it in production mode, as it returns a non-zero exit code. It works with: `[ \"$npm_config_production\" ] && exit 0; bower install` or `[ \"$NODE_ENV\" == production ] && exit 0; bower install` – Tim Jun 21 '16 at 13:44
  • I think it worth updating this answer based on last comment @SamMikes – Restuta Oct 09 '19 at 22:04
  • 1
    I think it would be even better if this answer reommended [cross-env](https://npmjs.com/package/cross-env) instead of using bash-only constructs. It's really actually easier and your build will be cross-platform. – Stijn de Witt Jul 13 '21 at 11:28
17

This only works if you're on a unix-like environment:

NPM sets an environment variable to "true" when install is run with --production. To only run the postinstall script if npm install was not run with --production, use the following code.

"postinstall": "if [ -z \"$npm_config_production\" ]; then node_modules/gulp/bin/gulp.js first-run; fi",
Samuel Bolduc
  • 18,163
  • 7
  • 34
  • 55
Synthe
  • 181
  • 1
  • 2
  • 4
    Thanks! I used the reverse for building assets only on heroku deploys: `"postinstall": "if [ -n \"$npm_config_production\" ]; then npm run build-for-production; fi"` – Derek Dahmer Jul 31 '15 at 18:12
13

Solution that is less dependent on unix nature of your shell:

  "scripts": {
    "postinstall": "node -e \"process.env.NODE_ENV != 'production' && process.exit(1)\" || echo do dev stuff"
  },
Alexey Vishentsev
  • 5,644
  • 2
  • 15
  • 8
  • 2
    This fails when I add this module to another module as a dependency and try to `npm install` the encompassing one. Modifying the return value of the command chain fixes this for me: `"(node -e \"process.env.NODE_ENV !== 'production' && process.exit()\" && grunt devTask) || node -v"` – mucaho Apr 10 '17 at 12:49
  • 1
    FYI: This still works in 2022 and it was the only one-liner solution for me – ghaschel Sep 26 '22 at 19:36
12

I work with windows, osx and linux so I use a NON environment specific solution to solve this problem:

In the postinstall handler i execute a js script that checks process.env.NODE_ENV variable and does the work.

in my specific case I have to execute a gulp task only in development env:

part of package.json

"scripts": {
  "postinstall": "node postinstall"
}

all postinstall.js script

if (process.env.NODE_ENV === 'development') {
  const gulp = require('./gulpfile');
  gulp.start('taskname');
}

last row of gulpfile.js

module.exports = gulp;

it's important to export gulp from the gulpfile.js because all tasks are in that specific gulp instance.

Giovanni Bruno
  • 844
  • 2
  • 9
  • 13
6

I have a more general problem - where I want to skip running the postinstall script on local (direct) installs - like when I'm developing the package and run yarn add --dev my-new-dependency.

This is what I came up with. It works with both npm and yarn.

postinstall.js:

const env = process.env;
if (
    // if INIT_CWD (yarn/npm install invocation path) and PWD
    // are the same, then local (dev) install/add is taking place
    env.INIT_CWD === env.PWD ||
    // local (dev) yarn install may have been run
    // from a project subfolder
    env.INIT_CWD.indexOf(env.PWD) === 0
) {
    console.info('Skipping `postinstall` script on local installs');
}
else {
    // do post-installation things
    // ...
}

package.json:

"script": {
    "postinstall": "node postinstall.js",
    ...
Már Örlygsson
  • 14,176
  • 3
  • 42
  • 53
  • 1
    Thanks, just what I was looking for. Created a npm package to do just that: https://www.npmjs.com/package/skip-local-postinstall – matthiasgiger Sep 05 '20 at 18:44
  • Matthias your package looks great but it would be great to have flags cause I actually need this inverted. – Nick Feb 24 '22 at 22:51
5

Landed here because I had the same issue. Ended up with a solution that tests for the existence of a package under node_modules that I know should only be available in development.

{
  "scripts": {
    "postinstall": "bash -c '[ -d ./node_modules/@types ] && lerna run prepare || echo No postinstall without type info'" 
  }
}

This works fine for me conceptually, as the prepare scripts here called by lerna are mainly to ts-to-js compilations.

Wouter van Vliet
  • 1,484
  • 11
  • 20
1

I'm using if-env module. It's less verbose.

PS: I didn't test it on windows yet.

Install with:

npm i if-env

than in package.json scripts:

"postinstall-production": "echo \"production, skipping...\"",
"postinstall-dev": "echo \"doing dev exclusive stuff\"",
"postinstall": "if-env NODE_ENV=production && npm run postinstall-production || npm run postinstall-dev"
JacopKane
  • 824
  • 11
  • 15