Skip to content

Instantly share code, notes, and snippets.

@chrmoritz
Last active July 12, 2017 11:44
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save chrmoritz/34e4c4d7779d72b549e2fc41f77c365c to your computer and use it in GitHub Desktop.
Save chrmoritz/34e4c4d7779d72b549e2fc41f77c365c to your computer and use it in GitHub Desktop.
What homebrews language/node should consider when handling prepare/prepublish scrips in node modules.

We have to support 5 different cases with lifecycle scripts (prepare and prepublish) in node module based formulas. These are sorted by increasing difficulty / complexity to support:

  1. arbitrary download location with no prepare (or prepublish) script: trivial to handle
  2. npm registry tarball download (already prepared) with a prepare script, which could be run a second time without failure: running prepare twice would be unnecessary but wouldn't break anything
  3. npm registry tarball download (already prepared) with a prepare script, which fails when run a second time (e.g. mkdir fails on already existing directory): --ignore-scripts is required for npm pack (and if we want this case to be rooted through the bundledDependencies branch for consistency we would need to pass --production to the first local npm install (because --ignore-scripts won't work here, see npm background#1+4))
  4. source tarball downloads (not already prepared), but we don't want to run prepare (e.g. formula implements it's own logic): same a 3.
  5. source tarball downloads (not already prepared), we want prepare to be executed (prepare_required is set): we have to install all deps (because they could be required to execute the prepare script) before npm pack by doing a local npm installation. Because said installation already executes prepare (see npm background#2) we still have to pass --ignore-scripts to npm pack (because executing prepare twice would be unnecessary and could even fail in some cases (e.g mkdir))

Supported cases by language/node version:

  • Old npm@<=4 (node <=7) version: 1, 2, 5 (3 + 4 will fail in all language/node versions before #2826)
  • current version: 1, 2, 3, 4
  • this PR: 1, 2, 3, 4, 5 (all)

npm background:

  1. passing --ignore-scrips to a command prevents all lifecycle scripts from being executed, even the lifecycle scripts from deps installed by this step (which is problematic if you want to use it to only prevent the prepare / prepublish script of the toplevel module from being executed, but accidentally are breaking e.g. the compilation of native addons in the deps with it)
  2. prepare (and npm@4+ deprecated prepublish) is run for npm pack (and npm publish). It is also run for local npm installation: npm install without argument to install all deps of a module according to package.json or npm install <moduleName> to install a module to the local node_modules folder. (It was also run in npm@<4 for our old npm install -g --prefix=<path> <folder> method).
  3. But prepare is NOT run for global npm registry installations (npm install -g <moduleName>) and installation from a local tarbal (npm install -g --prefix=<path> <pathToPackedTarball>), which we do now.
  4. Passing --production (to not instal devDeps) to any local npm install call will also prevent prepare scripts from being executed (because they might require devDeps). Unfortunately this behavior is not documented well in npms lifecycle script docs.
  5. The new prepublishOnly doesn't affect us, because it's only run before npm publish and NOT even before npm pack
  6. postinstall (also other install lifecycle scripts) is run AFTER any npm install command (local and global from any sources), but it shouldn't hurt us because npm registry tarballs are not already postinstalled and running it twice for our local install and the global prefix install wouldn't hurt, because its run AFTER install in the libexec directory at the 2nd run, which shouldn't conflict.

different installations method:

  1. npm pack only toplevel module (with empty dep tree) and install npm i -g --prefix=libexec it to libexec prefix (current version): doesn't support prepare (prepublish) scripts at all, quite hacky workaround for the npm@5 symlinks issues, npm pack is not designed to be run on an empty dep tree, but seems to work as long as no lifecycle scripts are involved
  2. install all dependencies beforehand (which executes prepare), pack everything in a self-contained package using npm pack with the bundledDependencies package.json rewrite and then install npm i -g --prefix=libexec it to libexec, without the need to handle deps in this last step (partly done in this PR for case 5): This matches exactly what the pre npm@<=4 (node <=7) version of language/node had done in npm internally, is more npm supported (running npm pack on a complete dep tree) and it supports prepare scripts out of the box.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment