From a0dd187042fbc842d6c1dbc37573c71d90a80998 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Fri, 11 Sep 2026 19:51:31 +0000 Subject: [PATCH] :memo: Document the TypeScript 5.0 type floor Consumers need TypeScript >= 5.0: the emitted declarations use const type parameters and keep relative .ts specifiers, both of which resolve only on TS >= 5.0. That also makes the emitted .ts specifiers a non-issue, so the backlog item is closed without a .d.ts post-step: TS <= 4.9 cannot parse the declarations anyway. The README/CONTRIBUTING wording now says the rewrite applies to the JavaScript output, not to the declarations. --- CONTRIBUTING.md | 2 +- README.md | 3 ++- backlog.tasks | 9 +++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9cb9e82..e36b231 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -6,7 +6,7 @@ This document is for maintainers and contributors working on the project itself. CI and review will bounce these even though `npm run check` and the linters don't catch them. They're the high-frequency things a contributor (or an agent) reaches for by default: -- **Source imports use `.ts` extensions, never `.js`.** `node --strip-types` resolves the `.ts` form at test time; `rewriteRelativeImportExtensions` emits `.js` in `dist/`. "Pre-fixing" an import to `.js` breaks the inner loop. (rationale: README § Tooling decisions) +- **Source imports use `.ts` extensions, never `.js`.** `node --strip-types` only resolves the `.ts` form at test time; "Pre-fixing" an import to `.js` breaks the inner loop. (rationale: README § Tooling decisions) - **A new `npm run` script must reuse an existing prefix** (`create:` / `check:` / `fix:` / `test:` / `watch:` / `maintain:` / `publish:` / `setup:`). If none fits, that's a signal the script doesn't belong in the pipeline — not a reason to invent a new prefix. If it genuinely does belong, add the prefix to both lists here in the same commit; an undocumented prefix becomes invisible and quietly accrues members. (see [Script prefix convention](#script-prefix-convention)) - **`oxlint-disable` directives live in source, not `.oxlintrc.json`.** The trade-off must sit next to the code it silences. This is a _human_ last-resort convention; agents must not add these — see [AGENTS.md § Never do](./AGENTS.md#never-do). (rationale: README § Tooling decisions) - **Don't put slow / network / whole-project scans in `check` or pre-commit.** Advisory scans are not correctness gates; they belong under `maintain:`. (see [Feedback tiers](#feedback-tiers) and [Script prefix convention](#script-prefix-convention)) diff --git a/README.md b/README.md index ff53e76..0326550 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ Each tool's configuration trade-off is recorded in [Tooling decisions](#tooling- The choice and configuration of each tool above is the result of deliberate trade-offs, not defaults. The non-obvious ones: - **`tsconfig.json` extends `@tsconfig/strictest` + `@tsconfig/node26`**; `tsconfig.build.json` extends it to add the emit-only options (`declaration`, `sourceMap`, `inlineSources`, `outDir`, `target: es2024`, `rewriteRelativeImportExtensions: true`) and to exclude test files. `inlineSources` embeds the original TypeScript in `dist/*.js.map`, so debuggers can map into `src/` without it being shipped; `declarationMap` is intentionally off because a `.d.ts.map` cannot embed source and would dangle. This separation lets the editor and CI type-check from one config while the build emits from the other. -- **Source imports use `.ts` extensions** so `node --strip-types` resolves them at test time. `rewriteRelativeImportExtensions: true` in `tsconfig.build.json` rewrites them to `.js` in the emitted `dist/*` output, so consumers see conventional ESM imports. +- **Source imports use `.ts` extensions** so `node --strip-types` resolves them at test time. `rewriteRelativeImportExtensions: true` in `tsconfig.build.json` rewrites them to `.js` in the emitted JavaScript; the emitted `.d.ts` keep the `.ts` specifier, which TypeScript >= 5.0 resolves (see [Requirements](#requirements)), so no post-processing step is needed. - **Type-aware oxlint is enabled declaratively** via `options.typeAware: true` in `.oxlintrc.json` (powered by `oxlint-tsgolint`). The script commands stay clean — no CLI flag — and type-aware mode is a property of the config, not the invocation. - **Source-level `oxlint-disable` directives** are used for known type-aware false positives (see `src/pattern.ts`, `src/match.ts`, `src/index.test.ts`). The disable lives next to the code it silences, not in `.oxlintrc.json`, so the trade-off is visible to anyone reading the source. - **`knip --include dependencies,exports,files`** intentionally omits the `types` category, which produces systematic false positives for libraries whose exported types are part of the public API. The targeted scope keeps the signal high without config-file boilerplate. @@ -52,6 +52,7 @@ The choice and configuration of each tool above is the result of deliberate trad ### Requirements - Node.js >= 26 (engines field; pinned via `.node-version`). +- TypeScript >= 5.0 to consume the published declarations. The emitted `.d.ts` use `const` type parameters (TS 5.0) and keep their relative `.ts` specifiers; both resolve on TS >= 5.0 in `node10`/`node16`/`nodenext`/`bundler`. ## VSCode integration diff --git a/backlog.tasks b/backlog.tasks index 3c58b0c..935aba9 100644 --- a/backlog.tasks +++ b/backlog.tasks @@ -16,7 +16,7 @@ Setup: - `verify = check + test:unit`; `check:tsc` uses the root config (`noEmit: true`), so `tsconfig.build.json` is only exercised by `build` - a broken build config (bad `rootDir`, emit-option typo) passes `verify` and only fails in CI ✔ Confirmed no change: `verify` is the local quick source-correctness check; the emit path is gated by CI's `build` job (`npm run build`), so adding `build` locally only adds inner-loop friction @done -☐ Fix `.ts` extensions in emitted `.d.ts` +✔ Fix `.ts` extensions in emitted `.d.ts` @done - emitted JS rewrites `./pattern.ts` → `./pattern.js`, but `dist/*.d.ts` still imports `"./pattern.ts"` / `"./match.ts"` - resolves under TS7, but a portability risk for older resolvers/bundlers; `attw` does not catch it - HANDOVER (setup-phase review, unfixed — emit work deferred out of that session): @@ -24,9 +24,10 @@ Setup: - `npm run build` + `publish:publint` + `publish:attw` + a packed-consumer `tsc -p` all pass today, so this is a portability/robustness fix, not a broken-build fix — don't expect a red gate to confirm it works - the durable proof is a consumer-resolution typecheck that asserts types still flow (a deliberately-wrong assignment must error), not just exit 0 — TS7 resolution silently tolerates the `.ts` form, so a green run alone proves nothing - two directions: (a) post-process the `.d.ts` imports to `.js` in the build, or (b) stop emitting relative `.ts` on the declaration surface (e.g. build-only source or an internal barrel). Either way reconcile the README/CONTRIBUTING sentence claiming the option yields "conventional ESM imports" for consumers - ☐ Fix the declaration emit (`.d.ts` post-step) or stop using relative `.ts` in the declaration surface - ☐ Reconcile the README/CONTRIBUTING claim that `rewriteRelativeImportExtensions` yields "conventional ESM imports" - ☐ Add a consumer-resolution test to the suite (durable guard; `attw` currently misses it) + - resolution: no code change. TypeScript >= 5.0 resolves the `.ts` specifier in shipped declarations in `node10`/`node16`/`nodenext`/`bundler` (verified across TS 4.7–7.0 with a consumer `@ts-expect-error` type guard). TS <= 4.9 rejects it, but also cannot parse the `` already emitted in `dist/pattern.d.ts` (const type parameters, a TS 5.0 feature), so no consumer that can use the package is affected; the effective floor is now documented in README § Requirements + ✘ Post-process the declaration emit to `.js`, or restructure to avoid relative `.ts` on the declaration surface @cancelled + ✔ Reconcile the README/CONTRIBUTING claim that `rewriteRelativeImportExtensions` yields "conventional ESM imports"; document the TS >= 5.0 floor @done + ✘ Add a consumer-resolution test (moot without a fix; `attw` still misses this class of issue) @cancelled ✔ Resolve sourcemap sources for consumers @done - `dist/*.map` `sources` are `../src/*.ts`, but `src` is not in `files`, so debuggers get missing sources - HANDOVER (setup-phase review, unfixed — emit work deferred out of that session):