Compare commits

...

1 Commits

Author SHA1 Message Date
clawbot
f5761b6227 Run every lint-class check inside Docker (closes #38)
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.
2026-08-10 12:52:56 +00:00
9 changed files with 295 additions and 50 deletions

View File

@@ -1,8 +1,18 @@
# Hugo static-site build image. The build runs `make check` (a clean # Hugo static-site build image. The build runs `make test` -- a clean
# `hugo --minify` production build, the `--printPathWarnings` lint # `hugo --minify` production build -- so the image build fails on any
# build, then the read-only prettier docs check), so the image build # template, content or config error.
# fails on any formatting or Hugo build error. This is what CI #
# (script/cibuild) runs on every push. # It deliberately does NOT run `make check`. `make check` runs
# script/lint, and script/lint is a `docker build` of Dockerfile.lint,
# so `RUN make check` here would be docker-in-docker inside a bare
# alpine with no docker client and no daemon socket. The checks are
# therefore split by where they run: the production build here, lint and
# the prettier format check in Dockerfile.lint. Do not reintroduce a
# `make check` (or a `make lint` / `make fmt-check`) line in this file.
#
# CI coverage is unaffected: script/cibuild builds this image and then
# calls script/lint and script/fmt-check, so every check in `make check`
# still runs on every push -- see script/cibuild.
# #
# Build this only via script/cibuild or script/docker: both pass the # Build this only via script/cibuild or script/docker: both pass the
# CHECK_EPOCH build argument that this file requires, and a bare # CHECK_EPOCH build argument that this file requires, and a bare
@@ -41,5 +51,5 @@ COPY . .
ARG CHECK_EPOCH ARG CHECK_EPOCH
RUN [ -n "$CHECK_EPOCH" ] || exit 1 RUN [ -n "$CHECK_EPOCH" ] || exit 1
# Run all checks - build fails if any check fails. # Run the production build - build fails if the site does not build.
RUN echo "check epoch: ${CHECK_EPOCH}" && make check RUN echo "check epoch: ${CHECK_EPOCH}" && make test

85
Dockerfile.lint Normal file
View File

@@ -0,0 +1,85 @@
# Lint image. Every lint-class check for this repo runs here and
# nowhere else: the checks are build steps, so a successful build IS a
# clean lint. There is no host lint path and no "am I already in a
# container?" bypass -- script/lint and script/fmt-check are reduced to
# building the stage below that carries their check.
#
# Build this only via script/lint or script/fmt-check: both pass the
# CHECK_EPOCH build argument that the stages here require, and a bare
# `docker build -f Dockerfile.lint .` fails by design. See the guards.
#
# Why this is a separate file from the main Dockerfile, and why that
# one no longer runs `make check`: `make check` runs script/lint, and
# script/lint is now a `docker build`. A `RUN make check` in an image
# would therefore be docker-in-docker inside a bare alpine with no
# docker client and no daemon socket. The checks are split by where
# they run instead -- the main Dockerfile runs the production build,
# this file runs lint and the format check -- so no image ever shells
# back into `make check`. script/cibuild drives all of them, so CI
# coverage is unchanged.
# alpine 3.21, 2026-02-28
FROM alpine@sha256:c3f8e73fdb79deaebaa2037150150191b9dcbfba68b4a46d70103204c53f4709 AS base
WORKDIR /src
# Keep these four instructions byte-identical to the main Dockerfile's,
# in the same order: Docker keys layers on the instruction chain, not on
# the file they live in, so an identical prefix means this stage is a
# cache hit against the main image's layers. script/bootstrap compiles
# the pinned Hugo from source, which is by far the most expensive step
# here, and it must not be paid twice.
COPY script/ script/
RUN script/bootstrap
COPY . .
# --- lint -------------------------------------------------------------
#
# The site's lint is a clean build that surfaces broken internal links
# and template path problems: `hugo` fails on build errors and
# --printPathWarnings reports render-target collisions.
#
# The flags are inlined rather than reached through `RUN script/lint`,
# and that is forced, not lazy: script/lint is the docker build that
# produces this stage, so calling it here is the recursion described
# above. Keep these flags in sync with script/lint's documentation.
FROM base AS lint
# CHECK_EPOCH is a per-invocation nonce supplied by script/lint. Without
# it an unchanged tree serves the check layer from cache: the lint never
# executes and the build still exits 0, which is precisely the false
# green this repo already fixed once in the main Dockerfile. Caching is
# explicitly waived for lint, so the value is expanded into the checked
# command as well as the guard -- two independent value-keyed
# invalidation points, so a cache miss never depends on BuildKit's
# treatment of an unreferenced ARG. Declared with no default: a default
# is a constant, and a constant is a stable cache key. ARG is
# stage-scoped, so the fmt-check stage below declares its own.
#
# Everything above this line still caches, so script/bootstrap is not
# rebuilt.
ARG CHECK_EPOCH
RUN [ -n "$CHECK_EPOCH" ] || exit 1
RUN echo "lint epoch: ${CHECK_EPOCH}" && hugo --minify --printPathWarnings
# --- fmt-check --------------------------------------------------------
#
# The read-only prettier check over this repo's markdown and CSS. Same
# version, same scope and same flags as script/fmt, which is the
# authoritative copy and stays on the host because it writes to the
# working tree; keep the two in sync. The exclusions live in
# .prettierignore with the reason for each.
#
# Built as a sibling of `lint` rather than stacked on top of it so that
# a --target build runs exactly one check, and so a lint failure and a
# formatting failure are reported independently.
FROM base AS fmt-check
# Same nonce, same reasoning as the lint stage above. Declared again
# because ARG does not cross a FROM.
ARG CHECK_EPOCH
RUN [ -n "$CHECK_EPOCH" ] || exit 1
RUN echo "fmt-check epoch: ${CHECK_EPOCH}" && \
npx --yes "prettier@3.4.2" --check \
'**/*.md' '**/*.css' --tab-width 4 --prose-wrap always

View File

@@ -39,8 +39,12 @@ build, and the formatting check:
make check make check
``` ```
The lint build and the formatting check run inside Docker, so `make check` needs
a working Docker daemon; there is no host fallback.
`make fmt` rewrites the repo's markdown and CSS to the project's prettier `make fmt` rewrites the repo's markdown and CSS to the project's prettier
settings; run it if `make check` fails on formatting. settings; run it if `make check` fails on formatting. It runs on the host,
because it writes to your working tree.
To contribute to this site, contact **sneak@sneak.berlin** for git repository To contribute to this site, contact **sneak@sneak.berlin** for git repository
access. access.
@@ -61,22 +65,41 @@ provide:
git pre-commit hook git pre-commit hook
- `script/test` — the correctness check: a clean `hugo --minify` production - `script/test` — the correctness check: a clean `hugo --minify` production
build build
- `script/lint` — a clean build that surfaces broken links and path collisions - `script/lint` — a clean build that surfaces broken links and path collisions,
run inside Docker: it builds the `lint` stage of `Dockerfile.lint`, where the
check is a build step, so a successful build is a clean lint
- `script/fmt` — format every markdown and CSS file in the repo with prettier; - `script/fmt` — format every markdown and CSS file in the repo with prettier;
the exclusions live in `.prettierignore` with the reason for each the exclusions live in `.prettierignore` with the reason for each. The one
- `script/fmt-check` — check that formatting (read-only) prettier entrypoint that runs on the host, because it writes to your working
tree
- `script/fmt-check` — check that formatting (read-only), also inside Docker:
the `fmt-check` stage of `Dockerfile.lint`
- `script/check` — run `script/test`, `script/lint`, then `script/fmt-check`; - `script/check` — run `script/test`, `script/lint`, then `script/fmt-check`;
modifies no tracked files modifies no tracked files
- `script/docker` — build the Docker image tagged with the project name - `script/docker` — build the Docker image tagged with the project name
- `script/cibuild` — the CI build; the Dockerfile runs `make check` - `script/cibuild` — the CI build: the main image (the production build), then
`script/lint` and `script/fmt-check`
- `script/install-precommit` — install the git pre-commit hook that runs - `script/install-precommit` — install the git pre-commit hook that runs
`script/check` `script/check`
Build the image through `script/cibuild` or `script/docker` only. Both pass a Every lint run for this repo happens inside a container. `script/lint` and
per-invocation `CHECK_EPOCH` build argument that the Dockerfile requires, so the `script/fmt-check` have no host path and no "already inside a container?"
`make check` layer can never be served from cache — without it Docker returns a branch, so what a developer runs and what CI runs are the same build.
green it did not earn. A bare `docker build .` fails closed on the Dockerfile's
`CHECK_EPOCH` guard rather than caching its way to a false success. That is also why the main `Dockerfile` runs `make test` rather than
`make check`: `make check` calls `script/lint`, which is itself a
`docker build`, so a `make check` inside an image would be docker-in-docker in a
bare Alpine with no docker client and no daemon socket. The checks are split by
where they run — the production build in `Dockerfile`, lint and the format check
in `Dockerfile.lint` — and `script/cibuild` drives all three, so CI coverage is
unchanged.
Build any image through `script/cibuild`, `script/docker`, `script/lint` or
`script/fmt-check` only. All four pass a per-invocation `CHECK_EPOCH` build
argument that the Dockerfiles require, so a check layer can never be served from
cache — without it Docker returns a green it did not earn. A bare `docker build`
fails closed on the `CHECK_EPOCH` guard rather than caching its way to a false
success.
A convenience `make serve` target runs `hugo server` for local preview. A convenience `make serve` target runs `hugo server` for local preview.

101
TODO.md
View File

@@ -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 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 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 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 # Next Step
Move the artifact actions in `.gitea/workflows/deploy.yml` to v4 once this Gitea Add the missing `cibuild` and `precommit` shims to the `Makefile`, so that every
Actions instance serves the v4 artifact protocol; they are pinned on the documented entrypoint has a make target and the documented "always use make
deprecated v3 line because v4 fails here (#20). This touches the live deploy targets" rule is actually satisfiable
path, so it needs a real workflow run to verify rather than a local check. (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 # 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 - 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 (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 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 # 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 - 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 - After the next deploy, confirm the `_headers` file actually took effect, on
both `https://lora.vegas/` and `https://www.lora.vegas/`: `curl -sSI` against both `https://lora.vegas/` and `https://www.lora.vegas/`: `curl -sSI` against
each must show `strict-transport-security` or `content-security-policy`. 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 `includeSubDomains` rests on `www.lora.vegas` being served by this same Pages
project, which was established behaviourally from identical response bodies project, which was established behaviourally from identical response bodies
rather than from the Cloudflare dashboard. If `www` turns out not to be 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`. - Decide the HSTS `includeSubDomains` and `preload` posture for `lora.vegas`.
Both are owner calls: neither can be walked back inside the max-age window, Both are owner calls: neither can be walked back inside the max-age window,
and `includeSubDomains` binds hostnames this repo does not control (#14) and `includeSubDomains` binds hostnames this repo does not control
- Sync the reformat of `REPO_POLICIES.md` back upstream to `prompts` so the (https://git.eeqj.de/sneak/lora.vegas/issues/14)
canonical copy is clean under the shared prettier settings and future syncs
are a straight byte copy
- Verify the Cloudflare Pages deploy still works after the workflow changes - Verify the Cloudflare Pages deploy still works after the workflow changes
- Keep mesh channel and signal group listings current

View File

@@ -3,6 +3,11 @@
# scripts-to-rule-them-all. Must not modify any tracked files. Runs the # scripts-to-rule-them-all. Must not modify any tracked files. Runs the
# canonical order: the clean production build, then the lint build that # canonical order: the clean production build, then the lint build that
# reports path warnings, then the read-only formatting check. # reports path warnings, then the read-only formatting check.
#
# The last two run inside Docker (they build stages of Dockerfile.lint),
# so this script needs a working docker daemon. That is deliberate:
# every lint run for this repo happens in a container, and there is no
# host fallback to drop back to.
set -eu set -eu
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"

View File

@@ -1,17 +1,32 @@
#!/bin/sh #!/bin/sh
# script/cibuild: run the CI build. The Dockerfile runs `make check`, # script/cibuild: run the CI build. The Gitea workflow runs this on
# so a successful build implies all checks pass. The Gitea workflow # push, and it is the single entrypoint that covers everything:
# runs this on push.
# #
# That implication only holds because of CHECK_EPOCH. Docker keys the # 1. the main Dockerfile, which runs the clean `hugo --minify`
# `RUN make check` layer on content, so on an unchanged tree it is # production build (`make test`)
# served from cache: the checks never execute and the build still exits # 2. script/lint, which builds Dockerfile.lint's `lint` stage
# 0. Passing a value that differs on every invocation invalidates that # 3. script/fmt-check, which builds Dockerfile.lint's `fmt-check`
# layer and everything below it, while the script/bootstrap toolchain # stage
# layer above it keeps caching. #
# Steps 2 and 3 are delegated to the same scripts a developer runs, so
# CI cannot drift from `make check`. They are separate builds rather
# than a `RUN make check` inside the main image because script/lint is
# itself a `docker build`: shelling back into `make check` from an image
# would be docker-in-docker inside a bare alpine with no docker client
# and no daemon socket. See Dockerfile.lint for the full reasoning.
#
# The main image build below only implies a passing production build
# because of CHECK_EPOCH. Docker keys the `RUN make test` layer on
# content, so on an unchanged tree it is served from cache: the build
# never executes and the image build still exits 0. Passing a value that
# differs on every invocation invalidates that layer and everything
# below it, while the script/bootstrap toolchain layer above it keeps
# caching. script/lint and script/fmt-check each do the same for their
# own stage.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
@@ -25,6 +40,9 @@ main() {
# 0, so `$$` is appended to cover that degradation. # 0, so `$$` is appended to cover that degradation.
epoch="$(date +%s%N)$$" epoch="$(date +%s%N)$$"
docker build --build-arg CHECK_EPOCH="$epoch" . docker build --build-arg CHECK_EPOCH="$epoch" .
"$SCRIPT_DIR/lint"
"$SCRIPT_DIR/fmt-check"
} }
main "$@" main "$@"

View File

@@ -6,6 +6,12 @@
# the reason next to each entry: the Hugo layout templates, which are # the reason next to each entry: the Hugo layout templates, which are
# Go templates and not HTML, and content/, whose reformatting was # Go templates and not HTML, and content/, whose reformatting was
# measured to change the rendered page. # measured to change the rendered page.
#
# This runs on the host, unlike the read-only check: it rewrites the
# working tree, which a container build cannot do. It is therefore the
# authoritative copy of the prettier version, scope and flags -- the
# fmt-check stage of Dockerfile.lint duplicates them and must be kept in
# sync with this file.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"

View File

@@ -1,17 +1,27 @@
#!/bin/sh #!/bin/sh
# script/fmt-check: check the formatting of this repo's markdown and # script/fmt-check: check the formatting of this repo's markdown and
# CSS (read-only). Same scope and same settings as script/fmt - keep # CSS (read-only). Like script/lint, it runs only in Docker: it builds
# the two in sync - but fails instead of writing. # the `fmt-check` stage of Dockerfile.lint, where prettier runs as a
# build step. Leaving prettier to run on the host here would have left
# `make check` with a host lint path, which is the thing being removed.
#
# The version, scope and flags live in Dockerfile.lint and must stay in
# sync with script/fmt, which is the authoritative copy and stays on the
# host because it writes to the working tree.
#
# Dockerfile.lint requires the CHECK_EPOCH build argument, generated
# here exactly as script/cibuild generates it -- see that script for why
# the check layer must not be allowed to cache, and why the value is
# built in an assignment rather than inline.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
PRETTIER_VERSION="3.4.2"
main() { main() {
cd "$ROOT" cd "$ROOT"
npx --yes "prettier@${PRETTIER_VERSION}" --check \ epoch="$(date +%s%N)$$"
'**/*.md' '**/*.css' --tab-width 4 --prose-wrap always docker build -f Dockerfile.lint --target fmt-check \
--build-arg CHECK_EPOCH="$epoch" .
} }
main "$@" main "$@"

View File

@@ -1,15 +1,26 @@
#!/bin/sh #!/bin/sh
# script/lint: this Hugo site has no dedicated linter, so the lint gate # script/lint: run the lint. This Hugo site has no dedicated linter, so
# is a clean build that surfaces broken internal links and template # the lint gate is a clean build that surfaces broken internal links and
# path problems. It is a real check: `hugo` fails on build errors, and # template path problems -- but where it runs is not negotiable: every
# --printPathWarnings reports render-target collisions. # lint run happens inside a Docker container, so this script does
# nothing except build the `lint` stage of Dockerfile.lint. The check is
# a build step there, so a successful build is a clean lint. There is
# deliberately no host fallback and no "already inside a container?"
# branch: either would be a host lint path wearing a disguise.
#
# Dockerfile.lint requires the CHECK_EPOCH build argument, generated
# here exactly as script/cibuild generates it -- see that script for why
# the check layer must not be allowed to cache, and why the value is
# built in an assignment rather than inline.
set -eu set -eu
ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" ROOT="$(cd "$(dirname "$0")/.." && pwd -P)"
main() { main() {
cd "$ROOT" cd "$ROOT"
hugo --minify --printPathWarnings epoch="$(date +%s%N)$$"
docker build -f Dockerfile.lint --target lint \
--build-arg CHECK_EPOCH="$epoch" .
} }
main "$@" main "$@"