diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml index b6085d6..f5bf33f 100644 --- a/.gitea/workflows/ci.yml +++ b/.gitea/workflows/ci.yml @@ -52,9 +52,9 @@ jobs: # `image` extends the runner's default job image (catthehacker/act) # with Node 26 pre-planted in the tool cache layout, so setup-node's # version probe hits and never downloads (see docker/Dockerfile). The - # tag MUST equal the exact version pinned in `.node-version`; rebuild - # via `npm run build:runner-image -- --push` and repoint here on every - # Node bump. The volume bind-mounts the shared pages tree so the + # tag MUST equal the exact version pinned in `.node-version`; the bump + # ritual is documented in CONTRIBUTING.md § CI runner image. The volume + # bind-mounts the shared pages tree so the # coverage step below can write into it; the runner whitelists this # path via `container.valid_volumes` (docker-space `setup/gitea.sh`). container: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 3fa590d..ee23cfd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -90,6 +90,18 @@ This is why every test in the suite pairs an `expectTypeOf(...)` with an `assert - CI runs `npm run check` + `npm run test:ci` on every push to `main` — this is the authoritative gate. The one exception: a push headed by a release commit (`:rocket: Release x.y.z`) skips the full `build`/`maintain` jobs, because `create:release` pushes the tag for that exact commit right after and the tag run is the authoritative one (see `release-gate` in [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml)). - **Releases are NOT triggered by pushes.** Only the maintainer triggers a release (see [Publishing workflow](#publishing-workflow)). +## CI runner image + +The `build` / `maintain` / `publish` jobs run in `gitea.e1nsnull.de/tmu/act-ci:` ([docker/Dockerfile](./docker/Dockerfile)) — the runner's default act image with the Node distribution overlaid at the exact `/opt/hostedtoolcache` layout `actions/setup-node` probes before downloading, so no job pays the ~50 MB fetch. The image tag MUST equal the exact version pinned in `.node-version`; `release-gate` uses no Node and stays on the default image. The script is deliberately NOT an `npm run` script: building requires a docker daemon and registry credentials, so it belongs to no feedback tier — per [Script prefix convention](#script-prefix-convention), no existing prefix fits and that is the signal. + +Bumping Node is one coordinated change, committed as a unit: + +1. Edit `.node-version` to the new exact `x.y.z` — floats like `26` resolve to the latest patch at runtime and silently bust the baked entry; `scripts/runner-image.sh` refuses them. +2. `docker login gitea.e1nsnull.de` (user + package/access token), then `./scripts/runner-image.sh --push` — it reads the version from `.node-version` and builds/pushes `:`. +3. Repoint the three `container.image` tags in [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) to the same version. + +Skipping step 2 fails CI at image pull; skipping step 3 silently reverts to the per-job download. + ## Publishing workflow Publishing is CI-only by policy. Local `npm publish` is not supported. The maintainer triggers releases from `main`: diff --git a/package.json b/package.json index 3a9db00..aca4c28 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,6 @@ }, "scripts": { "build": "tsc -p tsconfig.build.json", - "build:runner-image": "./scripts/runner-image.sh", "prebuild": "rm -rf dist", "check": "npm run check:tsc && npm run check:oxlint && npm run check:oxfmt && npm run check:cspell", "check:cspell": "cspell lint ${LEFTHOOK_FILES:-.}",