mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 21:46:49 +00:00
724c88c201
A mechanical prettier --write over server/src, server/tests, server/scripts and shared/src with the workspaces' .prettierrc; nothing but layout and import order changes, and every file keeps the same set of imports. shared is formatted throughout and its format baseline is empty. The server baseline keeps the 35 files that Prettier would grow past their lint:size limit (they need a split first, which is not a formatting change), and the generated entity files stay as their generator writes them.
116 lines
4.7 KiB
JavaScript
116 lines
4.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
* lint:strict: the strict type errors per file may only go down.
|
|
*
|
|
* The server compiles with strict: false, so a value that can be null or
|
|
* undefined reads as always present, and an untyped parameter is a silent
|
|
* `any` that no lint rule counts. Switching strict on at once is not a change
|
|
* anyone can review, so this runs tsc over tsconfig.strict.json (the build
|
|
* config plus strictNullChecks and noImplicitAny) and counts its errors per
|
|
* file. A file may hold at most its entry in scripts/strict-baseline.json and
|
|
* a file without an entry, a new one in particular, none. An entry above what
|
|
* its file holds now, or for a file that is gone, fails as well until
|
|
* --update lowers it, so a fixed error cannot come back unseen. An error tsc
|
|
* reports outside any file (a broken config) fails the run, and so does a
|
|
* tsc that exits non-zero without one error it can name: the check never
|
|
* passes on output it could not read.
|
|
*
|
|
* npm run lint:strict check against the baseline (CI)
|
|
* npm run lint:strict -- --update lower the baseline to today's counts;
|
|
* it never raises or adds an entry
|
|
*
|
|
* --dir=<path> points it at another server root and --tsc-output=<file> reads
|
|
* a saved `tsc --pretty false` output instead of running tsc (the unit tests
|
|
* use both). The counting lives in scripts/lib/tsc-errors.mjs, the baseline
|
|
* handling in scripts/lib/ratchet.mjs at the repository root.
|
|
*/
|
|
import {
|
|
countMap,
|
|
lowerCounts,
|
|
RatchetError,
|
|
readBaseline,
|
|
reportStale,
|
|
staleCounts,
|
|
writeBaseline,
|
|
} from '../../scripts/lib/ratchet.mjs';
|
|
import { countErrors } from './lib/tsc-errors.mjs';
|
|
|
|
import { execFileSync } from 'node:child_process';
|
|
import { readFileSync } from 'node:fs';
|
|
import { createRequire } from 'node:module';
|
|
import { join, resolve } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
/** tsc over tsconfig.strict.json, as its output and exit status. */
|
|
function runTsc(serverDir) {
|
|
const tsc = createRequire(join(serverDir, 'package.json')).resolve('typescript/bin/tsc');
|
|
try {
|
|
const output = execFileSync(
|
|
process.execPath,
|
|
[tsc, '-p', 'tsconfig.strict.json', '--noEmit', '--pretty', 'false'],
|
|
{
|
|
cwd: serverDir,
|
|
encoding: 'utf8',
|
|
maxBuffer: 256 * 1024 * 1024,
|
|
},
|
|
);
|
|
return { output, status: 0 };
|
|
} catch (err) {
|
|
if (typeof err.status !== 'number') throw err;
|
|
return { output: `${err.stdout ?? ''}${err.stderr ?? ''}`, status: err.status };
|
|
}
|
|
}
|
|
|
|
const sum = (map) => Object.values(map).reduce((a, b) => a + b, 0);
|
|
|
|
function check(argv) {
|
|
const dirArg = argv.find((a) => a.startsWith('--dir='));
|
|
const outputArg = argv.find((a) => a.startsWith('--tsc-output='));
|
|
const serverDir = dirArg ? resolve(dirArg.slice('--dir='.length)) : fileURLToPath(new URL('..', import.meta.url));
|
|
const baselinePath = join(serverDir, 'scripts', 'strict-baseline.json');
|
|
|
|
let baseline = readBaseline(baselinePath, countMap);
|
|
const { output, status } = outputArg
|
|
? { output: readFileSync(resolve(outputArg.slice('--tsc-output='.length)), 'utf8'), status: null }
|
|
: runTsc(serverDir);
|
|
const { counts, global } = countErrors(output);
|
|
if (global.length) {
|
|
throw new RatchetError(`tsc reported errors outside any file:\n ${global.join('\n ')}`);
|
|
}
|
|
if (status !== null && status !== 0 && !Object.keys(counts).length) {
|
|
throw new RatchetError(`tsc exited with ${status} without an error it could name:\n${output.slice(0, 2000)}`);
|
|
}
|
|
|
|
if (argv.includes('--update')) {
|
|
baseline = lowerCounts(baseline, counts);
|
|
writeBaseline(baselinePath, baseline);
|
|
}
|
|
|
|
const grown = Object.entries(counts).filter(([file, n]) => n > (baseline[file] ?? 0));
|
|
for (const [file, n] of grown) {
|
|
const entry = baseline[file];
|
|
console.error(
|
|
`FAIL ${file}: ${n} strict error(s), ` +
|
|
(entry ? `its baseline is ${entry}. ` : 'a file without a baseline entry may have none. ') +
|
|
'Run npx tsc -p tsconfig.strict.json --noEmit to see them; narrow the null case or type the parameter.',
|
|
);
|
|
}
|
|
const stale = staleCounts(baseline, counts);
|
|
reportStale(stale, { file: 'strict-baseline.json', command: 'lint:strict', root: serverDir, error: console.error });
|
|
console.log(
|
|
`strict: ${sum(counts)} error(s) in ${Object.keys(counts).length} file(s), baseline ${sum(baseline)} in ${Object.keys(baseline).length}`,
|
|
);
|
|
return grown.length || stale.length ? 1 : 0;
|
|
}
|
|
|
|
const isCli = Boolean(process.argv[1]) && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
if (isCli) {
|
|
try {
|
|
process.exitCode = check(process.argv.slice(2));
|
|
} catch (err) {
|
|
if (!(err instanceof RatchetError)) throw err;
|
|
console.error(`FAIL lint:strict: ${err.message}`);
|
|
process.exitCode = 1;
|
|
}
|
|
}
|