tj / n

Node version management
MIT License
18.76k stars 738 forks source link

node executable and libs seem to be duplicated #809

Open unphased opened 1 month ago

unphased commented 1 month ago

Bug Report

Summary and Repro

I set $N_PREFIX to ~/.n from my shell.

n seems to install:

Expected Behaviour

Some sort of symlink from ~/.n/bin/node to ~/.n/n/versions/node/22.5.1/bin/node and so on. If I install one node version it will consume 180-whatever MB disk. If I install two node versions it will consume 360-whatever MB disk.

Actual Behaviour

If I install one node version it will consume 360-whatever MB disk. If I install two node versions it will consume 550-whatever MB disk.

Other Information

I fully expect that there are various issues that have come up in the past that express some desire for $N_PREFIX/bin/node and friends NOT to be symlinks. However, I am not sure I buy that, because $N_PREFIX/bin/npm is itself a symlink, to a "duplicated" $N_PREFIX/bin/../lib/node_modules/npm/bin/npm-cli.js:

❯ ll ~/.n/bin/npm
lrwxrwxrwx 1 slu slu 38 Aug  1 07:38 /home/slu/.n/bin/npm -> ../lib/node_modules/npm/bin/npm-cli.js

Even if the 100+MB node executable has to get duplicated on disk why must we also do so for include/ and lib/?

unphased commented 1 month ago

I guess one "workaround" is if we want to save disk with things as they are, we can use n to install some version of node and then uninstall it with n. This should remove it then from $N_PREFIX/n/versions while leaving the whole installed copy under $N_PREFIX/bin etc. By this perspective $N_PREFIX/n/versions/* is a simple download cache.

HOWEVER. the behavior of n prune Remove all downloaded versions except the installed version would contradict this. In the situation where one version of node has been installed via n, running prune will not remove that one version, but it should if the above were true.

shadowspawn commented 1 month ago

As you worked out, the downloaded versions are cached in $N_PREFIX/n/versions and also installed (copied) to $N_PREFIX/bin.

So when you install 10 different versions of Node.js you end up with 11 copies. The active version and the 10 versions in the cache.

HOWEVER. the behavior of n prune Remove all downloaded versions except the installed version would contradict this.

This is how prune was originally implemented (#407). The installed (active) version of node is not deleted from the cache by prune. So only keeping the most relevant version. This lets you try another version of node and get back to where you were.

Following on from my original example, when I get annoyed I have 11 copies of node because I installed 10 different versions, I can run n prune which will drop me back down to 2 copies. One active, and one in the cache.

It is ok to install a version of node and then immediately delete it from the cache, like:

n 18.15.0
n rm 18.15.0
unphased commented 1 month ago

Yeah I see that to get full storage efficiency we can n rm the installed version. It's just definitely annoying that prune will take your 11 copies across 10 versions down to 2 identical copies of the installed version when the spirit of prune would here clearly call for bringing it down to just leaving installed intact.

unphased commented 1 month ago

What's even more confusing is this:

Installed node is v20

Clearly it's an uncommon use case as n is intended for rapid switching. But even the copying step if you switch between two cached versions takes nontrivial time because the executables and files are being actually copied around... somebody has to have an explanation for why we're doing all of these shenanigans when they could be done via changing symlinks.

unphased commented 1 month ago

While I'm complaining about that. If you just

In all these scenarios 20 is already installed but it repeats all the work of copying it in anyway.

shadowspawn commented 1 month ago

In all these scenarios 20 is already installed but it repeats all the work of copying it in anyway.

I understand your desire to skip the install. I used to think that was a good idea too.

The reinstall was a deliberate change in v5.0.0. The goal is a simple predictable behaviour. When you run n install 20 (or pick 20 again) it actually installs 20.

The previous behaviour was it would not install if the active version matches. Great when things are working properly. The problem is if your active version is broken. I found myself suggesting people with problems do things like:

n 8.0.0 # any version other than the one you actually want
n 20.0.0 # the one you actually want, and because it is changing versions it will actually reinstall
shadowspawn commented 1 month ago

n ls shows empty list! At minimum here it should be telling you that v20 is currently installed.

n ls shows the local versions available in the cache.

stasadev commented 1 month ago

@unphased, thank you for opening this issue.

I'm working on adding node from n in Docker and before using N_PREFIX I tried to install one node globally in /usr/local, but it was taking up suspiciously much space.

@shadowspawn thank you for mentioning n rm, it solved the problem.

ENV NODE_VERSION=20
RUN curl -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n | bash -s "${NODE_VERSION}" && \
    npm install -g n && \
    n rm "${NODE_VERSION}" && \
    ln -sf "$(which node)" "$(which node)js"
shadowspawn commented 1 month ago

n is a single bash file, so you could just save the curl download instead of piping it to bash. Something like:

curl -fsSL https://raw.githubusercontent.com/tj/n/master/bin/n -o /usr/local/bin/n
chmod a+x /usr/local/bin/n
n install "${NODE_VERSION}"
n rm "${NODE_VERSION}"

The n README suggests installing using npm so that you can manage n along with any other global npm packages, like npm list -g and npm outdated -g. This may not be useful in a docker container.

shadowspawn commented 3 days ago

I would like to make it easier to install node with a single command without leaving behind a cache copy. Trying out some option names:

n install --rm auto
n install --cleanup auto