/** * Strict tsconfig test — locks in the [E00-S01-T10] strict base tsconfig * committed at the workspace root and shared by every workspace package. * * Acceptance criteria covered (each test fails without the committed config): * - "strict base tsconfig is committed" → root `tsconfig.base.json` exists * and carries `strict: true` plus the strict family the workspace relies * on (`noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, * `noImplicitOverride`, `useUnknownInCatchVariables`, * `verbatimModuleSyntax`) and NodeNext module resolution; every workspace * package (apps/server, packages/core, extensions/example) commits a * `tsconfig.json` that extends that root base config. * - "all packages compile under the strict base config" → running * `tsc -p /tsconfig.json --noEmit` with the workspace-resolved * TypeScript (exact 6.0.3, locked in by E00-S01-T09) exits 0 for every * workspace package. * - (non-vacuousness) a scratch file that violates the strict family fails * to compile under the committed base config with the expected * strict-family error codes, proving the flags are enforced rather than * merely declared. * * Run: `node --test tests/strict-tsconfig.test.mjs` * (node:test — built into Node >= 18; no dependencies, lockfile untouched.) */ import test from 'node:test'; import assert from 'node:assert/strict'; import { readFileSync } from 'node:fs'; import { mkdtempSync, rmSync, writeFileSync } from 'node:fs'; import { spawnSync } from 'node:child_process'; import { tmpdir } from 'node:os'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; const REPO_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); const read = (relPath) => readFileSync(path.join(REPO_ROOT, relPath), 'utf8'); /** The workspace packages that must compile under the strict base config. */ const WORKSPACE_PACKAGES = ['apps/server', 'packages/core', 'packages/database-postgres', 'extensions/example']; /** The strict-family flags the committed base config must set. */ const STRICT_FAMILY_FLAGS = [ 'strict', 'noUncheckedIndexedAccess', 'exactOptionalPropertyTypes', 'noImplicitOverride', 'useUnknownInCatchVariables', 'verbatimModuleSyntax', ]; /** The TypeScript compiler resolved by the frozen install (exact 6.0.3, E00-S01-T09). */ const TSC_BIN = path.join(REPO_ROOT, 'node_modules', 'typescript', 'bin', 'tsc'); /** * Runs the workspace-resolved TypeScript against a tsconfig (the same binary * the package `typecheck` scripts use; see E00-S01-T05/T09), executed by the * Node runtime running this test so it works from any cwd. */ function runTsc(cwd, args) { return spawnSync(process.execPath, [TSC_BIN, ...args], { cwd, encoding: 'utf8', timeout: 120_000, }); } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- test('tsconfig.base.json is committed with the strict family enabled', () => { const base = JSON.parse(read('tsconfig.base.json')); assert.ok( base && typeof base === 'object' && base.compilerOptions, 'tsconfig.base.json must be a committed JSON object declaring compilerOptions', ); for (const flag of STRICT_FAMILY_FLAGS) { assert.equal( base.compilerOptions[flag], true, `tsconfig.base.json must set "${flag}": true (strict family)`, ); } assert.equal( base.compilerOptions.module, 'NodeNext', 'tsconfig.base.json must set module to NodeNext (ESM boundary source of truth)', ); assert.equal( base.compilerOptions.moduleResolution, 'NodeNext', 'tsconfig.base.json must set moduleResolution to NodeNext', ); assert.equal( base.compilerOptions.target, 'ES2023', 'tsconfig.base.json must set target to ES2023', ); }); test('every workspace package tsconfig.json extends the committed base config', () => { for (const pkg of WORKSPACE_PACKAGES) { const tsconfig = JSON.parse(read(path.join(pkg, 'tsconfig.json'))); assert.equal( tsconfig.extends, '../../tsconfig.base.json', `"${pkg}/tsconfig.json" must extend the committed root tsconfig.base.json`, ); } }); test('every workspace package compiles under the strict base config (tsc --noEmit)', () => { for (const pkg of WORKSPACE_PACKAGES) { const result = runTsc(path.join(REPO_ROOT, pkg), ['-p', 'tsconfig.json', '--noEmit']); assert.equal( result.status, 0, `"${pkg}" must compile under the strict base config:\n${(result.stderr || result.stdout || '').trim()}`, ); } }); test('the committed base config enforces the strict family (non-vacuous probe)', () => { const scratch = mkdtempSync(path.join(tmpdir(), 'strict-base-probe-')); try { writeFileSync( path.join(scratch, 'tsconfig.json'), JSON.stringify( { extends: path.join(REPO_ROOT, 'tsconfig.base.json'), compilerOptions: { noEmit: true }, include: ['.'], }, null, 2, ), ); writeFileSync( path.join(scratch, 'probe.ts'), [ '// Each line below violates one strict-family flag of tsconfig.base.json.', 'function implicitAny(param) { return param; } // TS7006 - noImplicitAny (strict)', 'const items: string[] = [];', 'const first = items[0].toUpperCase(); // TS2532 - noUncheckedIndexedAccess', 'interface Options { port?: number; }', 'const opts: Options = { port: undefined }; // TS2375 - exactOptionalPropertyTypes', 'class Base { greet(): string { return "hi"; } }', 'class Derived extends Base { greet(): string { return "yo"; } } // TS4114 - noImplicitOverride', 'try { JSON.parse(""); } catch (err) { const m = err.message; } // TS18046 - useUnknownInCatchVariables', '', ].join('\n'), ); const result = runTsc(scratch, ['-p', 'tsconfig.json', '--noEmit']); assert.notEqual( result.status, 0, 'a file violating the strict family must fail to compile under the committed base config', ); const output = (result.stderr || result.stdout || ''); for (const code of ['TS7006', 'TS2532', 'TS2375', 'TS4114', 'TS18046']) { assert.ok( output.includes(code), `probe output must contain ${code} (strict family not enforced):\n${output}`, ); } } finally { rmSync(scratch, { recursive: true, force: true }); } });