56 lines
1.5 KiB
JavaScript
56 lines
1.5 KiB
JavaScript
|
|
#!/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);
|