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
This commit is contained in:
clawbot
2026-09-21 07:31:53 +00:00
parent 337b319542
commit 2dd4b8c401
11 changed files with 121 additions and 23 deletions
+1
View File
@@ -3,6 +3,7 @@
.DS_Store
sfdupes
files.dat
node_modules
*.log
*.out
*.test
+2
View File
@@ -0,0 +1,2 @@
node_modules/
yarn.lock
+4
View File
@@ -0,0 +1,4 @@
{
"tabWidth": 4,
"proseWrap": "always"
}
+11 -6
View File
@@ -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 . .
+23 -14
View File
@@ -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
+3
View File
@@ -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
+5
View File
@@ -0,0 +1,5 @@
{
"devDependencies": {
"prettier": "3.8.1"
}
}
+31
View File
@@ -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,
+12 -1
View File
@@ -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 "$@"
+21 -2
View File
@@ -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 "$@"
+8
View File
@@ -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==