Versioning

Two audiences want different things from a version number, so a distribution gives them different ones.

Dates for end users

End users want to know their copy is current. 2023.12.01 against a latest of 2024.12.01 says a year has passed, immediately. 1.1.1 against 1.2.1 says nothing about time, and secure software needs regular updates — so bumping should be obviously overdue when it is.

A distribution is therefore versioned by the date it was released.

Semver for developers

Developers consuming a module want to know how much work an upgrade is. 1.2.3 to 1.2.4 is nothing; 2.0.0 is a morning. So modules are versioned by semver and tagged in their own repositories.

Tags go on the module, not the distribution. A monorepo that tags every package fills its history with noise; here each module has its own repository, so its tags are its own.

Where a version comes from

The submodule commit is the pin. The version is whatever the package.json inside that checkout says. Nothing in the distribution repository restates it.

pnpm links the checkout into the workspace, so that is also the version everything resolves against — an app depending on a package gets the checkout, not a published copy. Modules are depended on with workspace:*, which names no version at all.

A repository that also wrote "@acme/design": "1.4.0" somewhere would be carrying a second copy of the same fact, free to drift from the first.

fg-dist sync reports the version it reads out of each checkout:

apps/widget      v1.2.0  -> v1.3.0 (minor)
packages/design  v1.4.0  up to date

Published distributions

A published distribution has no submodules, so its manifest carries the versions it shipped. That is what an extension is compared against, and the only place versions are written down.

Maintenance branches

A mature distribution has users slow to take major upgrades, which is what an LTS branch is for. How often to cut one is a trade between the cost of maintaining branches and the cost of forcing upgrades — once or twice a year, or every two, depending on how often you break things.

Yearly

lts-2023 is created in 2023 and maintained until 2025, tagged 2023.01-lts, 2023.02-lts and so on. lts-2024 is created in 2024 and maintained until 2026, tagged 2024.01-lts onwards.

Quarterly

lts-2023-q2 is created in Q2 2023 and maintained until Q2 2024, tagged 2023.04.01-lts, 2023.04.02-lts and so on. lts-2023-q4 runs Q4 2023 to Q4 2024 with 2023.10.01-lts onwards, and lts-2024-q2 runs Q2 2024 to Q2 2025 with 2024.04.01-lts onwards.

Modules do not get LTS

A module that changes often enough gets maintenance branches by patch version instead:

@fairgarden/id
  1.2.3   the latest version
  1.2.x   main, until it is branched off when 1.3.0 is released
  1.1.x   the previous maintenance branch

So @fairgarden/core:2023.04.01 depends on 1.1.x and @fairgarden/core:2023.10.01 on 1.2.x.

These are only needed when a distribution is using LTS. A module carried in an LTS distribution is one that is probably dependable enough to use on its own too.