#!/usr/bin/env node /** * Pre-commit type check for staged TS files. * * Writes a temp tsconfig that extends the project root and pins * `files` to whatever lint-staged passed in. `tsc -p` then compiles * the whole dep graph from those entrypoints — catches errors in * the staged code AND in anything it imports — while still skipping * the 22s full-project pass. * * Replaces `tsc-files` (npm), which silently fails under pnpm because * its tsc-resolution path (typescript/../.bin/tsc) doesn't exist in * pnpm's virtual store layout. */ import { spawnSync } from 'node:child_process'; import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { tmpdir } from 'node:os'; import { join, relative, resolve } from 'node:path'; const cwd = process.cwd(); const args = process.argv.slice(2); const files = args.filter((a) => /\.(ts|tsx)$/.test(a)); if (files.length === 0) { process.exit(0); } const tmpDir = mkdtempSync(join(tmpdir(), 'tsc-staged-')); const tmpConfig = join(tmpDir, 'tsconfig.json'); const relFiles = files.map((f) => relative(tmpDir, resolve(cwd, f))); writeFileSync( tmpConfig, JSON.stringify( { extends: relative(tmpDir, join(cwd, 'tsconfig.json')), compilerOptions: { noEmit: true, skipLibCheck: true }, files: relFiles, include: [], }, null, 2, ), ); const tsc = spawnSync('pnpm', ['exec', 'tsc', '-p', tmpConfig, '--pretty'], { cwd, stdio: 'inherit', }); rmSync(tmpDir, { recursive: true, force: true }); process.exit(tsc.status ?? 1);