2023-03-04 A horrible gotcha with nodenv and Node.js scripts

Some time ago I ran into a terrible issue. It cost me more than an hour to debug and some new gray hairs, so here I am to warn others.

So, imagine that you have a few Node.js subprojects in a few directories, being a part of a larger project, like this:

project
   |
   \-- subprojectA
   \-- subprojectB

Each of them has some stuff usually associated with Node.js projects: a package.json, a package-lock.json and a .node-version (this last one tells nodenv which Node version to use).

Now, the system-wide version of Node.js is different than what is needed in, say, subprojectA. But that’s ok, because we have nodenv.

Except that it’s not. It turns out that nodenv is a leaky abstraction. And here is how it manifests itself.

In the main project directory I have a project-wide Node.js script, say build.js, which builds all of the subprojects. This means that I want to run npm ci (or something similar) in every one of them. Unfortunately, there seems to be no reliable programmatic API for npm, so I need to run an npm subprocess. However, when I start the build.js script, nodenv does its magic and sets the NODENV_DIR and NODENV_VERSION environment variables (along with a few others). Then, nodenv​’s npm shim sees those variables, and they override the .node-version setting. This way, the system-wide version of npm instead of the required version is run. (This bit me because package-lock.json is not compatible across npm versions.)

The remedy is fairly easy – just say

delete process.env.NODENV_DIR;
delete process.env.NODENV_VERSION;

before running npm from your script. The trick is, however, that it is not at all obvious that this should be required.

Oh, and one more thing. Before you run screaming and start to tell everyone that JavaScript is a terrible technology – please don’t. This is old news, we all know that JS is broken (together with most IT we know and love). But you know what? It is way better to work on shitty technology with a fantastic team than the opposite. So shut up. (And yes, it’s even better to work on fantastic technology with a fantastic team, so my long-term plan is to convert my teammates to Clojure and Emacs, but don’t tell anyone;-). As most other world-domination schemes, this requires patience and should be done in secret.)

CategoryEnglish, CategoryBlog, CategoryJavaScript