Semantic versioning support
Description
I was surprised while reading the docs that this project does not support semantic versioning (semver). https://heyapi.dev/openapi-ts/get-started#versioning
Currently, the project uses:
1.x.x: significant breaking changes, reserved for v1 release
x.1.x: breaking changes
x.x.1: new features, bug fixes, and non-breaking changes
While semantic versioning is:
1.x.x: any breaking change
x.1.x: additional new feature that does not break
x.x.1: bug fixes
I'm not sure what the exact rationale is for the current system; perhaps it's to avoid the initial 1.0.0 release.
If that's the case, I would request that once 1.0.0 is released, that semantic versioning be used going forward.
The vast majority of npm modules use semantic versioning, where it's almost become an expectation. Having to pin exact versions because of a different scheme is uncommon and makes upgrading more complicated.
Sure, but this ignores all of the challenges associated with semantic versioning. What happens if one plugin introduces a breaking change? Are we going to end up with v312.4.48? The effective result would be the same as bumping minor version in v0. Most notably, React Native uses a similar versioning strategy https://reactnative.dev/contributing/versioning-policy
@mrlubos React uses semantic versioning and the latest release is 19. There's nothing wrong with high version numbers; The latest Postgres is 20, the latest Chrome is 140, the latest Firefox is 142, Vite is already at version 7, etc.
In terms of the plugins, those would typically be separate packages that each have their own version in dependent from the core (they don't have to be separate repos). Until they are separate packages, yes, any introduction of a breaking change is worth a new major version since all breaking changes are significant.
Most tooling around versioning in JS assumes semver. npm upgrade, Dependabot, etc. The fact that it's necessary to pin a minor version to prevent these tools from behaving normally is an indication.
What's the complication with pinning?
What's the complication with pinning?
First, it's very uncommon. I had to look up what the -E flag was even doing in npm when I saw it in the docs.
Second, developers in the JS ecosystem are used to doing periodic updates. I can no longer do an npm upgrade without also remembering that I need to manually check changelogs for a particular package.
Third, pinning is usually reserved for temporarily dealing with problematic packages. When a package releases a breaking change by accident you pin to the previous version until it's fixed.
Fourth, pinned versions render tooling like dependabot mostly useless. It won't create PRs to update a package that's pinned.
First, it's very uncommon. I had to look up what the
-Eflag was even doing in npm when I saw it in the docs.
You're not required to pin the version. In fact, many people don't. It's simply a recommendation which I believe will lead to the best experience. Locking minor range (~) works and so does locking major range (^), provided you acknowledge how v0 versioning works.
Dynamic ranges might result in huge diffs depending on project configuration and specific update. This could be a long discussion.
tl;dr You can use dynamic ranges.
Second, developers in the JS ecosystem are used to doing periodic updates. I can no longer do an npm upgrade without also remembering that I need to manually check changelogs for a particular package.
You can still upgrade as with any other package. If there's a breaking change, your CI checks will catch it.
I would also say you should always read changelogs. However, if you want to automate dependency updates, having robust CI checks is a prerequisite regardless of the versioning strategy.
Third, pinning is usually reserved for temporarily dealing with problematic packages. When a package releases a breaking change by accident you pin to the previous version until it's fixed.
Pinning is mainly used for reproducible builds. This is the intended effect with our recommendation.
Fourth, pinned versions render tooling like dependabot mostly useless. It won't create PRs to update a package that's pinned.
This depends on your tooling. Renovate has an updatePinnedDependencies option for this.
You're not required to pin the version. In fact, many people don't. It's simply a recommendation which I believe will lead to the best experience. Locking minor range (
~) works and so does locking major range (^), provided you acknowledge how v0 versioning works.
Sure, but this is more complexity/work for the developer. It requires they treat your package differently than others. The fact that you need to document your versioning illustrates that it goes against expectation.
You can still upgrade as with any other package. If there's a breaking change, your CI checks will catch it.
Sure, but you have two types of breaking changes. "breaking changes" and "significant breaking changes". Any breaking change is significant since everyone's opinion on severity will be different. That's why semver has them only happening on major versions.
I would also say you should always read changelogs. However, if you want to automate dependency updates, having robust CI checks is a prerequisite regardless of the versioning strategy.
Sure, but if developer A checks out a project and gets 1.0.0 and then developer B checks it out the following day and gets 1.1.0 neither is going through CI/CD or reading the changelogs. They're just pulling from the registry and should both expect similar behaviour.
Pinning is mainly used for reproducible builds. This is the intended effect with our recommendation.
This is in reference to packages that don't follow semver.
This depends on your tooling. Renovate has an
updatePinnedDependenciesoption for this.
Sure, but now you're also requiring that I update tooling to accommodate an uncommon pattern.
Semver is a known, well documented pattern. Most of the JS ecosystem expects it which is why most packages don't have to document their versioning scheme.
You have the opportunity to inherit this scheme when you hit a 1.0.0. So consider this thread to be my vote, as developer using your package, in favour of that happening.
I don't feel this is uncommon pattern
https://semver.org/ states
“Major version zero (0.y.z) is for initial development. Anything MAY change at any time. The public API SHOULD NOT be considered stable.”
“The simplest thing to do is start your initial development release at 0.1.0 and then increment the minor version for each subsequent release.”
looks perfectly as semver!
more here https://docs.npmjs.com/cli/v6/using-npm/semver
Caret Ranges ^1.2.3 ^0.2.5 ^0.0.4 Allows changes that do not modify the left-most non-zero digit in the [major, minor, patch] tuple. In other words, this allows patch and minor updates for versions 1.0.0 and above, patch updates for versions 0.X >=0.1.0, and no updates for versions 0.0.X.
Many authors treat a 0.x version as if the x were the major "breaking-change" indicator.
Caret ranges are ideal when an author may make breaking changes between 0.2.4 and 0.3.0 releases, which is a common practice. However, it presumes that there will not be breaking changes between 0.2.4 and 0.2.5. It allows for changes that are presumed to be additive (but non-breaking), according to commonly observed practices.
^1.2.3 := >=1.2.3 <2.0.0 ^0.2.3 := >=0.2.3 <0.3.0 ^0.0.3 := >=0.0.3 <0.0.4
To add perspective from our org (happy hey-api users!): we have automation pipelines that continuously update minor and patch versions of dependencies across all our repos. Hey-api did create much more work for the team to keep up compared to our other dependencies. With exception of hey-api, those minor version updates don't require any code changes even for packages at 0.x.x.
But semantically you are right, 0.x.x should be treated as unstable in initial development.
EDIT: I guess what would help is a major release we can treat as stable. Do you have any plans for this?
@yegortokmakov Yes, but not any time soon.
The main issue today is even if I used semver with v1, it wouldn't make all the associated problems disappear. Namely, small plugin updates can produce breaking changes for their specific output. Are we really going to cut a new major release every time this happens? Smithy for example is only v3, that's the breaking release cadence I'd be aiming for.
While I expect breaking changes to happen less often once the core is stable, they will still happen because you rarely get the output right the first time. At the same time, you don't want to hold up releasing a plugin just because it's not polished. If you split plugins from the core and version only those, people will be confused about plugin <> core compatibility. So we'd need a sensible strategy around releases that keeps this in mind.
The core has to deal with the same problems. I didn't get it right the first time, but I didn't want to wait until it's perfect, either. I expected both of these things to happen, hence I took the v0 route. And instead of dedicating that time to thinking through the release strategy, I focus on the code.
That's my thinking in the nutshell and why things are the way they are. I don't think marking the current state as v1 is worth the tradeoff in velocity for end user stability. v1 for me looks like a fully baked product and we're not there yet. I do feel quite strongly about this but open to feedback if it addresses the concerns above and not just "I want this to be more stable" because yeah, so do I!
tl;dr stable core (WIP) -> fewer breaking changes to plugins -> fewer breaking changes to output <- needs a sensible release strategy
The main issue today is even if I used semver with v1, it wouldn't make all the associated problems disappear. Namely, small plugin updates can produce breaking changes for their specific output. Are we really going to cut a new major release every time this happens? Smithy for example is only v3, that's the breaking release cadence I'd be aiming for.
What's the downside of cutting a new major? If plugins are part of a monolithic code base then yes, a breaking change in one should force a new major version. Just like if you have a breaking change in an API that should happen in a new major version as well; that gives you room to make deprecations.
React is on 19, Angular is on 19, Node is on 25, Chrome is on 142, Firefox is on 145, etc. They all use semver and there's no confusion.
if it addresses the concerns above and not just "I want this to be more stable" because yeah, so do I!
I don't necessarily want this to be more stable, I just want breaking changes to be catalogued at major version markers so that it behaves like all my other dependencies.
It's an issue of consistency and expected behaviour, not stability.