Earlier quoted context omitted.
That’s still the fault of the package developer. “^1.2.1” means “any version with a public API compatible with 1.2.1”, or in other words “only minor versions”. The whole point of semantic versioning is to guarantee breaking changes are expressed through major versions. If you break your package’s compatibility and bump the version to 1.2.1 instead of 2.0.0 then people absolutely should be upset.
I think you might be misunderstanding the above comment. The default behavior of `npm i ` is to add `" ": "^1.2.1"` _not_ `" ": "1.2.1"`. The point the commenter was trying to make is that the tool itself has a bad default which makes it easy to make mistakes. I would go so far as to argue that when `npm i` does not have the behavior a user would expect from a package manager in that regard.
That is: "^1.2.1" shouldn't be a bad default relative to "1.2.1"; you generally want to be able to pull in non-breaking security updates automatically, for what I hope are obvious reasons, and if that goes sideways then the blame should be entirely on the package maintainer for violating version semantics, not on the package/dependency manager for obeying version semantics.
I don't have much of an opinion on this for Node.js, but the Ruby and Elixir ecosystems (among those of many, many other languages which I've used in recent years) have similar conventions, and I don't seem to recall nearly as many cases of widely-used packages blatantly ignoring semantic versioning. Then again, most typically require the programmer to be explicit about whether or not to allow sub-minor automatic version updates for a given dependency, last I checked (e.g. you edit a configuration file and use the build tool to pull the dependencies specified in that file, as opposed to the build tool itself updating that file like npm apparently does).