script in npm has been contentious
for quite some time, and in npm 4 it's finally been deprecated, in the first step on the long road to a real fix. If you're currently using
to ensure certain tasks get run before publish though, actually putting this into action in a development team isn't as easy as it sounds.
script problem was that it not only ran before every
, but also after every
. This behaviour was often useful, but frequently surprising, and could be very problematic in some cases, especially if your script was very slow or had side-effects. Here at resin.io
scripts in lots of our modules -- like resin-sdk
-- typically to guarantee that the compiled output is up to date and their tests are all passing before we publish anything to npm. That works well, but running those on install means it's easy to accidentally have the test suite running twice in CI, both when you install and when you run the tests directly. That can almost double your build time easily, for no benefit.
Npm 4 fixes this, by deprecating
prepublish and introducing a new
prepublishOnly script, which is only run before a
npm publish, and a
prepare script, which is run before both
npm install and
npm publish. Before npm 5, users need to move to
prepare to get the right behavior they're looking for.
In principle that means if you want to migrate a script that you only want to run before each publish, you can simply change your
prepublishOnly. This works fine for developers using npm 4, but means any npm < 4 users you have will suddenly not be running any tests or build that you have set up to run before your module is published, which can cause big problems. Npm 4 isn't that widespread yet, and it's very easy to not update your npm version for quite some time, so this creates a big mess if you're not careful.
The solution is to move to
, but add a new
script that ensures that in the cases where it matters (i.e. only when you're publishing, not just installing) you're forced to upgrade to npm 4 or above. That might sound a little tricky, but with a couple of existing command-line Node.js tools and some Bash-fu we can build a fairly simple fix. For example, given the below
script (taken from Resin-SDK
"prepublish": "npm test && npm run build",
We can run
npm install --save-dev in-publish semver to pull in some useful dependencies, and then change our scripts to:
"(not-in-publish && echo 'Skipping prepublish') ||
npm run require-npm4-to-publish",
"semver -r '>=4.0.0' $(npm --version) ||
(echo 'NPM 4+ required to publish' && exit 1)",
"prepublishOnly": "npm test && npm run build",
(Line-breaks added for readability)
Here, for npm 4 users we now have a
prepublishOnly script which does what you expect: before you publish the module, make sure we've rebuilt it and run the tests.
In addition, we've also still got a
prepublish script, which checks if you're actually publishing the module (for an install it just prints "Skipping prepublish"), and if so checks you're using a version of npm that matches
>=4.0.0. If you are, this is all happy, the
prepublishOnly will run as normal. If you're not, this prints a helpful error and exits with a non-zero exit status, blocking the publish entirely.
A little tricky, but easy to put into place once you get your head around it, and a simple working fix to an npm problem that's been annoying Node.js developers for years. Have improvements on this? Add your comments below.