Skip to content

Fail production builds on TypeScript errors - #215

Open
arandomogg wants to merge 1 commit into
Chainmove:mainfrom
arandomogg:fix/issue-178-typescript-build-gate
Open

Fail production builds on TypeScript errors#215
arandomogg wants to merge 1 commit into
Chainmove:mainfrom
arandomogg:fix/issue-178-typescript-build-gate

Conversation

@arandomogg

Copy link
Copy Markdown

closes #178

Problem

next.config.mjs set typescript.ignoreBuildErrors: true, so next build
produced deployment artifacts even when the project did not type-check. Across
payments, KYC, contract, and admin boundaries that meant a broken API contract
or an unsafe refactor could ship, with the type error only showing up at runtime
against real records.

What changed

The flag is gone. next build now fails on any TypeScript error. A comment
in next.config.mjs points at the rationale so it does not get reintroduced by
reflex.

The errors it was hiding are fixed, not suppressed. Removing the flag
surfaced eleven errors across nine route files, all from a single source: the
handler defineRoute returns declared its Next.js context argument as
nextContext?: { params?: Promise<Record<string, string>> | Record<string, string> }.
That does not satisfy the RouteContext contract Next generates under
.next/types — the argument is required there, and params is always a
promise. The signature is now the exported NextRouteContext type, declared
exactly as the framework passes it:

export type NextRouteContext = {
  params: Promise<Record<string, string | string[] | undefined>>
}

parseParams still tolerates a missing context at runtime, since direct
callers such as tests may omit it; only the exported handler signature is
strict. Tests that invoke a handler directly now pass an explicit empty-params
context, mirroring how Next actually calls them.

No compiler option was relaxed, no @ts-nocheck was added, and no blanket
ignore replaces the removed flag.

The gate is deterministic. npm run typecheck now runs
tsc --noEmit --incremental false. A stale .tsbuildinfo — easy to hit across
CI caches and developer machines — can otherwise let a check pass by reusing an
earlier result.

The gate is verified, not assumed. npm run typecheck:gate
(scripts/check-typecheck-gate.ts) asserts two things:

  1. next.config.mjs enables neither typescript.ignoreBuildErrors nor
    eslint.ignoreDuringBuilds.
  2. No tracked .ts/.tsx file carries a file-level @ts-nocheck — the same
    failure mode as the removed flag, with a narrower blast radius. (678 files
    scanned today, none suppressed.)
  3. A deliberately introduced type error fails the typecheck. The script writes
    a probe file under lib/ (inside the tsconfig include, so the compiler
    actually sees it), runs tsc, requires a non-zero exit naming the probe, and
    deletes the probe again in a finally.

Check 3 is what keeps this honest over time: it fails if strictness or the
tsconfig include list is ever weakened to the point where such an error would
slip through.

CI runs the gate. A TypeScript build gate step runs in both the
pull-request and main-branch jobs, after typecheck and before build, so no
deployment artifact is accepted without it.

Note that npm run typecheck runs before .next/types exists and therefore
cannot see the generated route contracts — npm run build is the step that
checks those. Both run on every PR; neither replaces the other. This is written
down in docs/type-safety.md.

Documentation

  • docs/type-safety.md — why the flag was dangerous, the four gate stages and
    what each covers, how to verify the gate, and what is not an acceptable
    suppression. It documents the one standing exception, skipLibCheck, which
    applies only to third-party declaration files in node_modules and does not
    weaken checking of any first-party code.
  • CONTRIBUTING.md — the pre-PR checklist now says npm run typecheck instead
    of a raw npx tsc --noEmit, and states that the gate must not be disabled.

Verification

Check Result
npm run lint 0 errors (2 pre-existing react-hooks/exhaustive-deps warnings, untouched)
npm run typecheck Clean
npm run typecheck:gate Passes; also confirmed failing when ignoreBuildErrors is restored
npm run test:contracts 163 passed across 8 files
npm run openapi:check No drift, no breaking changes
npm run build Clean production build

Verified negatively as well: restoring typescript.ignoreBuildErrors: true
makes npm run typecheck:gate fail with a message naming the flag, and a
genuinely broken source file failed next build at the Running TypeScript
step during development of this change.

Affected areas

  • Backend / API routes (defineRoute handler signature)
  • Build and CI configuration
  • Documentation

next.config.mjs set typescript.ignoreBuildErrors, so `next build` emitted
deployment artifacts even when the project did not type-check. On a codebase
spanning payments, KYC, contract, and admin boundaries, that turned type drift
into runtime failures against real records instead of a failed build.

Removing the flag surfaced eleven pre-existing errors, all from one source:
the handler `defineRoute` returns declared its Next.js context argument as
optional and loosely typed, which does not satisfy the RouteContext contract
Next generates under .next/types. The argument is now the exported
NextRouteContext type, declared exactly as the framework passes it. Callers
that construct a handler directly pass an explicit empty-params context.

Gate changes:
- `npm run typecheck` runs with --incremental false so a stale .tsbuildinfo
  cannot let a check pass by reusing an earlier result.
- `npm run typecheck:gate` (scripts/check-typecheck-gate.ts) fails if the
  suppression flags return or a file-level nocheck directive appears, and
  proves the gate still bites by introducing a deliberate type error and
  requiring tsc to reject it.
- CI runs the gate on pull requests and on main, before the build step.

Compiler strictness is unchanged: no blanket ignore, no file-level nocheck, and
no relaxed tsconfig option replaces the removed flag. docs/type-safety.md
records the gate, its stages, and the one standing exception (skipLibCheck,
which covers only third-party declaration files in node_modules).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUILD SAFETY] Stop ignoring TypeScript errors in production builds

1 participant