All checks were successful
check / check (push) Successful in 1m2s
Linting now happens in one place only: a new root Dockerfile.lint copies the repo into the digest-pinned node image already used by Dockerfile and runs eslint and prettier as build steps, so a successful build is a clean lint. script/lint is reduced to building it, which also works where the docker daemon is remote and bind mounts are impossible. No host lint path survives: the "lint" script is gone from package.json, so there is no second, unpinned way to get a lint verdict. Caching is waived for lint, because a lint build over an unchanged tree returns success in well under a second having linted nothing. LINT_EPOCH is the cache buster and it fails closed exactly as CHECK_EPOCH does: an unset ARG is the empty string, which is a perfectly stable cache key, so the guard rejects it and a bare `docker build -f Dockerfile.lint .` errors out instead of serving a green it did not earn. Both linters sit below the guard, so a fresh epoch forces them to execute while the bootstrap and dependency layers above stay cached. That makes script/lint a docker build, which nothing inside a container may call. script/check calls script/lint, so the Dockerfile image can no longer run make check: the lint stage and its COPY --from=lint ordering hack are deleted, and the remaining stage runs make test and make build under the existing CHECK_EPOCH guard. script/cibuild is now the composite gate and builds the lint image first, so a lint failure is reported before the slower suite runs. The .dockerignore exclusions are unchanged and still apply to the lint build, including the .claude/ exclusion (eslint's flat config does not ignore dot-directories, so a nested worktree in the context would be linted) and the deliberate exception that keeps .gitignore in the context for prettier. A new test asserts no per-Dockerfile ignore file shadows the root one for either image, and test/packaging/lint-docker.test.ts asserts the whole shape: the docker-only lint path, the digest pin, manifests copied before sources, the fail-closed guard with both linters below it, the absence of a lint stage or make check in Dockerfile, and the build order in script/cibuild.
29 lines
1.1 KiB
Docker
29 lines
1.1 KiB
Docker
# Test and build image: the suite, then the compile.
|
|
#
|
|
# Linting deliberately does not happen here. `script/lint` is a build of
|
|
# Dockerfile.lint, and `script/check` calls `script/lint`, so running
|
|
# `make check` in this image would mean running `docker build` inside a
|
|
# container. Lint runs exactly once, in Dockerfile.lint; script/cibuild
|
|
# builds that first and this second.
|
|
# node 22.22.0 on Alpine 3.23.3 (node:22-alpine), 2026-08-09
|
|
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS check
|
|
WORKDIR /app
|
|
|
|
COPY script/ script/
|
|
COPY package.json yarn.lock ./
|
|
RUN script/bootstrap
|
|
COPY . .
|
|
|
|
# CHECK_EPOCH is a cache buster: without it Docker serves the test layer 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 test
|
|
|
|
ARG CHECK_EPOCH
|
|
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
|
RUN make build
|