Skip to content

How the version is decided

Picking the next version is the most consequential thing colophon does, and the easiest to get quietly wrong. So the part that decides it is a pure function: no clock, no network, no filesystem, no git. Everything it needs arrives as an argument, the same inputs always produce the same decision, and the whole of it is testable as a table and reviewable without running anything.

The git reading happens one layer down, in a package whose only job is to produce what the decision needs: where the project is now, and what has landed since. Keeping the I/O in one narrow place is what buys the purity above it.

Type to bump

colophon reads Conventional Commits. The largest bump among the commits in range wins.

Type Bump
feat minor
fix patch
perf patch
refactor patch
anything else none
breaking (! or a BREAKING CHANGE: footer) major, with one exception below

perf and refactor releasing at all is a deliberate difference from releaser-pleaser, which releases on feat, fix and breaking changes only. A performance fix a user can feel, or a refactor that changes behaviour at the edges, is worth a patch release rather than sitting unreleased until something else happens to ship.

Commits that move nothing are counted and reported rather than dropped:

1 commit moved nothing.

That line is doing real work. A tool that only lists the commits which counted leaves you unable to tell a commit correctly ignored from one whose type was mistyped.

Breaking changes below 1.0

Above 1.0.0, a breaking change is a major bump. Below it, colophon holds the breaking change to a minor, and says why:

"why": "breaking change, held to a minor below 1.0 — promote deliberately with a `Release-As: 1.0.0` trailer"

The reasoning is that on a v0.x project a single BREAKING CHANGE: footer would otherwise cut v1.0.0. Declaring a stable API is a commitment to the people depending on it, and it should be a decision somebody makes on purpose rather than one that falls out of a commit message written on a Friday.

Promoting is then explicit, with a Release-As: trailer.

Overrides say so

When a Release-As: trailer sets the version, colophon reports the fact:

Note: version set to 1.0.0 by a Release-As: trailer

It does not quietly substitute the answer and leave you working out why the arithmetic looks wrong. Every departure from the computed version is visible in the output that shows the computation.

Version strings

A leading v is accepted on input and stripped, because every tag in this estate is written with the prefix and rejecting it would be pedantry rather than strictness. Anything that is not a valid semantic version is an error, and never a silent fallback to the computed value.

Reading the decision

--output json carries the whole thing, including a per-commit why:

{
  "current": "0.1.0",
  "from_tag": "v0.1.0",
  "next": "0.2.0",
  "bump": "minor",
  "releasable": true,
  "held": false,
  "reasons": [  ],
  "considered": 1
}

That is the shape to reach for when comparing colophon against whatever a project releases with today, because it is exact and it needs no credentials.