Give builds a version number #59

Closed
opened 2026-08-26 17:00:46 +00:00 by jcoffey-dev · 0 comments
Owner

ihasmail called itself 2.0 on About and 2.0.0 from /api/health, both hardcoded — four places in total, drifted from each other and from anything meaningful.

ihasmail v2.16.57
           │  │  └─ the pull request the commit came from
           │  └──── the Stalwart generation this build targets — 0.16
           └─────── ihasmail's own major

The first two live in the root package.json, so there is one place to bump them, and 16 becomes 17 when ihasmail moves to Stalwart 0.17. Dropping 0.15 is what makes that middle number honest — while two generations were supported it could not have been either.

Why nothing is committed

The PR number does not exist until the pull request has merged. A version written into the tree would always be describing a merge that hadn't happened yet, and every open branch would collide on the same line. So it is read from git at build time and never written back.

A commit that didn't arrive through a pull request carries the last number plus its own short SHA — 2.16.57+g1fa6578 — saying plainly that it is past that PR rather than quietly claiming to be it.

node scripts/version.mjs prints the version for a checkout.

Getting it into Docker

.dockerignore excludes .git deliberately, so an image build can't work any of this out. It takes --build-arg IHASMAIL_VERSION instead: the build stage bakes it into the bundle via a Vite define, and the runtime stage keeps it as an env var for the server.

Left out, it falls back to the base version from package.json rather than failing — so a version with no PR number on it means whoever built the image didn't pass one. That seemed better than a build that dies over a cosmetic string.

scripts/ is copied into the runtime image because the server resolves its version through it; there's no git in there to ask, which is the fallback's whole purpose.

Testing

226 web + 75 server tests, typecheck and build clean. Beyond that, the paths that matter here aren't unit-testable, so I exercised them for real:

check result
dev checkout, HEAD is a PR merge 2.16.57
dev checkout, HEAD is not (verified in a detached worktree at d5636e4) 2.16.58+gd5636e4
bundle after npm run build contains 2.16.57
/api/health on a dev checkout 2.16.57
real docker build --build-arg, queried inside the container 2.16.57, in both health and the bundle
same image with the arg left off 2.16.0 — falls back, doesn't crash

One thing this PR cannot fix

ihasmail-deploy.sh lives on the host, outside this repo. It currently runs a bare docker build, so production will report 2.16.0 until that line becomes:

docker build --build-arg IHASMAIL_VERSION="$(node scripts/version.mjs)" -t ihasmail:2.0 .

I've left that alone rather than reaching into the production host unasked. Happy to apply it at deploy time — it's also worth considering moving that script into the repo so it stops being the one piece that can silently fall out of step.

Merged 2026-08-26 as coffey-labs/ihasmail@0613b5bf29

Rebuilt from: git history, session transcript.

ihasmail called itself `2.0` on About and `2.0.0` from `/api/health`, both hardcoded — four places in total, drifted from each other and from anything meaningful. ``` ihasmail v2.16.57 │ │ └─ the pull request the commit came from │ └──── the Stalwart generation this build targets — 0.16 └─────── ihasmail's own major ``` The first two live in the root `package.json`, so there is one place to bump them, and `16` becomes `17` when ihasmail moves to Stalwart 0.17. **Dropping 0.15 is what makes that middle number honest** — while two generations were supported it could not have been either. ## Why nothing is committed The PR number does not exist until the pull request has merged. A version written into the tree would always be describing a merge that hadn't happened yet, and every open branch would collide on the same line. So it is read from git at build time and never written back. A commit that didn't arrive through a pull request carries the last number plus its own short SHA — `2.16.57+g1fa6578` — saying plainly that it is *past* that PR rather than quietly claiming to be it. `node scripts/version.mjs` prints the version for a checkout. ## Getting it into Docker `.dockerignore` excludes `.git` deliberately, so an image build can't work any of this out. It takes `--build-arg IHASMAIL_VERSION` instead: the build stage bakes it into the bundle via a Vite `define`, and the runtime stage keeps it as an env var for the server. Left out, it falls back to the base version from `package.json` rather than failing — so **a version with no PR number on it means whoever built the image didn't pass one.** That seemed better than a build that dies over a cosmetic string. `scripts/` is copied into the runtime image because the server resolves its version through it; there's no git in there to ask, which is the fallback's whole purpose. ## Testing 226 web + 75 server tests, typecheck and build clean. Beyond that, the paths that matter here aren't unit-testable, so I exercised them for real: | check | result | |---|---| | dev checkout, HEAD *is* a PR merge | `2.16.57` | | dev checkout, HEAD is *not* (verified in a detached worktree at `d5636e4`) | `2.16.58+gd5636e4` | | bundle after `npm run build` | contains `2.16.57` | | `/api/health` on a dev checkout | `2.16.57` | | real `docker build --build-arg`, queried inside the container | `2.16.57`, in both health and the bundle | | same image with the arg left off | `2.16.0` — falls back, doesn't crash | ## One thing this PR cannot fix `ihasmail-deploy.sh` lives on the host, outside this repo. It currently runs a bare `docker build`, so **production will report `2.16.0` until that line becomes**: ```bash docker build --build-arg IHASMAIL_VERSION="$(node scripts/version.mjs)" -t ihasmail:2.0 . ``` I've left that alone rather than reaching into the production host unasked. Happy to apply it at deploy time — it's also worth considering moving that script into the repo so it stops being the one piece that can silently fall out of step. **Merged** 2026-08-26 as coffey-labs/ihasmail@0613b5bf292d <sub>Rebuilt from: git history, session transcript.</sub>
This repo is archived. You cannot comment on issues.