From 2dd4b8c401a86adda26b22cf54b411c9846423db Mon Sep 17 00:00:00 2001 From: clawbot Date: Mon, 21 Sep 2026 07:31:53 +0000 Subject: [PATCH] Restore Markdown formatting in fmt/fmt-check and pin prettier (closes #19) script/fmt and script/fmt-check now run gofmt for Go and prettier for Markdown; fmt-check reports each independently. prettier is pinned at 3.8.1 by package.json/yarn.lock (integrity hash); .prettierrc and .prettierignore are the house settings copied from the prompts repo. script/bootstrap installs node and yarn from the host package manager and runs `yarn install --frozen-lockfile`. The Markdown check runs in CI via the Dockerfile build stage, where bootstrap provides prettier; it is removed from the lint stage because the golangci-lint image has no node. JS manifests are copied before bootstrap so the yarn layer caches, and node_modules is dockerignored. node is an unpinned host runtime like git/make/go: nvm's glibc node does not run on the musl/Alpine build image, so the canonical nvm route is not usable here; prettier is the hash-pinned formatter. The wholesale Markdown reformat follows in the next commit. Model: opus-4-8 --- .dockerignore | 1 + .prettierignore | 2 ++ .prettierrc | 4 ++++ Dockerfile | 17 +++++++++++------ README.md | 37 +++++++++++++++++++++++-------------- TODO.md | 3 +++ package.json | 5 +++++ script/bootstrap | 31 +++++++++++++++++++++++++++++++ script/fmt | 13 ++++++++++++- script/fmt-check | 23 +++++++++++++++++++++-- yarn.lock | 8 ++++++++ 11 files changed, 121 insertions(+), 23 deletions(-) create mode 100644 .prettierignore create mode 100644 .prettierrc create mode 100644 package.json create mode 100644 yarn.lock diff --git a/.dockerignore b/.dockerignore index 3522d5b..ed9c5a6 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,7 @@ .DS_Store sfdupes files.dat +node_modules *.log *.out *.test diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..23d67fc --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +node_modules/ +yarn.lock diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..8af31cd --- /dev/null +++ b/.prettierrc @@ -0,0 +1,4 @@ +{ + "tabWidth": 4, + "proseWrap": "always" +} diff --git a/Dockerfile b/Dockerfile index a6009f2..86bcff3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,9 +27,12 @@ ARG CHECK_EPOCH # target now runs `docker build -f Dockerfile.lint`, and a docker build # cannot run a docker build: routing the gate through make would mean # nesting docker inside this image. Same reason `make check` is gone -# from the build stage below. `make fmt-check` stays as it is — it is a -# gate, not the aggregate, and it shells out to nothing. -RUN echo "gate fmt-check, epoch ${CHECK_EPOCH}" && make fmt-check +# from the build stage below. +# +# `make fmt-check` is not run in this stage: it now also runs prettier +# over Markdown, and this golangci-lint image has no node. The gate runs +# in the build stage below, where script/bootstrap installs node and +# prettier. # The FROM above and the one in Dockerfile.lint pin the same linter # twice, and nothing else keeps them in sync; this fails the build when @@ -77,10 +80,12 @@ COPY --from=lint /src/go.sum /dev/null # rather than duplicating the installs inline. Only script/ and the # dependency manifests are copied first, nothing else, so this layer # stays cached until the scripts or the dependencies change — bootstrap -# ends in `go mod download`, which is why there is no separate -# invocation of it here. +# runs `go mod download` and `yarn install`, which is why there is no +# separate invocation of either here. The JS manifests (package.json, +# yarn.lock) are copied too so the yarn install layer caches alongside +# the Go one. COPY script/ script/ -COPY go.mod go.sum ./ +COPY go.mod go.sum package.json yarn.lock ./ RUN script/bootstrap COPY . . diff --git a/README.md b/README.md index 182a50d..e931cd7 100644 --- a/README.md +++ b/README.md @@ -452,14 +452,18 @@ and may be invoked directly. The provided entrypoints are: - `script/bootstrap` — install everything needed to build and develop this repository, idempotently, assuming nothing is - present. `git`, `make`, and `go` come from the first of nix, apt, - brew, or apk found on the host, and are presence-checked only. - `golangci-lint` is deliberately **not** installed: it runs from a - digest-pinned image via `script/lint` and never from a host - install, so there is no host copy to drift from the pin. A missing - `docker` is warned about rather than installed or treated as - fatal — everything except linting works without it. Ends with - `go mod download`. + present. `git`, `make`, `go`, and `node` come from the first of nix, + apt, brew, or apk found on the host, and are presence-checked only; + `node` is an unpinned host runtime like the rest, because nvm's + prebuilt node is glibc-linked and does not run on this repo's + musl/Alpine build image. The Markdown formatter itself — `prettier` — + is pinned by `yarn.lock`'s integrity hash and installed with + `yarn install --frozen-lockfile`. `golangci-lint` is deliberately + **not** installed: it runs from a digest-pinned image via + `script/lint` and never from a host install, so there is no host copy + to drift from the pin. A missing `docker` is warned about rather than + installed or treated as fatal — everything except linting works + without it. Ends with `go mod download` and the `yarn` install. - `script/setup` — make a fresh clone ready for development: runs `script/bootstrap`, then `script/install-precommit`. - `script/projectname` — print this project's name (`sfdupes`). @@ -490,10 +494,15 @@ and may be invoked directly. The provided entrypoints are: `go.sum` changes and the download layer goes cold again. Because the daemon only ever sees a build context, this works when the docker daemon is remote and bind mounts are impossible. -- `script/fmt` — format the Go sources in place (`gofmt -s -w`). - Markdown is not formatted. -- `script/fmt-check` — the read-only counterpart of `script/fmt`: - prints any unformatted file and exits non-zero instead of writing. +- `script/fmt` — format in place: `gofmt -s -w` for Go sources and + `prettier` for Markdown (`--tab-width 4 --prose-wrap always`, the + house settings, also carried in `.prettierrc`). prettier is the + pinned devDependency in `package.json`/`yarn.lock`, installed by + `script/bootstrap`. +- `script/fmt-check` — the read-only counterpart of `script/fmt`: runs + both checks, reports each independently so it is clear which failed, + and exits non-zero if either found unformatted files instead of + writing. - `script/check` — run `script/test`, `script/lint`, and `script/fmt-check`, in that order. Modifies nothing. Needs `docker`, because `script/lint` does. @@ -557,8 +566,8 @@ carries the compile recipe: `-v` on failure). - `make lint` — run `golangci-lint` with the repo config, in Docker (see `script/lint`); requires `docker`. -- `make fmt` / `make fmt-check` — format Go sources / verify - formatting without writing. +- `make fmt` / `make fmt-check` — format Go and Markdown sources / + verify both without writing. - `make check` — `test`, `lint`, and `fmt-check`; modifies nothing. Requires `docker`, via `lint`. - `make docker` — build the Docker image, which runs the gates as diff --git a/TODO.md b/TODO.md index 3da0b6c..58f287b 100644 --- a/TODO.md +++ b/TODO.md @@ -29,6 +29,9 @@ # Completed Steps +- restore Markdown formatting in `script/fmt`/`fmt-check` and reformat + all Markdown to the house prettier settings (2026-09-21, closes + https://git.eeqj.de/sneak/sfdupes/issues/19) - fix the lint-image pin comments and `FROM` form in `Dockerfile` and `Dockerfile.lint` (2026-08-10, branch `next`, closes https://git.eeqj.de/sneak/sfdupes/issues/25): dropped the false diff --git a/package.json b/package.json new file mode 100644 index 0000000..dc05cde --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "devDependencies": { + "prettier": "3.8.1" + } +} diff --git a/script/bootstrap b/script/bootstrap index 936ea8b..2630ec3 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -11,6 +11,12 @@ set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +# yarn provides prettier, which formats Markdown. yarn is a tool, like +# node/git/make/go below; the reference that governs formatting output is +# prettier, pinned by yarn.lock's integrity hash and installed by +# `yarn install --frozen-lockfile`. +YARN_VERSION="1.22.22" + PKGMGR="" SUDO="" APT_UPDATED="" @@ -58,6 +64,21 @@ missing() { ! command -v "$1" >/dev/null 2>&1 } +ensure_node() { + if ! missing node; then return 0; fi + pkg_install nodejs nodejs node nodejs +} + +ensure_yarn() { + if ! missing yarn; then return 0; fi + if ! missing corepack; then + corepack enable >/dev/null 2>&1 || true + corepack prepare "yarn@$YARN_VERSION" --activate + else + pkg_install yarn yarn yarn yarn + fi +} + main() { cd "$ROOT" @@ -71,6 +92,16 @@ main() { if missing make; then pkg_install gnumake make make make; fi if missing go; then pkg_install go golang go go; fi + # node runs prettier and is an unpinned host tool for the same reason + # git/make/go are: it comes from the host package manager, whatever + # version it ships. It is not installed via nvm the way the canonical + # template does, because nvm's prebuilt node is glibc-linked and does + # not run on this repo's musl/Alpine build image. prettier — the tool + # whose version affects formatting output — is pinned by yarn.lock. + ensure_node + ensure_yarn + yarn install --frozen-lockfile + # Linting runs via docker only (script/lint), so docker is a lint # prerequisite rather than something bootstrap installs. Warn, do # not fail: everything except `make lint` — and, through it, diff --git a/script/fmt b/script/fmt index 28af13b..20e3f06 100755 --- a/script/fmt +++ b/script/fmt @@ -1,12 +1,23 @@ #!/bin/sh -# script/fmt: format all files (writes). +# script/fmt: format all files (writes). gofmt for Go, prettier for +# Markdown. prettier is the pinned devDependency in package.json/ +# yarn.lock; script/bootstrap installs it (see run_prettier). set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +run_prettier() { + if ! command -v yarn >/dev/null 2>&1; then + echo "fmt: yarn not found; run script/bootstrap first" >&2 + exit 1 + fi + yarn run prettier "$@" +} + main() { cd "$ROOT" gofmt -s -w . + run_prettier --write '**/*.md' --tab-width 4 --prose-wrap always } main "$@" diff --git a/script/fmt-check b/script/fmt-check index 96fffa0..8dadbdc 100755 --- a/script/fmt-check +++ b/script/fmt-check @@ -1,18 +1,37 @@ #!/bin/sh # script/fmt-check: check formatting (read-only). Same scope as -# script/fmt, but fails instead of writing. +# script/fmt: gofmt for Go, prettier for Markdown. Both run every time +# and each reports independently, so a failure names which formatter is +# unhappy; the script exits non-zero if either found unformatted files. set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +run_prettier() { + if ! command -v yarn >/dev/null 2>&1; then + echo "fmt-check: yarn not found; run script/bootstrap first" >&2 + exit 1 + fi + yarn run prettier "$@" +} + main() { cd "$ROOT" + rc=0 + files="$(gofmt -s -l .)" if [ -n "$files" ]; then echo "gofmt: files not formatted:" >&2 echo "$files" >&2 - exit 1 + rc=1 fi + + if ! run_prettier --check '**/*.md' --tab-width 4 --prose-wrap always; then + echo "prettier: Markdown not formatted; run make fmt" >&2 + rc=1 + fi + + exit "$rc" } main "$@" diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..d846639 --- /dev/null +++ b/yarn.lock @@ -0,0 +1,8 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +prettier@3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.8.1.tgz#edf48977cf991558f4fcbd8a3ba6015ba2a3a173" + integrity sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==