Skip to content
This repository has been archived by the owner on Aug 11, 2022. It is now read-only.

npm install inexplicably runs npm run prepublish in npm 1.2.0 #3059

Closed
dfellis opened this issue Jan 15, 2013 · 88 comments
Closed

npm install inexplicably runs npm run prepublish in npm 1.2.0 #3059

dfellis opened this issue Jan 15, 2013 · 88 comments

Comments

@dfellis
Copy link

dfellis commented Jan 15, 2013

damocles@moya:~/oss/sloppy-queue-flow(master)$ npm install

> sloppy-queue-flow@0.1.10 prepublish /Users/damocles/oss/sloppy-queue-flow
> npm test && docco ./lib/sloppy-queue-flow.js && uglifyjs ./lib/sloppy-queue-flow.js > ./lib/sloppy-queue-flow.min.js && git commit -am "Automatic doc and minification for version $npm_package_version" && git tag $npm_package_version && git push && git push --tags


> sloppy-queue-flow@0.1.10 test /Users/damocles/oss/sloppy-queue-flow
> nodeunit ./test/test.js


test.js
✔ sloppyType
✔ sloppy
✔ badHandler
✔ kill
✔ processNextTickPatch
✔ jscoverage

OK: 13 assertions (1044ms)
docco: ./lib/sloppy-queue-flow.js -> docs/sloppy-queue-flow.html
# On branch master
nothing to commit (working directory clean)
npm ERR! sloppy-queue-flow@0.1.10 prepublish: `npm test && docco ./lib/sloppy-queue-flow.js && uglifyjs ./lib/sloppy-queue-flow.js > ./lib/sloppy-queue-flow.min.js && git commit -am "Automatic doc and minification for version $npm_package_version" && git tag $npm_package_version && git push && git push --tags`
npm ERR! `sh "-c" "npm test && docco ./lib/sloppy-queue-flow.js && uglifyjs ./lib/sloppy-queue-flow.js > ./lib/sloppy-queue-flow.min.js && git commit -am \"Automatic doc and minification for version $npm_package_version\" && git tag $npm_package_version && git push && git push --tags"` failed with 1
npm ERR! 
npm ERR! Failed at the sloppy-queue-flow@0.1.10 prepublish script.
npm ERR! This is most likely a problem with the sloppy-queue-flow package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     npm test && docco ./lib/sloppy-queue-flow.js && uglifyjs ./lib/sloppy-queue-flow.js > ./lib/sloppy-queue-flow.min.js && git commit -am "Automatic doc and minification for version $npm_package_version" && git tag $npm_package_version && git push && git push --tags
npm ERR! You can get their info via:
npm ERR!     npm owner ls sloppy-queue-flow
npm ERR! There is likely additional logging output above.

npm ERR! System Darwin 12.2.0
npm ERR! command "node" "/usr/local/bin/npm" "install"
npm ERR! cwd /Users/damocles/oss/sloppy-queue-flow
npm ERR! node -v v0.8.17
npm ERR! npm -v 1.2.0
npm ERR! code ELIFECYCLE
npm ERR! 
npm ERR! Additional logging details can be found in:
npm ERR!     /Users/damocles/oss/sloppy-queue-flow/npm-debug.log
npm ERR! not ok code 0

Hopefully this is something minor (accidentally replaced preinstall with prepublish somewhere)

@mfncooper
Copy link
Contributor

Running prepublish as part of npm install is as designed, per Isaac.

@dfellis
Copy link
Author

dfellis commented Jan 15, 2013

I refuse to believe that. prepublish is a trigger that clearly means "before publishing to NPM".

How am I supposed to do things like auto-test, generate documentation, and create git tags before publishing to NPM if Travis-CI and every single user of the library would end up running that same code!?

preinstall is the trigger before npm install and prepublish is the trigger before npm publish. Anything else is insanity.

@dfellis
Copy link
Author

dfellis commented Jan 15, 2013

Okay, so I found the commit that's causing the issue, and I think there's a better way to solve the problem rather than cause inexplicable behavior to help some people out (and completely wreck all of my NPM modules).

What you wanted was to be able to run the prepublish script inside of a local in-directory install only, so this hack was added in for that because there's no way for the preinstall script to know that it was invoked that way. (This totally breaks my libraries because npm install is also used to bootstrap a testing environment for them on Travis-CI, and I use prepublish to actually take care of chores that need to be done prior to publishing, such as generating a minified version for browsers, building documentation from the source code, making the commit for these things and tagging that commit with the new version number.)

A better solution would be to expose an $npm_invocation environment variable (or similar) that passes down the string the end-user typed into their console, and then let the relevant script decide based on that what it should do.

For this particular use-case, preinstall.sh would look like this:

#!/usr/bin/env bash

if ["$npm_invocation" == "npm install"]; then
    ./prepublish.sh
fi

This, I think, is much more useful because ./prepublish.sh could be replaced with ./preupdate.sh or ./pretest.sh, or the script could crank up C++ compiler optimization if it sees --production.

This is all necessary because the $npm_lifecycle_event can't be used because it's still reporting prepublish rather than preinstall.

I'm willing to make a PR to add the $npm_invocation functionality and then I can simply disable this bizarre behavior if you're unwilling to remove it, but I need a way to turn off prepublish running during preinstall.

@isaacs
Copy link
Contributor

isaacs commented Jan 15, 2013

This change was by design, and was carefully considered.

I would say you're doing way too much in your prepublish script. I suggest adding a Makefile with a publish: target that does all this stuff, and then runs npm publish at the end of it. Then you'd run make publish instead of npm publish.

Or, if that's not to your liking, you can set a config var on your local machine like npm config set do_my_fancy_prepublish_junk true and check the value $npm_config_do_my_fancy_prepublish_script in the environment in your prepublish script.

Or yet another workaround, you can have a script with a different name entirely like "mypublish", and run it with npm run mypublish. (It can end with npm publish, even.)

Far and away, the most common use case for prepublish is to compile coffeescript into javascript (or minify javascript into ugly javascript, which is essentially the same sort of task). Since this is deterministic, and not platform-dependent, it makes no sense to do it multiple times, on each install target in the preinstall event. It should be done, instead, right before publishing the package, so that it can be bundled in the arball. However, the objection to moving it there is that it no longer compiles CoffeeScript to JavaScript in npm install <noargs>. So, now it does.

Eventually, arbitrary pre/post/install scripts will be removed, and node-gyp will be the only thing that will be run at install time. Compilation of binary addons is the only justifiable use of install-time scripts.

It looks like these packages are going to be affected by this. On the other hand, these packages will benefit from the change.

@dfellis
Copy link
Author

dfellis commented Jan 15, 2013

Your two links aren't working for me, though I can see what you're going for (it won't catch scripts that actually delegate to a sh file and do their magic in there because it was too long to keep as a JSON string).

Why wouldn't it make more sense to keep the semantic meaning of pre<something> intact and for projects that want special behavior when installed without arguments, have them detect that condition? You say it was carefully considered but I don't understand the logic at all.

This new behavior breaks the contract of the scripts interface, and if they're deprecated for gyp, I think it would be nice if NPM informed me with a console warning so I'd know to migrate my code off of it.

I just don't see this as doing "too much" in my prepublish script -- it's simply doing the housekeeping on the repo and making sure that I don't publish a release without minified code, or generated docs, or a missing git tag -- little details that such a script is perfect for.

@isaacs
Copy link
Contributor

isaacs commented Jan 15, 2013

Ah, yes, markdown mangled the links. Fixed.

Why wouldn't it make more sense to keep the semantic meaning of presomething intact and for projects that want special behavior when installed without arguments, have them detect that condition?

Because most modules with a prepublish script want to have it do the exact same thing in both cases. So that's the default. I understand that it's not what you'd prefer. I'm sorry. You are the edge case in this scenario.

There are already several ways that you can work around this (listed in my previous message), and we're talking about fewer than a dozen packages. I'd even be happy to send you pull requests to change them.

@dfellis
Copy link
Author

dfellis commented Jan 15, 2013

I've started migrating my code as suggested to a custom npm script. I still think this behavior is unexpected because prepublish, to me, is something only the author of the lib should run, coupled with the regularity npm <foo> runs the pre<foo> hook first, then if successful does its expected operation, and then if that's successful, runs the post<foo> hook.

You don't need to work on any PRs for me, but the other two authors in that list probably deserve a warning that the next time they bootstrap their dev environment for their libraries it will fail.

I guess I'm mostly upset because I've touted NPM as an example of a well-done source package manager that does exactly what you expect of it, and this just doesn't make much sense, even if it is more convenient for the majority of lib authors.

@Munter
Copy link

Munter commented Feb 1, 2013

I'm with @dfellis
It defies all intuition about this hook.

And for the record I have been using the same hook exactly as @dfellis has, to make sure my unit tests pass before publishing, to to git housekeeping, to publish to the gazillion frontend module package managers etc.

I have no problem writing Makefiles, but really I prefer pre- and post-hooks to be intuitive.

@millermedeiros
Copy link

Having the same issue, I guess I'm always the edge case... Should add a new hook, don't break consistency.

@grncdr
Copy link
Contributor

grncdr commented Feb 3, 2013

Far and away, the most common use case for prepublish is to compile coffeescript into javascript (or minify javascript into ugly javascript, which is essentially the same sort of task). Since this is deterministic, and not platform-dependent, it makes no sense to do it multiple times, on each install target in the preinstall event. It should be done, instead, right before publishing the package, so that it can be bundled in the arball.

This all makes sense, and worked well before (and after) this change.

However, the objection to moving it there is that it no longer compiles CoffeeScript to JavaScript in npm install . So, now it does.

This argument seems pretty weak to me, but maybe I don't understand it fully. Is there further discussion somewhere else that I missed?

My understanding is that the main (and probably only) reason for running npm install <no args> is to install dependencies and otherwise set up a package that has just been pulled down from source control. There's roughly two groups that would be doing this:

  1. Developers - if you are hacking on a project, you will need to understand any build toolchain it uses anyways.
  2. Automated systems (e.g. continuous integration service) - All of these support specifying the command(s) that they need to run after retrieving sources, so having them automatically compile sources is trivial. Furthermore, anybody using one of them already does this.

FWIW, I found this issue after the change broke my travis-ci builds. I don't mind modifying my Makefile to work around it, but I really don't understand who the user group is that needs npm install <noargs> to compile coffeescript etc.

EDIT: found this https://github.com/isaacs/npm/pull/3053 in which a use case for having scripts automatically run on plain npm install is outlined.

@isaacs
Copy link
Contributor

isaacs commented Feb 4, 2013

The eventual goal for package.json scripts will be to run pre/post install ONLY on npm install <noargs> in the package dir, and just run node-gyp rebuild on install if there's a gyp file. There are steps to getting there. First it's deprecated, and an alternative path is provided for people who want to compile coffeescript and do other silly things to do their silly things in a way that doesn't depend on us all doing the same deterministic silly things on our computers as well.

evdb referenced this issue in evdb/uri-template Mar 20, 2013
This might be related to the behaviour change introduced in isaacs/npm#3059
@strk
Copy link

strk commented May 28, 2013

Does anyone have a pointer of where was this carefully considered ? A mailing list thread, another ticket or something ?

@grncdr
Copy link
Contributor

grncdr commented May 28, 2013

Does anyone have a pointer of where was this carefully considered ? A mailing list thread, another ticket or something ?

None that I was able to find, (and I did a lot of searching when this issue first broke my CI setup). The justifications given in this issue are terrible as well. In particular...

Eventually, arbitrary pre/post/install scripts will be removed, and node-gyp will be the only thing that will be run at install time. Compilation of binary addons is the only justifiable use of install-time scripts.

... and ...

The eventual goal for package.json scripts will be to run pre/post install ONLY on npm install in the package dir, and just run node-gyp rebuild on install if there's a gyp file.

... are both well reasoned arguments against preinstall/postinstall, but have nothing to do with prepublish. The comments in this discussion (again, the only recorded discussion of the issue afaik) are:

  • prepublish is a trigger that clearly means "before publishing to NPM".
  • It defies all intuition about this hook.
  • Should add a new hook, don't break consistency.
  • Insanity.

This change was a clearly confusing and undesirable to many of the users of the "prepublish" hook, and it concerns me that @isaacs (who has absolute authority over a lot of critical infrastructure for node) would defend it.

@aseemk
Copy link

aseemk commented Jun 3, 2013

I'm upgrading to npm 1.2, and this just bit me too. I was pretty baffled as well.

The install-through-git use case in #3053 is interesting food for thought, though I'm pretty surprised that that's not being called an "edge case", and wanting to do things purely pre-publish is.

notslang referenced this issue in jescalan/roots Dec 30, 2013
prepublish gets run during `npm install`, which can screw stuff up
see issue: https://github.com/isaacs/npm/issues/3059
notslang referenced this issue in jescalan/roots Jan 2, 2014
prepublish gets run during `npm install`, which can screw stuff up
see issue: https://github.com/isaacs/npm/issues/3059
notslang referenced this issue in jescalan/roots Jan 3, 2014
closes #336
prepublish gets run during `npm install`, which can screw stuff up
so do it in a Makefile
see issue: https://github.com/isaacs/npm/issues/3059
@jsdevel
Copy link

jsdevel commented Jan 9, 2014

+1 @dfellis Prepublish is a nice way for me to sanity check my modules before sending them off to npm. To say that install should trigger publish is counter-intuitive.

I will resort to @isaacs hack for now, I.E. exporting a variable within my prepublish script, but it sure feels like bad design.

npm publish # runs prepublish script
npm install # runs (pre|post)install

@JamesMGreene
Copy link

@garthk:
Keep in that @iarna works for @npm, Inc... so, if anyone were going to be attuned to the NPM internals [and any forthcoming changes that would break the "in-publish" module's analysis technique], she is a prime candidate!

@garthk
Copy link

garthk commented Apr 2, 2015

@JamesMGreene, perhaps that attunement is reflected in her README (emphasis mine):

This detects that its running as a part of publish command in a terrible, terrible way. NPM dumps out its config object blindly into the environment prior to running commands. This includes the command line it was invoked with. This module determines if its being run as a result of publish by looking at that env var. This is not a part of the documented npm interface and so it is not guarenteed to be stable.

@iarna
Copy link
Contributor

iarna commented Apr 2, 2015

Indeed, it's a cow path that we can pave, if that pattern is effective for people. As far as breaking in-publish goes, it probably is unlikely as I doubt we can change the dumping of the config object without breaking any number of packages. It's unfortunate that it's so unconstrained, but it's the code we all have to live with.

If we were to pave this cow path then yes, I'd want a more explicit env var. The whole prepublish lifecycle thing is something that we've discussed internally, but we haven't reached any conclusions (and likely wouldn't without further public discussion) and isn't currently on any roadmaps.

@olalonde
Copy link

olalonde commented May 6, 2015

+1 this is totally unexpected behaviour. Got bitten by this this evening.

@flockonus
Copy link

+1 prepublish is really surprising in a bad way - altho i can't complain about the docs: https://docs.npmjs.com/misc/scripts

Wanted to refer back to @isaacs comment above about deprecating this behaviour, a long time has passed

@lxe
Copy link
Contributor

lxe commented May 18, 2015

This also causes npm prepublish to get stuck in infinite loops:

"prepublish": "npm install && npm run build"

@david0178418
Copy link

Just another vote for the priority to be raised on this. I keep forgetting that "prepublish" doesn't actually mean "prepublish".

@mtscout6
Copy link

Is this not getting prioritized because it's closed?

@david0178418
Copy link

@mtscout6 Good question. I'm hoping this is being tracked in another issue. If an issue's been logged and it's agreed that something should happen at some point to fix it, it should be tracked somewhere. Otherwise, these types of things tend to fall off the radar.

@olalonde
Copy link

@david0178418 I just created a new issue here: #8402 , let's hope this one doesn't get closed.

@monolithed
Copy link

+1. cause problems for me

@npm npm locked and limited conversation to collaborators Jul 7, 2015
ds82 added a commit to ds82/angular-contextmenu that referenced this issue Jul 9, 2015
travis runs a `npm install` for the package. npm-install runs `npm prepublish`
(what is an unexpected behaviour if you ask me or (*1)). npm-test would run
the karma test with chrome as browser which would fail on travis.

(*1) npm/npm#3059
ds82 added a commit to ds82/angular-contextmenu that referenced this issue Jul 9, 2015
travis runs a `npm install` for the package. npm-install runs `npm prepublish`
(what is an unexpected behaviour if you ask me or (*1)). npm-test would run
the karma test with chrome as browser which would fail on travis.

(*1) npm/npm#3059
jensklose added a commit to mazehall/mazehall that referenced this issue Jul 15, 2015
doggan added a commit to mechanica/MPQ that referenced this issue Aug 1, 2015
Package must be published AFTER 'make clean' is run to prevent any dirty cache file from being packed into tar.
Ideally, we could run 'make clean' prior to publishing (prepublish), but it doesn't currently work like that: npm/npm#3059 😒
jednano referenced this issue in postcss/postcss Aug 6, 2015
laughinghan referenced this issue in mathquill/mathquill Jun 24, 2016
Steps:
1. On a clean tree on `master`, add a new entry to `CHANGELOG.md` in the
   same format as the other entries, then run `script/prep-release.sh`
   to do everything that can be done locally: bump version, build,
   package as tarball + zipfile, commit, tag.
2. After double-checking the build/package/commit/tag, run
   `script/push-release.sh` (with a GitHub access token env variable)
   to automatically publish to NPM, push to GitHub, create a new GitHub
   Release, and cleanup the tarballs etc.

Notes:
- `CHANGELOG.md`: I changed the format to resemble [the GitHub Releases
  page] more. In particular, each entry has a one-line summary on the
  GitHub Releases page that was the commit message for the version bump/
  changelog addition, but didn't show up in the changelog at all.
- `Makefile`: since these scripts create and cleanup the tarballs and
  zipfiles, there's no need for `make dist` anymore, and `make clean` is
  now, well, cleaner.
- Creating GitHub Releases: this was loosely inspired by the
  [gh-release Bash script], but was mostly based on the [GitHub Releases
  API docs].

[the GitHub Releases page] https://github.com/mathquill/mathquill/releases
[gh-release Bash script]: https://github.com/progrium/gh-release/blob/master/bash/gh-release.bash
[itHub Releases API docs]: https://developer.github.com/v3/repos/releases/#create-a-release

--

I designed this release process to have 2 steps:

1. Do everything that can be done locally: build, changelogs, bump
   version, commit, tag, create tarballs and zipfiles
2. Only after getting a chance to double-check the stuff done locally do
   we publish on public servers like NPM and GitHub

Not coincidentally, `npm` has two relevant commands, [`npm version`]
and [`npm publish`]. They seem like they'd correspond nicely with my
2 steps, so I tried implementing the release process as hooks into those
commands, but it was too annoying:
- [`npm version`] ensures that the working tree is clean even before
  calling the `preversion` hook, and then automatically commits, so
  the releaser would have to edit `CHANGELOG.md` during one of those
  hook calls (like it opens a shell or editor or something).
- [`npm publish`] is not as well-documented as `npm version`, but I
  [stumbled on] a sub-step: [`npm pack`], which figures out the files
  to be included and packs them into a tarball to be uploaded to the
  NPM registry. However, we actually want that to happen during the
  Step 1, so we can double-check it before uploading, not this step.

So using NPM hook scripts, the workflow would have to be:
1. On a clean tree, run `npm version patch`, which opens an editor on
   `CHANGELOG.md`, then builds, commits, tags, and in particular
   packages a tarball like `mathquill-0.10.2.tgz`.
2. After double-checking the build/package/commit/tag, run
   `npm publish mathquill-0.10.2.tgz`. Note that plain `npm publish`
   would re-run `npm pack` instead of using the tarball from Step 1.
   (I guess that would be fine, just inefficient?)

Rather than work within these inconveniences, I figured it'd be simpler
to just use "lower-level" tools, or at least, the same tools but in a
lower-level capacity. Specifically, `prep-release.sh` calls
`npm version --no-git-tag-version`, which just bumps the version in
package.json and doesn't do anything with Git, and then calls
`npm pack` to package the tarball; and `push-release.sh` calls
`npm publish <tarball>`, skipping the internal call to `npm pack` simply
using `npm publish` to upload to the NPM registry.

[`npm version`]: https://docs.npmjs.com/cli/version
[`npm publish`]: https://docs.npmjs.com/cli/publish
[`npm pack`]: https://docs.npmjs.com/cli/pack
[stumbled on]: npm/npm#13080
FlorianWendelborn referenced this issue in jorgebucaran/hyperapp Feb 15, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests