Scope the release signal, collapse the contract onto the release version, publish the Hub overview #83

Merged
lilleman merged 17 commits from auto-release-only-when-affected into main 2026-08-22 22:19:59 +02:00
7 changed files with 19 additions and 19 deletions
Showing only changes of commit 5820264885 - Show all commits
+1 -1
View File
@@ -7,7 +7,7 @@ on:
workflow_dispatch:
inputs:
overview_version:
description: 'Version the overview should tell adopters to pull (e.g. 0.2.0)'
description: 'Version the overview should tell adopters to pull (e.g. 0.1.0)'
required: true
jobs:
+4 -4
View File
@@ -293,7 +293,7 @@ Revisit only if the stated reason stops holding.
docs and skip the gate over a source file that was gone. `src/ci-gate.test.ts` locks the flags as a
*text* guard — the test image ships neither `git` nor `bash`. This is why the Docker Hub overview is
`release-tooling/dockerhub-overview.md.tmpl` and not a `.md`: a release reads it and a unit test
guards it, so renaming it back would let a broken `{{VERSION}}` merge with its own guard skipped.
guards it, so giving it a `.md` name would let a broken `{{VERSION}}` merge with its own guard skipped.
- **CI docker logins share the runner host's Docker config.** The act_runner is host-mode, so
`docker login`/`logout` in the workflows mutate one shared `~/.docker/config.json`: concurrent jobs
can race (one job's logout can 401 another's push — recover by re-running), and tokens sit in that
@@ -353,9 +353,9 @@ one-time setup. A file-map or table row gets a clause, not a paragraph.
trailer, so a dependency added anywhere else never escalates the release version and nothing fails to
say so. A new manifest, compose file, custom manager or dep type is a decision: can it reach a
running Plainpages? If yes it needs a rule; if no, record nothing and let it ride the next patch.
- **`HOST_API_VERSION` *is* the release version — one number, not two.** Its `major.minor` must equal
the release tag's, and both release paths refuse a tag that disagrees
(`release-tooling/contract-version.ts`). The patch digit may lag on purpose: `checkApiVersion`
- **`HOST_API_VERSION` *is* the release version.** Its `major.minor` must equal the release tag's, and
both release paths refuse a tag that disagrees (`release-tooling/contract-version.ts`). The patch
digit may lag on purpose: `checkApiVersion`
ignores patch, and auto-release cuts patch releases with no commit to bump a constant in. So a
dependency update big enough to force a **minor** is plugin-visible by definition — `auto-release`
stops rather than tagging, and the fix is to bump `HOST_API_VERSION` to that `X.Y.0` in a PR, merge
+7 -7
View File
@@ -594,9 +594,9 @@ works without editing host config.
### Contract versioning
Each manifest declares `apiVersion` — a **semver** string naming the **Plainpages release** it was
built against. There is no second number to track: the host's `HOST_API_VERSION` *is* its release
version, so a plugin author reads one version off the image they run and writes it down. Both release
paths refuse a tag whose `major.minor` disagrees with the constant, so the two cannot drift.
built against. The host's `HOST_API_VERSION` *is* its release version, so a plugin author reads one
version off the image they run and writes it downthere is no separate contract number. Both
release paths refuse a tag whose `major.minor` disagrees with the constant, so the two cannot drift.
Patch releases are invisible here — `checkApiVersion` ignores the patch digit, which is what lets
dependency updates ship continuously without touching any plugin. At discovery the host parses both
@@ -612,10 +612,10 @@ provider/consumer semantics in `checkApiVersion`:
| missing / not a valid semver | `refuse` | **abort boot** — must be declared |
The plugin pins one exact version (no ranges, per the project's pinning rules); the *host* supplies
the caret-style compatibility. Note what a **minor** means now that one digit carries the whole
release: either the plugin contract changed, or a dependency moved far enough to warrant one. A
`warn` therefore says "built against an older release", not "new plugin features exist" — check the
Upgrading section for that release before assuming there is anything to adopt. While Plainpages is `0.x` every release shares major `0`, so a plugin
the caret-style compatibility. One digit carries the whole release, so a **minor** means either the
plugin contract changed or a dependency moved far enough to warrant one. A `warn` therefore says
"built against an older release" rather than promising new plugin features; check that release's
notes before assuming there is anything to adopt. While Plainpages is `0.x` every release shares major `0`, so a plugin
built against `0.1.0` still loads on a `0.9.0` host with a `warn`; reaching `1.0.0` refuses everything
built against `0.x`, which is the point of that milestone.
+1 -1
View File
@@ -17,6 +17,6 @@ test("the real template renders clean and pins no literal image tag", () => {
const rendered = renderOverview(readFileSync("release-tooling/dockerhub-overview.md.tmpl", "utf8"), "9.9.9");
assert.deepEqual(leftoverPlaceholders(rendered), []);
assert.match(rendered, /larvit\/plainpages:9\.9\.9/); // the placeholder actually reaches the examples
// A hardcoded version here is what went stale on the live page; the release must own every one.
// The release owns every image tag on the page, so a literal one must not survive rendering.
assert.doesNotMatch(rendered, /larvit\/plainpages:\d+\.\d+\.\d+(?<!9\.9\.9)/);
});
+2 -2
View File
@@ -1,6 +1,6 @@
// Publishes the Docker Hub repository overview from dockerhub-overview.md.tmpl. The page is the
// first thing an adopter copies, and its image tags were maintained by hand — they pointed at a
// version that no longer existed. `{{VERSION}}` is rendered from the release, so they cannot.
// first thing an adopter copies, so `{{VERSION}}` is rendered from the release being published
// rather than written by hand.
const HUB = "https://hub.docker.com/v2";
+1 -1
View File
@@ -6,7 +6,7 @@
"automerge": true,
"packageRules": [
{
"description": "The host's own runtime deps. Release-Bump is opt-in per surface (README → CI/CD) because updateType rates the dependency's own jump, not its effect here — unscoped, the bot's self-update bumped the product",
"description": "The host's own runtime deps. Release-Bump is opt-in per surface (README → CI/CD): updateType rates the dependency's own jump, not whether it reaches a running Plainpages",
"matchDepTypes": ["dependencies"],
"matchFileNames": ["package.json"],
"matchManagers": ["npm"],
+3 -3
View File
@@ -8,9 +8,9 @@ import type { RequestContext } from "../http/context.ts";
import type { NavNode } from "../ui/nav.ts";
import type { StorageCredentials } from "./storage.ts";
// The Plainpages release this contract ships in — one version, not a second one to track. Its
// major.minor must equal the release tag's; `release.yml` refuses to promote a tag that disagrees.
// The patch digit may lag, since checkApiVersion ignores patch and auto-release cuts patches itself.
// The Plainpages release this contract ships in. Its major.minor must equal the release tag's, and
// both release paths refuse a tag that disagrees. The patch digit may lag: checkApiVersion ignores
// patch, and auto-release cuts patch releases with no commit to bump this in.
export const HOST_API_VERSION = "0.1.0";
export type HttpMethod = "DELETE" | "GET" | "HEAD" | "PATCH" | "POST" | "PUT";