The amount of fighting I've had to do with setting up NPM for legacy applications, or with any sort of virtualization is insane. I no longer set up Node/NPM on anything automated without NVM, since they seem to drop support for old packages at random. You have to jump through a ton of hoops to make NVM play well when it's effectively installed by root but ran by another user within the container. Running NPM in a unix VM on Windows means you have to update everything with --no-bin-links since symlinks aren't well supported, and there is no fallback. It's baffling that they haven't had some sort of manual copy management when a symlink can't be created.
Meanwhile, I setup composer in a docker file in 4 lines (download, run, move to bin, cleanup), and it works everywhere for any user, respects permissions, etc. I don't think I've run into a single error on composer that wasn't something like a mis-typed command, package, or a host being down.
Funny! I'm dealing with a very similar problem right now. We're trying to port our CI workflow from DroneCI to GitHub Actions and our private NPM packages have been a literal nightmare. In comparison, our private composer packages worked fine with very little fuss.
Meanwhile, I setup composer in a docker file in 4 lines (download, run, move to bin, cleanup), and it works everywhere for any user, respects permissions, etc. I don't think I've run into a single error on composer that wasn't something like a mis-typed command, package, or a host being down.