diff --git a/Dockerfile b/Dockerfile index 4ca4ac3..5110e92 100644 --- a/Dockerfile +++ b/Dockerfile @@ -9,7 +9,8 @@ RUN go mod download COPY . . # Go half of fmt-check only: this image has no node, so no prettier. The -# markdown half runs in the mdfmt stage below. +# markdown half runs in the mdfmt stage below. The image has no gofumpt +# either; script/gofumpt builds the version it pins with `go run`. RUN make fmt-check-go # The linter directly, not `make lint`: script/lint builds this stage, and # there is no docker inside this build. diff --git a/Makefile b/Makefile index 8ba3c71..fc8f6eb 100644 --- a/Makefile +++ b/Makefile @@ -46,7 +46,7 @@ fmt-check: # Halves of fmt-check, for environments that have only one toolchain: # the Docker lint stage has Go but no node, the markdown stage the reverse. fmt-check-go: - @script/fmt-check-go + @script/gofumpt --check fmt-check-md: @script/prettier --check diff --git a/README.md b/README.md index edb1f46..be73a8e 100644 --- a/README.md +++ b/README.md @@ -91,16 +91,19 @@ provide: - `script/lint` — run `golangci-lint` in Docker: builds only the `lint` stage of the `Dockerfile` (the Go format check, then the linter), uncached so it runs every time, then removes the image -- `script/fmt` — format all code and docs (writes): `gofumpt` and +- `script/fmt` — format all code and docs (writes): `script/gofumpt --write` and `script/prettier --write` +- `script/gofumpt` — run `gofumpt` over every Go file in the repository in the + given mode, `--write` or `--check`, at the one version it pins (built on + demand by `go run`, so nothing installs it); `script/fmt`, `script/fmt-check` + and the Docker lint stage (`make fmt-check-go`) all go through it, so they + cannot disagree about Go formatting - `script/prettier` — run prettier over the repository's canonical file set (Markdown and JSON, minus `.prettierignore`) in the given mode, `--write` or `--check`; the single definition of that file set, so `script/fmt` and `script/fmt-check` cannot disagree about it -- `script/fmt-check` — check formatting without writing: `script/fmt-check-go` - plus `script/prettier --check` -- `script/fmt-check-go` — the Go half of `script/fmt-check`, on its own, for the - Docker lint stage, whose image has no node +- `script/fmt-check` — check formatting without writing: + `script/gofumpt --check` plus `script/prettier --check` - `script/check` — run `script/test`, `script/lint`, and `script/fmt-check` - `script/docker` — build the Docker image tagged with the project name - `script/cibuild` — CI entrypoint: builds the image with the same command as diff --git a/script/fmt b/script/fmt index fd81efa..f091088 100755 --- a/script/fmt +++ b/script/fmt @@ -7,8 +7,9 @@ ROOT="$(cd "$SCRIPT_DIR/.." && pwd -P)" main() { cd "$ROOT" - gofumpt -l -w mfer internal cmd - # Markdown and JSON, over the same file set script/fmt-check verifies. + # Go, then Markdown and JSON, through the same scripts script/fmt-check + # uses, so both see the same files and the same tool versions. + "$SCRIPT_DIR/gofumpt" --write "$SCRIPT_DIR/prettier" --write } diff --git a/script/fmt-check b/script/fmt-check index 1bd6882..b11390f 100755 --- a/script/fmt-check +++ b/script/fmt-check @@ -1,13 +1,13 @@ #!/bin/sh # script/fmt-check: check formatting (read-only). Same scope as -# script/fmt, but fails instead of writing: Go via script/fmt-check-go, +# script/fmt, but fails instead of writing: Go via script/gofumpt, # Markdown and JSON via script/prettier. set -eu SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)" main() { - "$SCRIPT_DIR/fmt-check-go" + "$SCRIPT_DIR/gofumpt" --check "$SCRIPT_DIR/prettier" --check } diff --git a/script/fmt-check-go b/script/fmt-check-go deleted file mode 100755 index e0248d4..0000000 --- a/script/fmt-check-go +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/sh -# script/fmt-check-go: check Go formatting (read-only). Split out from -# script/fmt-check so the Docker lint stage, whose image has no node and -# therefore no prettier, can run the Go half on its own. -set -eu - -ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" - -main() { - cd "$ROOT" - if [ -n "$(gofmt -l .)" ]; then - echo "gofmt: files need formatting:" >&2 - gofmt -l . >&2 - exit 1 - fi -} - -main "$@" diff --git a/script/gofumpt b/script/gofumpt new file mode 100755 index 0000000..66dba19 --- /dev/null +++ b/script/gofumpt @@ -0,0 +1,44 @@ +#!/bin/sh +# script/gofumpt: run gofumpt over this repo's Go files. +# +# Takes exactly one mode argument, --write or --check, and runs the same +# gofumpt version over the same files in both modes. script/fmt and +# script/fmt-check both go through here, and so does the Docker lint +# stage (make fmt-check-go), so what gets formatted and what gets +# verified cannot drift apart. +set -eu + +ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" + +# gofumpt v0.12.0, 2026-10-04. `go run` fetches and builds exactly this +# version, so neither a developer machine nor the lint image needs +# gofumpt installed. +GOFUMPT="mvdan.cc/gofumpt@v0.12.0" + +usage() { + echo "usage: script/gofumpt --write|--check" >&2 + exit 2 +} + +main() { + [ "$#" -eq 1 ] || usage + cd "$ROOT" + # Every Go file in the repo. gofumpt holds generated files, such as + # mfer/mf.pb.go, to gofmt's rules only. + case "$1" in + --write) go run "$GOFUMPT" -l -w . ;; + --check) + # Own line: a failing command inside `[ -n "$(...)" ]` does + # not trip `set -e`, so a gofumpt that never ran would pass. + unformatted="$(go run "$GOFUMPT" -l .)" + if [ -n "$unformatted" ]; then + echo "gofumpt: files need formatting (run make fmt):" >&2 + echo "$unformatted" >&2 + exit 1 + fi + ;; + *) usage ;; + esac +} + +main "$@"