Run every lint-class check inside Docker (closes #38)
All checks were successful
check / check (push) Successful in 1m9s
All checks were successful
check / check (push) Successful in 1m9s
Add a root Dockerfile.lint that carries the checks as build steps -- a `lint` stage running `hugo --minify --printPathWarnings` and a `fmt-check` stage running the prettier check -- and reduce script/lint and script/fmt-check to building their stage. A successful build is a clean check. There is no host path and deliberately no "am I already inside a container?" branch, which would be a host lint path in disguise. The two stages share a `base` whose first four instructions are byte-identical to the main Dockerfile's, so the expensive `RUN script/bootstrap` layer that compiles the pinned Hugo from source is a cache hit against the main image instead of a second build of the same thing. Resolve the resulting recursion by splitting the checks by where they run, not with an escape hatch. `make check` runs script/lint, so the main Dockerfile can no longer `RUN make check`: that would be docker-in-docker inside a bare Alpine with no docker client and no daemon socket, and script/cibuild is what CI runs on every push. The main Dockerfile therefore runs `make test`, the production build, and script/cibuild builds it and then calls script/lint and script/fmt-check. CI still covers the production build, lint and the format check, and it runs exactly what a developer runs. script/fmt stays on the host because it rewrites the working tree, which a container build cannot do. That makes it the authoritative copy of the prettier version, scope and flags that the fmt-check stage duplicates; both sides carry a keep-in-sync note. The duplication is forced: any `RUN script/fmt-check` inside the image is the recursion again. Caching is waived for the checks in the shape this repo already settled: `ARG CHECK_EPOCH` with no default, declared and guarded separately in each stage because ARG does not cross a FROM, with the value expanded into the checked command as well as the guard so invalidation does not rest on BuildKit's treatment of an unreferenced ARG. All four image-building entrypoints now generate and pass it -- script/cibuild, script/docker, script/lint, script/fmt-check. Verified: two consecutive script/lint runs on an unchanged tree both executed hugo for real, with script/bootstrap CACHED; a constant-epoch counterfactual restored the false green (exit 0, lint layer CACHED, no hugo output); an empty epoch failed closed on the guard; a broken template failed the lint stage and an unformatted README failed the fmt-check stage, both reverted and re-run clean; script/cibuild and `make check` are green with all three checks demonstrably executing.
This commit is contained in:
101
TODO.md
101
TODO.md
@@ -20,17 +20,64 @@ wrangler CLI install, an exact version), and the Hugo that builds the published
|
||||
site is a deliberate pinned version rather than whatever the base image's
|
||||
package repo serves. The site now ships a Cloudflare Pages `_headers` file, so
|
||||
its response security headers are declared in the repo instead of being whatever
|
||||
the edge defaults to — unverified in production until the next deploy.
|
||||
the edge defaults to — unverified in production until the next deploy. Every
|
||||
lint-class check now runs inside a container and nowhere else: `script/lint` and
|
||||
`script/fmt-check` build stages of `Dockerfile.lint`, with no host path to fall
|
||||
back to.
|
||||
|
||||
# Next Step
|
||||
|
||||
Move the artifact actions in `.gitea/workflows/deploy.yml` to v4 once this Gitea
|
||||
Actions instance serves the v4 artifact protocol; they are pinned on the
|
||||
deprecated v3 line because v4 fails here (#20). This touches the live deploy
|
||||
path, so it needs a real workflow run to verify rather than a local check.
|
||||
Add the missing `cibuild` and `precommit` shims to the `Makefile`, so that every
|
||||
documented entrypoint has a make target and the documented "always use make
|
||||
targets" rule is actually satisfiable
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/34). Done when `make cibuild` and
|
||||
`make precommit` exist, are declared `.PHONY`, and the README Entrypoints
|
||||
section matches.
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-08-10: moved every lint-class check into Docker
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/38). A new root `Dockerfile.lint`
|
||||
carries the checks as build steps — a `lint` stage running
|
||||
`hugo --minify --printPathWarnings` and a `fmt-check` stage running the
|
||||
prettier check — on a shared `base` stage whose first four instructions are
|
||||
byte-identical to the main `Dockerfile`'s, so the expensive
|
||||
`RUN script/bootstrap` layer that compiles Hugo from source is a cache hit
|
||||
against the main image rather than a second build of the same thing.
|
||||
`script/lint` and `script/fmt-check` are now nothing but a `docker build` of
|
||||
their stage; there is no host path and deliberately no "already inside a
|
||||
container?" branch, which would be a host lint path in disguise. The recursion
|
||||
this creates was resolved by splitting the checks by where they run rather
|
||||
than by adding an escape hatch: `make check` runs `script/lint`, so the main
|
||||
`Dockerfile` can no longer `RUN make check` — that would be docker-in-docker
|
||||
inside a bare Alpine with no docker client and no daemon socket, and
|
||||
`script/cibuild` is what CI runs on every push. The main `Dockerfile`
|
||||
therefore runs `make test`, the production build, and `script/cibuild` builds
|
||||
it and then calls `script/lint` and `script/fmt-check`, so CI still covers all
|
||||
three and cannot drift from what a developer runs. `script/fmt` stays on the
|
||||
host because it rewrites the working tree, which makes it the authoritative
|
||||
copy of the prettier version and flags that the `fmt-check` stage duplicates;
|
||||
both sides carry a keep-in-sync note, and that duplication is forced, since
|
||||
any `RUN script/fmt-check` inside the image is the recursion again. Caching is
|
||||
waived for the checks exactly as the main `Dockerfile` already does it:
|
||||
`ARG CHECK_EPOCH` with no default, declared and guarded separately in each
|
||||
stage because `ARG` does not cross a `FROM`, with the value expanded into the
|
||||
checked command as well as the guard. All four image-building entrypoints now
|
||||
generate and pass it — `script/cibuild`, `script/docker`, `script/lint`,
|
||||
`script/fmt-check` — which is the failure mode this repo already hit once, a
|
||||
Dockerfile guard asserting a property one entrypoint did not supply. Verified
|
||||
rather than assumed: two consecutive `script/lint` runs on an unchanged tree
|
||||
both executed hugo for real (second run 2.9s wall, `RUN script/bootstrap`
|
||||
`CACHED`, distinct epoch echoed, `Total in 37 ms` printed), a constant-epoch
|
||||
counterfactual restored the false green (exit 0 in 0.25s, lint layer `CACHED`,
|
||||
no hugo output at all), an empty epoch failed closed on the guard, a broken
|
||||
template failed the lint stage with hugo's own render error, and an over-long
|
||||
line appended to `README.md` failed the fmt-check stage with
|
||||
`[warn] README.md`; both violations were reverted and re-run clean. Not
|
||||
changed here, and still true: the lint stage fails on hugo build errors but
|
||||
not on render-target collisions, which `--printPathWarnings` only prints
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/25) — containerising the run
|
||||
neither fixes nor worsens that
|
||||
- 2026-08-10: added the `LICENSE` file and made the README say what it says
|
||||
(closes #10). The repo is public (`private: false` on the Gitea API, verified
|
||||
rather than assumed), so the owner's standing policy — MIT on any public repo
|
||||
@@ -226,8 +273,40 @@ path, so it needs a real workflow run to verify rather than a local check.
|
||||
|
||||
# Future Steps
|
||||
|
||||
Startable work first. Everything under "Blocked" waits on somebody or something
|
||||
outside this repo, so nothing there may be picked up as the Next Step.
|
||||
|
||||
- Make the prettier scope's exclusion of dot-directories explicit instead of
|
||||
leaning on `.gitignore` (https://git.eeqj.de/sneak/lora.vegas/issues/33)
|
||||
- Fix the README's SSH-only clone URL, and add the two entrypoints the
|
||||
Entrypoints section omits, `script/precommit` and `script/projectname`
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/36)
|
||||
- Drop the Go toolchain and module cache from the check image's final layer;
|
||||
they are needed to build hugo and dead weight afterwards
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/28)
|
||||
- Add a timeout guard to `script/test` and `script/lint` so a wedged build fails
|
||||
instead of hanging (https://git.eeqj.de/sneak/lora.vegas/issues/16)
|
||||
- Sync the reformat of `REPO_POLICIES.md` back upstream to `prompts` so the
|
||||
canonical copy is clean under the shared prettier settings and future syncs
|
||||
are a straight byte copy
|
||||
- Keep mesh channel and signal group listings current
|
||||
|
||||
## Blocked
|
||||
|
||||
- Decide whether `script/lint` should fail on render-target collisions rather
|
||||
than only print them; `--printPathWarnings` exits 0 today, so the signal is
|
||||
reported and not enforced. Owner call, since it changes what the gate rejects
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/25)
|
||||
- Move the artifact actions in `.gitea/workflows/deploy.yml` to v4 once this
|
||||
Gitea Actions instance serves the v4 artifact protocol; they are pinned on the
|
||||
deprecated v3 line because v4 fails here
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/20). This touches the live deploy
|
||||
path, so it needs a real workflow run to verify rather than a local check
|
||||
- Move the deploy container to a pinned node 22 so the wrangler pin can advance
|
||||
past 4.86.0 (#21)
|
||||
past 4.86.0 (https://git.eeqj.de/sneak/lora.vegas/issues/21)
|
||||
- Delete the stale remote branches `feat/initial-site` and `security-audit`;
|
||||
only the owner can remove them
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/15)
|
||||
- After the next deploy, confirm the `_headers` file actually took effect, on
|
||||
both `https://lora.vegas/` and `https://www.lora.vegas/`: `curl -sSI` against
|
||||
each must show `strict-transport-security` or `content-security-policy`.
|
||||
@@ -237,12 +316,10 @@ path, so it needs a real workflow run to verify rather than a local check.
|
||||
`includeSubDomains` rests on `www.lora.vegas` being served by this same Pages
|
||||
project, which was established behaviourally from identical response bodies
|
||||
rather than from the Cloudflare dashboard. If `www` turns out not to be
|
||||
covered, the `includeSubDomains` decision has to be revisited (#14)
|
||||
covered, the `includeSubDomains` decision has to be revisited
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/14)
|
||||
- Decide the HSTS `includeSubDomains` and `preload` posture for `lora.vegas`.
|
||||
Both are owner calls: neither can be walked back inside the max-age window,
|
||||
and `includeSubDomains` binds hostnames this repo does not control (#14)
|
||||
- Sync the reformat of `REPO_POLICIES.md` back upstream to `prompts` so the
|
||||
canonical copy is clean under the shared prettier settings and future syncs
|
||||
are a straight byte copy
|
||||
and `includeSubDomains` binds hostnames this repo does not control
|
||||
(https://git.eeqj.de/sneak/lora.vegas/issues/14)
|
||||
- Verify the Cloudflare Pages deploy still works after the workflow changes
|
||||
- Keep mesh channel and signal group listings current
|
||||
|
||||
Reference in New Issue
Block a user