Files
civitai__civitai/scripts/prettier-changed.mjs
T
briant c7c8ef0a01 chore: guard the two tooling traps that cost days on the moderator branch
Both were discovered during the Retool migration and are repo-wide, not migration-specific. That work
is a long way from merging, so they are lifted out on their own.

1. SvelteKit typecheck vs check. check runs svelte-kit sync, regenerating ~690 files into the
   directory the Vite dev server watches; run in an edit-verify loop with the dev server up, the two
   fight and the editor freezes. It cost a full day before it was diagnosed. All three SvelteKit apps
   defined typecheck IDENTICALLY to check, so there was no way to avoid sync at all. typecheck is now
   svelte-check alone in auth, creator-studio and moderator. Safe because all three run svelte-kit
   sync from prepare, so a fresh checkout already has its $types - verified by running the split
   typecheck in apps/auth here: no 'Cannot find module ./$types', same 2 pre-existing errors as
   before the change.

2. Repo-wide prettier. prettier:write globbed the whole workspace, and the repo is not
   prettier-2.8.8-clean - one run produced 1,085 modified files, burying the actual change and
   reformatting a colleague's uncommitted work in place. It now formats only what git reports dirty.
   CI already scopes itself this way and gates only on added files for the same reason.

The PreToolUse Bash hook enforces both, since a rule in a doc only binds whoever read the doc: it
blocks a repo-wide format write and the prettier-plugin-svelte invocation that empties .svelte files
to zero bytes, and asks before svelte-kit sync / pnpm check. It scrubs heredoc bodies and -m messages
first, so describing a blocked command in a commit message is not itself blocked.

CLAUDE.md documents all of it, including what prettier:write does NOT cover: the root formatter is
2.8.8 globbing ts/tsx, so no root command formats .svelte at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-09 16:20:09 -06:00

59 lines
2.2 KiB
JavaScript

// Prettier over UNCOMMITTED files only — never the whole repo.
//
// The repo is not Prettier-clean and will not be until the 2->3 upgrade reformats it deliberately
// (see .github/workflows/lint.yml): a repo-wide `prettier --write` rewrites ~1000 committed files,
// burying the actual change and reformatting other people's in-flight work. CI already scopes itself
// to the files a PR touched; this is the local equivalent.
//
// Scope is "what git reports as dirty" — modified-vs-HEAD plus untracked. That is exactly the set a
// commit is about to capture, and it is empty on a clean tree, so running this twice is a no-op.
import { execFileSync, spawnSync } from 'node:child_process';
import { existsSync, readFileSync } from 'node:fs';
import { createRequire } from 'node:module';
import { dirname, resolve } from 'node:path';
const mode = process.argv[2] === 'check' ? 'check' : 'write';
const git = (args) =>
execFileSync('git', args, { encoding: 'utf8' })
.split('\n')
.map((l) => l.trim())
.filter(Boolean);
let files;
try {
files = [
...git(['diff', '--name-only', '--diff-filter=ACMR', 'HEAD']),
...git(['ls-files', '--others', '--exclude-standard']),
];
} catch {
console.error('prettier-changed: not a git repository (or no HEAD yet).');
process.exit(1);
}
const targets = [...new Set(files)].filter((f) => /\.(ts|tsx)$/.test(f) && existsSync(f));
if (!targets.length) {
console.log('prettier-changed: no uncommitted .ts/.tsx files.');
process.exit(0);
}
// Resolved rather than hardcoded: Prettier 2's entry is bin-prettier.js and 3's is bin/prettier.cjs,
// and the 2->3 upgrade is planned.
const require = createRequire(import.meta.url);
const pkgPath = require.resolve('prettier/package.json');
const pkgBin = JSON.parse(readFileSync(pkgPath, 'utf8')).bin;
const bin = resolve(dirname(pkgPath), typeof pkgBin === 'string' ? pkgBin : pkgBin.prettier);
// Chunked: a large branch can exceed the command-line length limit, and Windows' is the shortest.
let failed = false;
for (let i = 0; i < targets.length; i += 100) {
const { status } = spawnSync('node', [bin, `--${mode}`, ...targets.slice(i, i + 100)], {
stdio: 'inherit',
});
if (status !== 0) failed = true;
}
process.exit(failed ? 1 : 0);