From 9227e5ef8f11afc18125ae169105bc544f590833 Mon Sep 17 00:00:00 2001 From: Bryan Fawcett Date: Sat, 12 Sep 2026 03:03:07 +0800 Subject: [PATCH] fix(reusable-lint): pin prettier to the repo-root config A nested .prettierrc that names a plugin kills the whole prettier job. nyuchi-identity's apps/auth-ui is a SvelteKit app with its own auto-discovered .prettierrc declaring prettier-plugin-svelte and prettier-plugin-tailwindcss. This job installs prettier globally and never runs `npm ci`, so when prettier walks into that directory and resolves the nested config, plugin loading fails and the ENTIRE job dies - not just that subtree: [error] Cannot find package 'prettier-plugin-svelte' imported from /noop.js Every option available inside the repo was bad. Deleting the nested config breaks the app's own formatting - without the svelte plugin prettier cannot parse a .svelte file at all. Adding apps/auth-ui/ to .prettierignore narrows the gate, which is the thing we are trying to stop. Leaving both is a live trap: the app's own `prettier --write .` re-tabs package.json and turns the org check red the next time anyone formats. So pass --config .prettierrc, exactly symmetric with the --ignore-path argument already there and for the same reason: this job must use the ORG's settings, not whatever it auto-discovers walking the tree. A sub-app keeps its own .prettierrc for its own scripts and editors; it just does not get to redefine the org gate from a subdirectory. And it means one markdown/JSON dialect per repo, which is the point. This does NOT narrow the gate - files under the sub-app are still checked, now against the org config. Verified: apps/auth-ui/README.md badly formatted is still reported. A missing root .prettierrc is now a loud error naming the fix, rather than prettier silently falling back to defaults. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/reusable-lint.yml | 40 ++++++++++++++++++++++++----- ADOPTING-LINT.md | 11 +++++++- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/.github/workflows/reusable-lint.yml b/.github/workflows/reusable-lint.yml index 8cd94c2..16951fe 100644 --- a/.github/workflows/reusable-lint.yml +++ b/.github/workflows/reusable-lint.yml @@ -23,8 +23,10 @@ # Each consuming repo MUST ship .prettierrc, .prettierignore, # .yamllint.yaml and .markdownlint.jsonc at its root - copy them # verbatim from nyuchi/.github. Prettier is invoked with an explicit -# --ignore-path .prettierignore, so .prettierignore is the ONLY ignore -# file that applies; the others auto-discover from the repo root. +# --config .prettierrc --ignore-path .prettierignore, so those two files +# at the repo ROOT are the only ones that apply - a nested .prettierrc in +# a sub-app cannot redefine the org gate, and .gitignore cannot hide a +# file from it. The other tools auto-discover from the repo root. # # CROSS-ORG USE: this repo is public, and a public reusable workflow can # be called from any repository, including a private one in a different @@ -281,16 +283,40 @@ jobs: echo "No tracked file is shadowed by .gitignore." fi - name: prettier --check - # --ignore-path is passed EXPLICITLY so that .gitignore is not - # consulted. Without it Prettier 3 silently skips any tracked file - # that .gitignore happens to match, which turns this gate into a - # no-op for exactly the files someone tried to hide. + # BOTH --config and --ignore-path are passed EXPLICITLY, and for the + # same reason: this job must use the ORG's prettier settings, not + # whatever it happens to auto-discover. + # + # --ignore-path, because Prettier 3 also reads .gitignore, and a + # tracked file matching a .gitignore pattern was silently skipped - + # a no-op gate for exactly the files someone tried to hide. + # + # --config, because prettier resolves a NESTED .prettierrc per file. + # nyuchi-identity's apps/auth-ui is a SvelteKit app whose own + # .prettierrc names prettier-plugin-svelte. This job installs + # prettier globally and never runs `npm ci`, so that plugin is not + # installed, and walking into that directory killed the ENTIRE job - + # not just the subtree. Any monorepo with a per-app prettier config + # naming a plugin does the same. + # + # Pinning the root config also means one markdown/JSON dialect per + # repo, which is the whole point. An app keeps its own .prettierrc + # for its own scripts and editors; it just does not get to redefine + # the org gate from a subdirectory. env: PRETTIER_GLOB: ${{ inputs.prettier-glob }} shell: bash run: | set -euo pipefail - prettier --check --ignore-path .prettierignore "$PRETTIER_GLOB" + if [ ! -f .prettierrc ]; then + echo "::error::.prettierrc is missing from the repo root." + echo "Copy it from nyuchi/.github - see ADOPTING-LINT.md step 2." + exit 1 + fi + prettier --check \ + --config .prettierrc \ + --ignore-path .prettierignore \ + "$PRETTIER_GLOB" markdownlint: name: markdownlint diff --git a/ADOPTING-LINT.md b/ADOPTING-LINT.md index 10a6f2b..9626c94 100644 --- a/ADOPTING-LINT.md +++ b/ADOPTING-LINT.md @@ -111,7 +111,8 @@ pip install yamllint==1.38.0 # actionlint 1.7.12 actionlint .github/workflows/*.yml -prettier --check --ignore-path .prettierignore "**/*.{md,mdx,json,jsonc}" +prettier --check --config .prettierrc --ignore-path .prettierignore \ + "**/*.{md,mdx,json,jsonc}" markdownlint-cli2 "**/*.md" "!**/node_modules/**" yamllint -s . ``` @@ -137,6 +138,14 @@ Two traps this gate has already been bitten by, both now fixed here: action silently enabled `MD060`, which failed in CI and passed locally. The reusable now installs a pinned `markdownlint-cli2` instead, so the version is a number you can copy. +- **Prettier resolves a nested `.prettierrc` per file.** A sub-app with + its own config that names a plugin — `nyuchi-identity`'s + `apps/auth-ui` declares `prettier-plugin-svelte` — killed the entire + job, not just that subtree, because the gate installs prettier globally + and never runs `npm ci`. The reusable now passes `--config .prettierrc` + so only the repo-root config governs the gate. A sub-app keeps its own + `.prettierrc` for its own scripts and editors; it just cannot redefine + the org gate from a subdirectory. Files under it are still linted. - **Prettier 3 reads `.gitignore` as an ignore file.** `mukoko-home`'s `.gitignore` contained `claude.md`, which silently exempted the tracked `CLAUDE.md` from `prettier --check` - a green gate that never