Make lint and test phases of the Dockerfile (closes #96)
check / check (push) Successful in 33s
check / check (push) Successful in 33s
Follows the template: Dockerfile.lint is gone; the Dockerfile has a lint phase (eslint, prettier --check .) and a test phase (vitest, run as the node user so the not-writable-directory tests are not skipped), and its last stage compiles and depends on both. script/lint and script/test build one phase each with --no-cache; script/docker and script/cibuild pass --no-cache, so CHECK_EPOCH and LINT_EPOCH are removed. script/cibuild is the single image build, so CI runs lint and the tests once each. The tests that checked the old layout are deleted, REPO_POLICIES.md is re-copied and the README describes the new layout. Model: opus-5-5
This commit is contained in:
+58
-17
@@ -1,28 +1,69 @@
|
||||
# Test and build image: the suite, then the compile.
|
||||
# Lint phase. The linters are invoked directly rather than through `make
|
||||
# lint` or `script/lint`, which are themselves a docker build and would
|
||||
# recurse into a daemon that does not exist in a build step.
|
||||
#
|
||||
# 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
|
||||
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS lint
|
||||
|
||||
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
|
||||
RUN yarn run eslint .
|
||||
RUN yarn run prettier --check .
|
||||
|
||||
# Test phase, same shape and for the same reason. The suite runs without
|
||||
# verbose output first and is rerun verbosely only if it fails; the timeout
|
||||
# catches a hung test.
|
||||
#
|
||||
# node 22.22.0 on Alpine 3.23.3 (node:22-alpine), 2026-08-09
|
||||
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34 AS test
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY script/ script/
|
||||
COPY package.json yarn.lock ./
|
||||
RUN script/bootstrap
|
||||
|
||||
COPY . .
|
||||
|
||||
# Unlike the template, the suite runs as the image's non-root `node` user:
|
||||
# root ignores directory permissions, so the tests of a destination that is
|
||||
# not writable would otherwise be skipped. vitest writes into /app.
|
||||
RUN chown -R node:node /app
|
||||
USER node
|
||||
|
||||
RUN timeout 90 yarn run vitest run --reporter=dot || \
|
||||
{ echo "--- Rerunning with verbose for details ---"; \
|
||||
timeout 90 yarn run vitest run --reporter=verbose; exit 1; }
|
||||
|
||||
# Build stage, and the last stage: a plain `docker build .` names no target
|
||||
# and so builds this one. Nothing is wanted from the two phases above; the
|
||||
# copies are what make BuildKit build them first, so this image cannot be
|
||||
# produced unless lint and test passed. A stage appended after this one
|
||||
# would drop all three out of a plain build.
|
||||
#
|
||||
# node 22.22.0 on Alpine 3.23.3 (node:22-alpine), 2026-08-09
|
||||
FROM node@sha256:e4bf2a82ad0a4037d28035ae71529873c069b13eb0455466ae0bc13363826e34
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
COPY --from=lint /app/package.json /dev/null
|
||||
COPY --from=test /app/package.json /dev/null
|
||||
|
||||
COPY script/ script/
|
||||
COPY package.json yarn.lock ./
|
||||
RUN script/bootstrap
|
||||
|
||||
COPY . .
|
||||
|
||||
# The version is computed on the host and passed in, because
|
||||
# .dockerignore excludes .git.
|
||||
ARG VERSION=dev
|
||||
LABEL org.opencontainers.image.version="${VERSION}"
|
||||
|
||||
ARG CHECK_EPOCH
|
||||
RUN [ -n "$CHECK_EPOCH" ] || exit 1
|
||||
RUN make build
|
||||
|
||||
Reference in New Issue
Block a user