{"record":{"id":"3e440e513bc36f81","repo":"chenglou/pretext","slug":"expected-typescript-consumer-misuse-to-fail-but-i","errorCode":null,"errorMessage":"Expected TypeScript consumer misuse to fail, but it compiled successfully.","messagePattern":"Expected TypeScript consumer misuse to fail, but it compiled successfully\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/package-smoke-test.ts","lineNumber":136,"sourceCode":"      \"const prepared = prepare('hello', '16px Inter')\",\n      \"const width = '100'\",\n      'layout(prepared, width, 20)',\n      '',\n    ].join('\\n'),\n  )\n\n  const badCompile = run(\n    [path.join(root, 'node_modules', '.bin', tscBinaryName()), '-p', 'tsconfig.json'],\n    {\n      cwd: projectDir,\n      stdout: 'pipe',\n      stderr: 'pipe',\n      allowFailure: true,\n    },\n  )\n\n  if (badCompile.exitCode === 0) {\n    throw new Error('Expected TypeScript consumer misuse to fail, but it compiled successfully.')\n  }\n\n  const combinedOutput = `${badCompile.stdout}${badCompile.stderr}`\n  if (\n    !combinedOutput.includes(\"Argument of type 'string' is not assignable to parameter of type 'number'.\") &&\n    !combinedOutput.includes(\"Type 'string' is not assignable to type 'number'.\")\n  ) {\n    throw new Error(`Unexpected TypeScript consumer error output:\\n${combinedOutput}`)\n  }\n\n  console.log('ts ok')\n}\n\nasync function createProject(dir: string, pkg: Record<string, unknown>): Promise<void> {\n  await mkdir(dir, { recursive: true })\n  await writeFile(path.join(dir, 'package.json'), JSON.stringify(pkg, null, 2) + '\\n')\n}\n","sourceCodeStart":118,"sourceCodeEnd":154,"githubUrl":"https://github.com/chenglou/pretext/blob/ac49b09b7d83ede19581fa94a8b892b07d309baf/scripts/package-smoke-test.ts#L118-L154","documentation":"smokeTypeScript() runs a deliberately invalid consumer program (`layout(prepared, '100', 20)` — passing a string for a number parameter) and asserts tsc rejects it. If tsc exits 0, this throws: it means the package's emitted type declarations are too permissive and no longer catch the misuse. This is a negative type-contract test for the public API.","triggerScenarios":"The width parameter's type in the published dist/*.d.ts became `string | number`, `any`, or `unknown`, so passing '100' type-checks cleanly. Can also happen if the consumer's tsconfig relaxed strictness — but the smoke test pins strict:true, so the root cause is almost always the package's declarations.","commonSituations":"A refactor widened a parameter type (e.g. unioning in string for convenience), or a declaration-emission change dropped the precise number type.","solutions":["Inspect dist/layout.d.ts for the layout() signature and confirm the width parameter is typed exactly `number`.","If the source type was widened, narrow it back in src/layout.ts and rebuild dist/.","Re-run the smoke test to confirm the negative case is rejected again."],"exampleFix":"// before — src/layout.ts widened the type\nexport function layout(prepared: Prepared, width: string | number, lineHeight: number)\n\n// after\nexport function layout(prepared: Prepared, width: number, lineHeight: number)","handlingStrategy":"validation","validationCode":"// Before shipping, assert the negative type test fails as expected\nimport { spawnSync } from 'node:child_process'\nconst r = spawnSync('npx', ['tsc', '-p', 'tsconfig.negative.json'], { encoding: 'utf8' })\nif (r.status === 0) throw new Error('Negative type test compiled — layout() width is not typed as number')","typeGuard":"import type { Prepared } from '@chenglou/pretext'\n// Compile-time guard: width MUST be number. If this assignment errors, the contract holds.\ntype WidthIsNumber = Parameters<typeof import('@chenglou/pretext').layout>[1] extends number ? true : never","tryCatchPattern":null,"preventionTips":["Keep layout()'s width parameter typed exactly `number` in src/layout.ts.","Run the package smoke test (which includes the negative TS case) before every release."],"tags":["typescript","type-declarations","package","smoke-test"],"backgroundTag":null,"analyzedSha":"ac49b09b7d83ede19581fa94a8b892b07d309baf","analyzedAt":"2026-08-12T17:03:16.263Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}