Make baseline regeneration discoverable and cover the CI guard

Drift failures are routine here: any PR that incidentally fixes an e2e or
mobile type error trips the stale-entry check. Finding the fix meant opening
the script to learn --write exists, so the failure now prints the exact
regenerate command, and both packages expose it as a script.

Resolve the env-supplied PR base against the repo root like the flag already
was. Mobile CI runs with working-directory: mobile, so a repo-relative value
would have resolved against the wrong directory.

Cover the growth guard on the check path and the env form CI actually uses;
both branches were previously exercised only through --write.
This commit is contained in:
Brennan Benson
2026-08-28 03:43:27 -07:00
parent 293aac9722
commit c4f33a044c
4 changed files with 87 additions and 4 deletions
@@ -136,6 +136,12 @@ function subtractDiagnostics(left, right) {
})
}
/** Failures here are routine, so the message carries its own fix. */
function regenerationHint() {
const argv = process.argv.slice(2).filter((value) => value !== '--write')
return `Regenerate with: node ${relative(repoRoot, import.meta.filename)} ${argv.join(' ')} --write`
}
function formatDiagnostic(diagnostic) {
const location = diagnostic.file
? `${diagnostic.file}:${diagnostic.line}:${diagnostic.column}`
@@ -168,9 +174,9 @@ async function main() {
const options = parseArgs(process.argv.slice(2))
const projectPath = resolve(repoRoot, options.project)
const baselinePath = resolve(repoRoot, options.baseline)
const baseBaselinePath = options.baseBaseline
? resolve(repoRoot, options.baseBaseline)
: process.env.TYPECHECK_BASELINE_BASE_PATH
// Resolve the env form like the flag: mobile runs with working-directory: mobile.
const baseBaselineOption = options.baseBaseline ?? process.env.TYPECHECK_BASELINE_BASE_PATH
const baseBaselinePath = baseBaselineOption ? resolve(repoRoot, baseBaselineOption) : undefined
const tscPath = resolve(repoRoot, options.tsc ?? 'node_modules/typescript/bin/tsc')
const diagnostics = collectDiagnostics(projectPath, tscPath)
const baseBaseline = baseBaselinePath ? await readBaseline(baseBaselinePath) : null
@@ -207,6 +213,7 @@ async function main() {
console.error(formatDiagnostic(diagnostic))
}
}
console.error(regenerationHint())
process.exitCode = 1
}
@@ -47,7 +47,7 @@ function runBaseline(paths, extraArgs = [], spawnOptions = {}) {
...extraArgs
],
{
cwd: repoRoot,
cwd: spawnOptions.cwd ?? repoRoot,
encoding: 'utf8',
env: { ...process.env, ...spawnOptions.env }
}
@@ -151,6 +151,80 @@ describe('typecheck diagnostic baseline', () => {
expect(result.stdout).toContain('1 known diagnostics, no drift')
})
it('rejects a committed baseline that grew from the PR base', () => {
const paths = createFixture("const first: number = 'bad'\n")
expect(runBaseline(paths, ['--write']).status).toBe(0)
// Base knows only the first error; the committed baseline was raised to cover a second.
const base = JSON.parse(readFileSync(paths.baseline, 'utf8'))
writeFileSync(paths.baseBaseline, JSON.stringify(base))
writeFileSync(paths.source, "const first: number = 'bad'\nconst second: boolean = 'also-bad'\n")
expect(runBaseline(paths, ['--write', '--base-baseline', paths.baseBaseline]).status).toBe(1)
writeFileSync(
paths.baseline,
JSON.stringify({
version: 1,
diagnostics: [
...base.diagnostics,
{
file: 'source.ts',
line: 2,
column: 7,
code: 2322,
message: "Type 'string' is not assignable to type 'boolean'."
}
]
})
)
const check = runBaseline(paths, ['--base-baseline', paths.baseBaseline])
expect(check.status).toBe(1)
expect(check.stderr).toContain('Diagnostic baseline grew (1)')
})
it('reads the PR base from the environment the way CI passes it', () => {
const paths = createFixture("const value: number = 'bad'\n")
expect(runBaseline(paths, ['--write']).status).toBe(0)
writeFileSync(paths.baseBaseline, JSON.stringify({ version: 1, diagnostics: [] }))
const result = runBaseline(paths, [], {
env: { TYPECHECK_BASELINE_BASE_PATH: paths.baseBaseline }
})
expect(result.status).toBe(1)
expect(result.stderr).toContain('Diagnostic baseline grew (1)')
})
it('resolves the env-supplied PR base against the repo, not the working directory', () => {
// Mobile CI runs this with working-directory: mobile, so a repo-relative base
// path must not resolve against the caller's cwd.
const result = runBaseline(
{
project: 'config/tsconfig.e2e.json',
baseline: 'config/typecheck-e2e-diagnostics.json'
},
[],
{
cwd: join(repoRoot, 'mobile'),
env: { TYPECHECK_BASELINE_BASE_PATH: 'config/typecheck-e2e-diagnostics.json' }
}
)
expect(result.stderr).not.toContain('ENOENT')
expect(result.status).toBe(0)
})
it('names the regeneration command when the baseline drifts', () => {
const paths = createFixture("const value: number = 'bad'\n")
expect(runBaseline(paths, ['--write']).status).toBe(0)
writeFileSync(paths.source, 'const value: number = 1\n')
const stale = runBaseline(paths)
expect(stale.status).toBe(1)
expect(stale.stderr).toContain('--write')
})
it('refuses to regenerate a baseline that grows from the PR base', () => {
const paths = createFixture("const first: number = 'bad'\n")
expect(runBaseline(paths, ['--write']).status).toBe(0)
+1
View File
@@ -10,6 +10,7 @@
"postinstall": "node scripts/build-terminal-webview-engine.mjs && node scripts/build-mermaid-webview-engine.mjs",
"test": "vitest run",
"typecheck": "node ../config/scripts/typecheck-diagnostic-baseline.mjs --project mobile/tsconfig.json --baseline mobile/typecheck-test-diagnostics.json --tsc mobile/node_modules/typescript/bin/tsc",
"typecheck:write": "node ../config/scripts/typecheck-diagnostic-baseline.mjs --project mobile/tsconfig.json --baseline mobile/typecheck-test-diagnostics.json --tsc mobile/node_modules/typescript/bin/tsc --write",
"lint": "oxlint",
"format": "oxfmt --write .",
"format:check": "oxfmt --check .",
+1
View File
@@ -49,6 +49,7 @@
"typecheck:cli": "tsc --noEmit -p config/tsconfig.tc.cli.json",
"typecheck:web": "tsc --noEmit -p config/tsconfig.tc.web.json",
"typecheck:e2e": "node config/scripts/typecheck-diagnostic-baseline.mjs --project config/tsconfig.e2e.json --baseline config/typecheck-e2e-diagnostics.json",
"typecheck:e2e:write": "node config/scripts/typecheck-diagnostic-baseline.mjs --project config/tsconfig.e2e.json --baseline config/typecheck-e2e-diagnostics.json --write",
"typecheck": "node config/scripts/run-typecheck-projects-in-parallel.mjs",
"typecheck:tsc:node": "tsc --noEmit -p config/tsconfig.node.json --composite false",
"typecheck:tsc:cli": "tsc --noEmit -p config/tsconfig.cli.json --composite false",