On breaking changes in transitive dependencies
dmytro.sh
dmytro.sh
That change, however trivial to handle, is still a bloody breaking change and it still has to be handled, by the library users, at one point of time or another. And how do you notify your users that they need to handle something that's changed? That's right, you mention it in the RELEASE file, a major version bump is entirely superfluous. /s
This is why we can't have nice things: apparently there are library maintainers out there who just don't get it what semver is actually about.
"Imagine a world where software could be tested against a version of the dependency, and then allow all version updates as long as they are minor (a new functionality added in a backward-compatible manner)"
is a fantasy in my experience. The Semver contract exists as a concept sure, but there's no guarantee that the publisher of a dependency you consume actually maintains compatibility, in the context of your applications usage, between minor versions. It's more of an aspiration.
A more durable approach might be to require every API to formally and verifiably provide precondition and postcondition that can be checked with an SMT solver for every public interface.
With current tech that is pushing a lot onto module authors, but I believe there is some research being done on inferring both preconditions and postconditions from the semantics of the component statements so perhaps we can avoid forcing programmers to carry the intolerable burden of learning the first order predicate calculus.
The theory so far is: there’s a subset of unit tests that specify the API. If you create new tests, that’s mostly fine. If have to alter a test, that’s probably a breaking change.
The main problem I can already see with this, which is why it’s on the shelf, is that some test frameworks and many tester writers cannot guarantee the invariants as stated above. You end up changing tests for minor behavioral changes. You mix tests of private fields in with tests of public ones. And even if you split them, then functional tests end up defining parts of the API.
But somewhere out there in the future is a language where types are defined by tests, and certain testing patterns indicate minor version increments and others major.
The other issue is that tests can have bugs too, sometimes you are just fixing a test bug.
If I change the test but not the code, that may be a clarification. If I change both, it’s ambiguous.
Truffle, which I don't work on myself very often, is more trusting of semver, and does use yarn for development.
I in the past 've had to step away from a Thanksgiving dinner to do a hot fix release due to a transitive semver breakage, so I'm probably more sensitive to its problems than others.
Yarn ignoring published lock files isn't totally crazy though. Yarn states that allowing automatic updates to transitive dependencies is better because users can respond to new security incidents faster than a chain of downstream package authors. An excellent point. My stance is that package authors generally introduce new breaking bugs/exploits much more often than they fix them; popular tools like Ganache are more likely to be the target of supply-chain attacks than non-financial tools.
I'd honestly love to switch to, or support, a version of yarn, or pnpm, that allows package authors to prefer strict transitive dependencies, that can then be overruled by consumers of the package of they prefer a looser policy.
Worse, SemVer is useless from small teams where they are not budgeting time and effort to maintain all of the old version bumps. Pointedly, just updating the feature version can help folks know it is dangerous to upgrade. But, if you aren't going to maintain the old versions forever, it is equally dangerous to not upgrade.
I also prefer an external manifest that can lock specific versions of all dependencies, but that usually requires a separate package manager from the language provided one, which introduces a whole new set of issues.