I recently had a case where a developer joined us on a project and he got a different version of a package than I did because the lockfile didn’t constraint the dependencies and sub-dependencies and everything else. (For that you have to pass an explicit parameter like `--ci` or `--frozen-lockfile` depending on which of three different package managers you use.)
Bollocks, I say.
Are you actually arguing over the state of JS development from 4 years ago?
For a Node project you just need node. npm is part of node, and yarn is optional. The repeatable builds was a real issue but that's been solved years ago.
Npm's dev dependencies are analogous to Maven's plugins.
Bazel is an alternative to maven. Docker fulfills the same role as in the Node ecosystem. The runtime class path is an implementation detail that you need to concern yourself with if you wish to run your Java code without Maven (analogous to running your JavaScript code without npm).
The two forms of the same dependency is a limitation that stems from Java the language rather than its ecosystem.
FWIW, nearly every language ecosystem I know of is at the point of "download build tool, run build tool."
Nowadays on javaland you should only need to have JDK and git/svn/hg/(wtv version control you use). And with the JDK, you can always download the most recent that java is backwards compatible.
Find me a mainstream language that's more complicated than this? "Checkout and build" works pretty much everywhere? I mean, you need the right version of mvn or pip or whatnot... just like you need the right version of npm.
If anything, node/npm deserves special mention for NOT having reliable repeatable builds. Part of this is that a lot of packages rely on native code. But most of it is that the ecosystem culture defaults to "always grab the latest versions of everything".
And npm only got package-lock.json a few years ago, not "from day one". Prior to package-lock.json, builds were wildly unpredictable - like, expect breaking changes week to week even if you don't change anything at all in your code.
If you want an "entirely self contained" payoff, languages that produce static binaries are pretty hard to beat. Node is not that.