💾 Archived View for d.moonfire.us › blog › 2023 › 02 › 08 › package-management-versions captured on 2023-12-28 at 15:50:10. Gemini links have been rewritten to link to archived content
⬅️ Previous capture (2023-09-28)
-=-=-=-=-=-=-
The start of almost every package journey starts with versions. This is how one distinguishes between different copies and hopefully makes sense of when to get the latest version verses only updating a few or not updating at all.
This is going to be a series of posts, but I have no idea of how fast I'll be writing them out. I want to work out my ideas, maybe have a few conversations, and then start to move to more technical concepts.
2023-02-07 Package Management - Introduction
2023-02-08 Package Management - Versions
2023-02-12 Package Management - Identifiers
2023-02-13 Package Management - Dependencies
2023-09-20 Package Management - Identifiers 2
2023-11-30 Package Management - Formats and Registries
These days, almost every package system proclaims that they use semantic versions[1] and write the infrastructure as such. I love semantic versions with a passion, including putting them in my novels and stories. They are relatively clear and concise, and easy for me to understand: breaking-things.adding-things.fixing-things.
The problem is, semantic versions are hard to manage when it comes to packages and dependencies, not to mention the business practices.
The biggest one are the ones who won't use it. One of the library I use during my day job is EasyQuery[2] because they have a .NET and a JS library and give me a lot of flexibility in creating dynamic queries. They also are responsive to bug issues. What they don't do is follow semantic versions. They use romantic versions with every x.y version being a breaking change from the previous one. This applies to their code on NPM and NuGet.
It is easy to manage, but you have to know that you can't go from 7.0.2 to 7.2.0 without major work, though semantic version says that it should have been a safe change.
My day job product is also romantically versioned, so I deal with this on a quarterly basis.
Another example is `SlimMessageBus.Host`. In version 1.14.1[3], it requires “.netstandard2.0”. In 1.15.0[4], it is “.netstandard2.1”. While this seems like safe change, it was a minor version bump that made it impossible for us to update because our product is still bound to “.netstandard2.0” until we can get off WebForms.
3: https://www.fuget.org/packages/SlimMessageBus.Host/1.14.1
4: https://www.fuget.org/packages/SlimMessageBus.Host/1.15.0
Since it is important, there are also a number of ways of communicating version ranges. Node uses the `^` for semantic versions and `~` for “only patch updates” (basically what EasyQuery uses).
NuGet uses a different system[5] such as `[1.0.0, 1.15.0)` with the `[]` meaning inclusive and `()` meaning exclusive. This is my preferred system, mainly because it hearkens back to my computer science days and allows a mid-version break such as SlimMessageBus. In that regard, it is more flexible because `^` and `~` can translate directly into the verison range.
5: https://learn.microsoft.com/en-us/nuget/concepts/package-versioning
I haven't gotten to dependencies yet, but the developer knows when something is safe update a package verses when it is not. EasyQuery knows that a patch is the breaking point, it would be better that they be able to communicate that as part of their metadata instead of requiring a customer support call. In effect, using the first two semantic version components (major and minor), EasyQuery 7.0.0 would be:
{ version: "7.0.0", majorUpdate: "[7.0.0, 7.1.0)", minorUpdate: "[7.0.0, 7.1.1)", // The third number is their minor versions }
On the other hand, SlimMessageBus would use (using an older version for illustrative purposes):
{ version: "1.10.0", majorUpdate: "[1.10.0, 1.15.0)", minorUpdate: "[1.10.0, 1.11.0)", }
I'm assuming that anything more precise is a patch and is safe to update. I'm also think Bakfu should lean heavily on well-defined undefined values. So if `majorUpdate`, then assume semantic version. If `minorUpdate` is missing, then use `majorUpdate` (which may be assumed).
The last bit is one that I've encountered a number of times while packaging books, but also while I was doing Debian packages[6]. The version of the package should be independent of the content. Using the above example, if SlimMessageBus realized they had made a breaking change, they should be able to update the 1.14.0 version of their package to indicate that 1.15.0 is a major breaking change.
6: https://manpages.debian.org/stretch/dpkg-dev/deb-version.5.en.html
Today, their only choice would be to remove the 1.15.0 (and any later) version and create a 2.0.0 version. This can be a lot of work because most likely someone realized it after there were a number of updates already out in the field.
Instead, if the package version was decoupled from the content version, then it would be possible to retroactively update critical information such as dependencies and safe upgrade ranges. Using SlimMessageBus again:
{ package: { version: "1.0.0", }, content: { version: "1.10.0", }, }
After they realize the breaking change:
{ package: { version: "2.0.0", }, content: { version: "1.10.0", majorUpdate: "[1.10.0, 1.15.0)", minorUpdate: "[1.10.0, 1.11.0)", }, }
Categories:
Tags:
Below are various useful links within this site and to related sites (not all have been converted over to Gemini).
https://d.moonfire.us/blog/2023/02/08/package-management-versions/