Gate the build on Docker lint and test phases (closes #40, closes #30)
check / check (push) Successful in 5m12s
check / check (push) Successful in 5m12s
Per the owner ruling on issue 40, linting and testing are phases of the main Dockerfile rather than a separate lint file. script/lint and script/test build one phase each by name with caching disabled, and the final stage copies a harmless file from each so the image cannot be built unless both passed. A stage that is not the last is built only when something depends on it or --target names it, so the gates are invoked by name and the edges kept. script/check runs the gates and builds no image of its own; script/cibuild bootstraps first, because CI runs it alone and fmt-check is native. fmt and fmt-check source nvm for the pinned node before calling yarn, which bootstrap installs but leaves off its caller's PATH. Every build in script/ is tagged and uncached. Issue 30 closes too: a container has its own lint cache and lock. Model: opus-5
This commit is contained in:
@@ -72,9 +72,13 @@ Template files can be fetched from:
|
||||
declared in the stage that compiles. **No stage calls `git describe`** —
|
||||
`.dockerignore` excludes `.git`, so it yields an empty version without
|
||||
failing the build.
|
||||
- All Dockerfiles must run `make check` as a build step
|
||||
- Server: also builds and runs the application
|
||||
- Non-server: brings up dev environment and runs `make check`
|
||||
- The Dockerfile carries a `lint` phase and a `test` phase, each invoking
|
||||
its tool directly rather than through `make` or `script/`, and the final
|
||||
stage carries a `COPY --from=` of a harmless file from each so the image
|
||||
cannot be built unless both passed. Keep the final stage last: a stage
|
||||
nothing depends on is built only when `--target` names it.
|
||||
- Server: the final stage builds and runs the application
|
||||
- Non-server: the final stage brings up the dev environment
|
||||
- Image pinned by sha256 hash with version/date comment
|
||||
- [ ] Gitea Actions workflow at `.gitea/workflows/check.yml` that runs
|
||||
`script/cibuild` on push — reference
|
||||
@@ -100,21 +104,34 @@ are thin shims calling them. Model scripts:
|
||||
installs
|
||||
- [ ] `script/setup` / `make setup` — readies a fresh clone: runs `bootstrap`,
|
||||
then `install-precommit`, plus repo-specific init
|
||||
- [ ] `script/test` / `make test` — runs real tests, not a no-op (90-second
|
||||
timeout, 60-second hard cap on wall time)
|
||||
- [ ] `script/lint` / `make lint` — runs linter
|
||||
- [ ] `script/fmt` / `make fmt` — formats code (writes)
|
||||
- [ ] `script/fmt-check` / `make fmt-check` — checks formatting (read-only)
|
||||
- [ ] `script/test` / `make test` — `docker build --no-cache --target test .`,
|
||||
tagged; the phase runs real tests, not a no-op (90-second timeout,
|
||||
60-second hard cap on wall time)
|
||||
- [ ] `script/lint` / `make lint` — `docker build --no-cache --target lint .`,
|
||||
tagged. No lint verdict may come from a host invocation of the linter.
|
||||
- [ ] `script/fmt` / `make fmt` — formats code (writes; native, never in a
|
||||
container)
|
||||
- [ ] `script/fmt-check` / `make fmt-check` — checks formatting (read-only;
|
||||
native)
|
||||
- [ ] `script/check` / `make check` — runs `test`, `lint`, `fmt-check`; must not
|
||||
modify files
|
||||
- [ ] `script/projectname` — outputs the project name (used by `script/docker`
|
||||
for the image tag)
|
||||
- [ ] `script/docker` / `make docker` — builds Docker image, tagged via
|
||||
`script/projectname` (byte-identical across repos); passes `--no-cache`
|
||||
like `script/cibuild`
|
||||
- [ ] `script/cibuild` — cd to repo root, `docker build --no-cache .` (what CI
|
||||
runs; without `--no-cache` an unchanged tree serves the check layers from
|
||||
cache and the build reports a green it never ran)
|
||||
`script/projectname` (byte-identical across repos); `--no-cache`, plus the
|
||||
version as a build arg
|
||||
- [ ] `script/cibuild` — cd to repo root, run `script/bootstrap`, run
|
||||
`script/check`, then
|
||||
`docker build --no-cache --build-arg VERSION="$version" .` (what CI runs).
|
||||
The bootstrap is required: CI checks out and runs this alone, and
|
||||
`script/fmt-check` runs the formatter on the host.
|
||||
- [ ] `script/fmt` and `script/fmt-check` source nvm for the pinned node version
|
||||
before invoking `yarn`, as `script/bootstrap`'s own install step does.
|
||||
`script/bootstrap` leaves the node and yarn it installs off the `PATH` of
|
||||
the shell that called it, so a bare `yarn` exits 127 on a runner carrying
|
||||
nothing but docker and git.
|
||||
- [ ] Every `docker build` in `script/` is tagged, so no invocation leaves a
|
||||
dangling image behind
|
||||
- [ ] `script/precommit` — called by the pre-commit hook; runs `script/check`
|
||||
- [ ] `script/install-precommit` — installs the pre-commit hook that runs
|
||||
`script/precommit`
|
||||
@@ -126,8 +143,13 @@ are thin shims calling them. Model scripts:
|
||||
|
||||
- [ ] `make check` passes
|
||||
- [ ] `make docker` succeeds
|
||||
- [ ] `script/cibuild` succeeds and demonstrably executed the checks — a
|
||||
sub-second build, or `CACHED` on a check layer, means nothing ran
|
||||
- [ ] `script/cibuild` succeeds in a fresh clone on a host carrying nothing but
|
||||
docker and git, with no node or yarn on `PATH`, which is what CI has, and
|
||||
demonstrably executed the checks — a sub-second build, or `CACHED` on a
|
||||
gate layer, means nothing ran
|
||||
- [ ] Plant a lint violation and confirm both `make lint` and a plain
|
||||
`docker build .` fail on it; revert. A plain build that passes proves the
|
||||
final stage is missing its `COPY --from=` edge to the gate phases.
|
||||
- [ ] No secrets in repo, and none in the build context: enumerate a probe image
|
||||
rather than reading `.dockerignore`
|
||||
- [ ] No mutable image/package references
|
||||
|
||||
Reference in New Issue
Block a user