From d827a7e65c9fff57e65fe694dd5367a11dfcfd9d Mon Sep 17 00:00:00 2001 From: clawbot <35+clawbot@noreply.example.org> Date: Tue, 6 Oct 2026 14:03:51 +0000 Subject: [PATCH] Format Markdown with prettier in make fmt (closes #43) script/fmt and script/fmt-check now run prettier on every Markdown file after their Go steps, as the standard scripts from sneak/prompts at dd4027b do. script/bootstrap gains the standard node, nvm and yarn install next to its goimports install, and the last Dockerfile stage copies package.json and yarn.lock before running it. .prettierrc, .prettierignore and yarn.lock are the standard copies. package.json is the standard one without its licence field and marked private, because this repo's licence is the WTFPL. .dockerignore keeps the log of a failed yarn install out of the build context. The existing Markdown is reformatted in the next commit. Model: opus-5-5 --- .dockerignore | 4 ++- .prettierignore | 2 ++ .prettierrc | 4 +++ Dockerfile | 2 +- README.md | 12 +++++--- TODO.md | 4 +++ package.json | 6 ++++ script/bootstrap | 79 +++++++++++++++++++++++++++++++++++++++++++++++- script/fmt | 28 ++++++++++++++--- script/fmt-check | 23 +++++++++++++- yarn.lock | 8 +++++ 11 files changed, 159 insertions(+), 13 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 fd0ef7a..aae93d2 100644 --- a/.dockerignore +++ b/.dockerignore @@ -72,10 +72,12 @@ **/*.sublime-* # This repository's host-built artifacts: the example program's binary, -# test binaries and coverage output. +# test binaries, coverage output, and the log yarn writes when an install +# fails. /cmd/example/example /*.test /*.out +/yarn-error.log # aider's files, among them a config file that can hold an API key. **/.aider* 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 e38ad31..62eb613 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,6 +31,6 @@ COPY --from=lint /src/go.sum /dev/null COPY --from=test /src/go.sum /dev/null WORKDIR /src 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 a64aa34..09ece22 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,10 @@ alpine. We provide: - `script/bootstrap` — install all dependencies (go if missing, goimports with `go install` at a pinned commit unless the installed one already reports the - pinned version, then `go mod download`); golangci-lint is not installed, - since it runs only in Docker + pinned version, then `go mod download`; node through nvm at a pinned version + if node is missing, yarn at a pinned version if it is missing, then the + `prettier` that `package.json` and `yarn.lock` pin); golangci-lint is not + installed, since it runs only in Docker - `script/setup` — set up the repo for development after a fresh clone: runs `script/bootstrap`, then `script/install-precommit` - `script/projectname` — output the project name (our own extension); used by @@ -155,10 +157,10 @@ alpine. We provide: - `script/lint` — run golangci-lint in Docker by building only the `lint` stage of the `Dockerfile`, without the build cache, so every run lints; the image is tagged `simplelog-lint` -- `script/fmt` — format all files (writes) with the goimports that - `script/bootstrap` installs +- `script/fmt` — format all files (writes): Go with the goimports that + `script/bootstrap` installs, then every `*.md` file with `prettier` - `script/fmt-check` — check formatting (read-only); fails if `gofmt -l` - reports files + reports files, or if `prettier` would change any `*.md` file - `script/check` — run all checks: `test`, `lint`, `fmt-check` (our own extension) - `script/docker` — build the Docker image without the build cache, tagged diff --git a/TODO.md b/TODO.md index bbd37ae..72fb0f4 100644 --- a/TODO.md +++ b/TODO.md @@ -22,6 +22,10 @@ Started, Rationale, Design, TODO, License, Author # Completed Steps +* 2026-10-06: `make fmt` and `make fmt-check` also format Markdown, with + `prettier` at the standard settings, pinned through `package.json` and + `yarn.lock`; `script/bootstrap` installs node and yarn for it, and the + existing Markdown was formatted once * 2026-10-06: re-vendored the standard files from `sneak/prompts` at commit `dd4027b`: `REPO_POLICIES.md`, the CI workflow, `.gitignore`, and `.golangci.yml` together with the lint stage's new image, plus diff --git a/package.json b/package.json new file mode 100644 index 0000000..fb8b622 --- /dev/null +++ b/package.json @@ -0,0 +1,6 @@ +{ + "private": true, + "devDependencies": { + "prettier": "3.8.1" + } +} diff --git a/script/bootstrap b/script/bootstrap index 02277dc..3d10d5a 100755 --- a/script/bootstrap +++ b/script/bootstrap @@ -2,11 +2,21 @@ # script/bootstrap: install all dependencies needed to build and develop # this repo. Idempotent: every install is guarded by a check so already # installed tools are skipped. Base tooling comes from nix, apt, brew, -# or apk (detected in that order); assumes nothing is present. +# or apk (detected in that order); assumes nothing is present. Node is +# used directly if installed; otherwise it is installed at a pinned +# version via nvm (installing nvm itself first, from a hash-verified +# release archive, never curl | sh). set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +# Pinned versions, 2026-07-06 +NODE_VERSION="22.17.0" +NVM_VERSION="0.40.3" +# sha256 of https://github.com/nvm-sh/nvm/archive/refs/tags/v0.40.3.tar.gz +NVM_SHA256="5f4d6aaa04a177dc93c985e31dbc411ab6b8c6e1e21d8015dbc1372625fcd1d0" +YARN_VERSION="1.22.22" + # goimports from golang.org/x/tools v0.30.0, 2025-02-10: the last release # that Go 1.22, the Go of go.mod and the Dockerfile, can build. GOIMPORTS_COMMIT="09747cdf594a7924dcecb506312be3bd6e437962" @@ -81,6 +91,69 @@ ensure_goimports() { echo "goimports $GOIMPORTS_VERSION at $(command -v goimports)" } +# verify_sha256 +verify_sha256() { + if command -v sha256sum >/dev/null 2>&1; then + actual="$(sha256sum "$1" | cut -d' ' -f1)" + else + actual="$(shasum -a 256 "$1" | cut -d' ' -f1)" + fi + if [ "$actual" != "$2" ]; then + echo "bootstrap: sha256 mismatch for $1" >&2 + echo " expected: $2" >&2 + echo " actual: $actual" >&2 + exit 1 + fi +} + +# nvm is a bash script; run a command in a bash with nvm loaded +nvm_sh() { + bash -c ". \"\$HOME/.nvm/nvm.sh\" && $*" +} + +ensure_nvm() { + [ -s "$HOME/.nvm/nvm.sh" ] && return 0 + # nvm prerequisites; nvm itself requires bash + if missing bash; then pkg_install bash bash bash bash; fi + if missing curl; then pkg_install curl curl curl curl; fi + if missing git; then pkg_install git git git git; fi + tmp="$(mktemp -d)" + curl -fsSL -o "$tmp/nvm.tar.gz" \ + "https://github.com/nvm-sh/nvm/archive/refs/tags/v${NVM_VERSION}.tar.gz" + verify_sha256 "$tmp/nvm.tar.gz" "$NVM_SHA256" + mkdir -p "$HOME/.nvm" + tar -xzf "$tmp/nvm.tar.gz" -C "$HOME/.nvm" --strip-components=1 + rm -rf "$tmp" +} + +ensure_node() { + if ! missing node; then return 0; fi + ensure_nvm + nvm_sh "nvm install $NODE_VERSION" +} + +ensure_yarn() { + if ! missing yarn; then return 0; fi + if ! missing corepack; then + corepack enable + corepack prepare "yarn@$YARN_VERSION" --activate + elif [ -s "$HOME/.nvm/nvm.sh" ]; then + nvm_sh "nvm use $NODE_VERSION >/dev/null && corepack enable && \ + corepack prepare yarn@$YARN_VERSION --activate" + else + npm install -g "yarn@$YARN_VERSION" + fi +} + +install_js_deps() { + if missing yarn && [ -s "$HOME/.nvm/nvm.sh" ]; then + nvm_sh "nvm use $NODE_VERSION >/dev/null && cd \"$ROOT\" && \ + yarn install --frozen-lockfile" + else + yarn install --frozen-lockfile + fi +} + main() { cd "$ROOT" @@ -93,6 +166,10 @@ main() { go mod download + ensure_node + ensure_yarn + install_js_deps + echo "bootstrap complete" } diff --git a/script/fmt b/script/fmt index 0685452..16c6159 100755 --- a/script/fmt +++ b/script/fmt @@ -1,17 +1,37 @@ #!/bin/sh -# script/fmt: format all files (writes) with the goimports that -# script/bootstrap installs. It puts Go's bin directory first on PATH, as -# script/bootstrap does, so it runs that goimports even when the directory -# is not on the caller's PATH. +# script/fmt: format all files (writes): Go with the goimports that +# script/bootstrap installs, then Markdown with prettier. It puts Go's bin +# directory first on PATH, as script/bootstrap does, so it runs that +# goimports even when the directory is not on the caller's PATH. set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +# Must match the pin in script/bootstrap. +NODE_VERSION="22.17.0" + +# script/bootstrap installs node and yarn under nvm and leaves neither +# on the PATH of the shell that called it, so resolve the pinned +# toolchain here the way bootstrap's own install step does. nvm is a +# bash script, hence the subshell. +run_yarn() { + if command -v yarn >/dev/null 2>&1; then + exec yarn "$@" + fi + if [ ! -s "$HOME/.nvm/nvm.sh" ]; then + echo "fmt: no yarn; run script/bootstrap first" >&2 + exit 1 + fi + exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null && + shift && exec yarn "$@"' bash "$NODE_VERSION" "$@" +} + main() { cd "$ROOT" gobin="$(go env GOBIN)" PATH="${gobin:-$(go env GOPATH)/bin}:$PATH" goimports -l -w . + run_yarn run prettier --write '**/*.md' --tab-width 4 --prose-wrap always } main "$@" diff --git a/script/fmt-check b/script/fmt-check index 6a64782..4fbd6b4 100755 --- a/script/fmt-check +++ b/script/fmt-check @@ -1,10 +1,30 @@ #!/bin/sh # script/fmt-check: check formatting (read-only). Fails and lists the -# offending files if gofmt would reformat anything. +# offending files if gofmt would reformat any Go file; otherwise fails if +# prettier would reformat any Markdown file. set -eu ROOT="$(cd "$(dirname "$0")/.." && pwd -P)" +# Must match the pin in script/bootstrap. +NODE_VERSION="22.17.0" + +# script/bootstrap installs node and yarn under nvm and leaves neither +# on the PATH of the shell that called it, so resolve the pinned +# toolchain here the way bootstrap's own install step does. nvm is a +# bash script, hence the subshell. +run_yarn() { + if command -v yarn >/dev/null 2>&1; then + exec yarn "$@" + fi + if [ ! -s "$HOME/.nvm/nvm.sh" ]; then + echo "fmt-check: no yarn; run script/bootstrap first" >&2 + exit 1 + fi + exec bash -c '. "$HOME/.nvm/nvm.sh" && nvm use "$1" >/dev/null && + shift && exec yarn "$@"' bash "$NODE_VERSION" "$@" +} + main() { cd "$ROOT" unformatted="$(gofmt -l .)" @@ -13,6 +33,7 @@ main() { echo "$unformatted" exit 1 fi + run_yarn run prettier --check '**/*.md' --tab-width 4 --prose-wrap always } 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==