All checks were successful
check / check (push) Successful in 23s
The image build reported a green it had not earned. `script/cibuild` is a bare `docker build .`, and with `COPY . .` followed by `RUN make check`, an unchanged tree served that layer from cache: the suite never ran and the build still exited 0, while the script's header comment asserted the opposite. CHECK_EPOCH, passed by `script/cibuild` and `script/docker`, changes the cache key of the check and build layers on every invocation. It is guarded, because an unset ARG is the empty string and therefore a stable key: without the guard a plain `docker build .` — the command the policy names, and the one anyone debugging types — would still get the false green. A missing argument is now a hard failure rather than a silent degradation to the behaviour the epoch was added to prevent. The Dockerfile is now two stages: `fmt-check` and `lint` run first, and the check stage takes a `COPY --from=lint` dependency on them, so a formatting mistake fails the build in seconds instead of racing the suite to the finish. Both stages stay pinned to the same digest. The remaining fixes are one-liners that had made the target unusable: `script/projectname` still printed the pre-rename name, so `make docker` tagged its image after a name this project dropped in May; `script/bootstrap` installed without fetching apt's package lists, which cannot work on a Debian base; and `.dockerignore` had drifted far enough from `.gitignore` to ship a ~100 MB compiled binary and any agent worktree under `.claude/` into the build context. The second of those is a correctness problem, not a size one — vitest globs a copied worktree's tests alongside the real ones and runs the suite twice over. `.gitignore` itself stays in the context, because prettier reads it as a default ignore file and dropping it would change what `make fmt-check` sees.
38 lines
1.3 KiB
Docker
38 lines
1.3 KiB
Docker
# Lint stage — fast feedback on formatting and lint issues
|
|
# node 22.22.0 on Alpine 3.23.3 (node:22-alpine), 2026-08-09
|
|
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS lint
|
|
WORKDIR /app
|
|
COPY script/ script/
|
|
COPY package.json yarn.lock ./
|
|
RUN script/bootstrap
|
|
COPY . .
|
|
RUN make fmt-check
|
|
RUN make lint
|
|
|
|
# Check stage — the full suite and the build
|
|
# node 22.22.0 on Alpine 3.23.3 (node:22-alpine), 2026-08-09
|
|
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS check
|
|
WORKDIR /app
|
|
|
|
# Force BuildKit to run the lint stage before proceeding. Without this the
|
|
# two stages run in parallel and a lint failure can lose the race.
|
|
COPY --from=lint /app/yarn.lock /dev/null
|
|
|
|
COPY script/ script/
|
|
COPY package.json yarn.lock ./
|
|
RUN script/bootstrap
|
|
COPY . .
|
|
|
|
# CHECK_EPOCH is a cache buster: without it Docker serves `make check` from
|
|
# cache on an unchanged tree, the suite never executes, and the build still
|
|
# exits 0. The guard makes an absent argument a hard failure — an unset ARG
|
|
# is the empty string, which is a perfectly stable cache key, so a plain
|
|
# `docker build .` would otherwise still get the false green. Fail closed.
|
|
ARG CHECK_EPOCH
|
|
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
|
RUN make check
|
|
|
|
ARG CHECK_EPOCH
|
|
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
|
RUN make build
|