# prettier over the markdown, in a container, so it is never installed # on the host. script/fmt-check-markdown builds the fmt-check stage; # script/fmt builds fmt-out and takes the formatted files back. # node:22-bookworm-slim, 2026-09-05 FROM node:22-bookworm-slim@sha256:83f487e0a63425e5b4d146fb5e5be574bcbe1b7b843d3ebafdd95eaf7767a7e5 AS nodedeps # prettier lives outside /src so that a `COPY . .` of the repo cannot # overwrite it, and so that node_modules never appears in the tree # prettier is about to walk. WORKDIR /tools # package.json pins the version and yarn.lock pins the bytes: # --frozen-lockfile installs exactly the lockfile's resolution and fails # if package.json disagrees with it, so the tool cannot float between # runs. yarn is the one in the image above. COPY package.json yarn.lock ./ RUN yarn install --frozen-lockfile --non-interactive --no-progress ENV PATH="/tools/node_modules/.bin:${PATH}" WORKDIR /src # Read-only markdown check. Must match $stage in # script/fmt-check-markdown. FROM nodedeps AS fmt-check COPY . . # --config, not discovery: a .prettierrc that failed to arrive would # otherwise leave prettier on its defaults, where proseWrap is "preserve" # and every wrap this check exists to enforce passes. Missing the file is # a hard error instead. --no-editorconfig for the same reason in reverse: # .editorconfig is not in the build context, so honouring it here and on # a developer's machine would be two different answers. RUN prettier --config .prettierrc --no-editorconfig --check "**/*.md" # Write path. Not a check: script/fmt builds this and takes the files. FROM nodedeps AS fmt COPY . . RUN prettier --config .prettierrc --no-editorconfig --write "**/*.md" # Only the markdown leaves, with its paths intact, so that the export # below cannot put anything else back over the caller's working tree. RUN mkdir -p /out && cd /src && \ find . -name '*.md' -type f -exec cp --parents '{}' /out/ ';' # Export target: `docker build --target fmt-out --output type=local` # writes /out's tree into a directory on the client, which is how # script/fmt gets formatted markdown back without a bind mount. # Must match $stage in script/fmt. FROM scratch AS fmt-out COPY --from=fmt /out/ /