From 1a749b13d64aa723d5e6eb8e26c53dc0fc5ac0e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= Date: Sat, 5 Sep 2026 00:13:10 +0200 Subject: [PATCH] :memo: Reconcile project-specs.md and cspell.json with the feature/setup branch --- cspell.json | 2 - project-specs.md | 119 ++++++++++++++++++++++++++++++----------------- 2 files changed, 77 insertions(+), 44 deletions(-) diff --git a/cspell.json b/cspell.json index 63d2117..a3c24ce 100644 --- a/cspell.json +++ b/cspell.json @@ -2,8 +2,6 @@ "version": "0.2", "language": "en", "words": [ - "tslib", - "typefest", "lefthook", "oxlint", "oxfmt", diff --git a/project-specs.md b/project-specs.md index 43069b2..cc338d9 100644 --- a/project-specs.md +++ b/project-specs.md @@ -8,13 +8,28 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte ## 1. Development Environment -- **TypeScript**: Use strictest practical rules. Inline in `tsconfig.json` (not - via `@tsconfig/strictest`). The rule set is: `strict`, `noImplicitAny`, - `noImplicitThis`, `alwaysStrict`, `strictNullChecks`, `strictFunctionTypes`, - `strictBindCallApply`, `strictPropertyInitialization`, `noImplicitReturns`, - `noFallthroughCasesInSwitch`, `noUncheckedIndexedAccess`, `noImplicitOverride`, - `noUnusedLocals`, `noUnusedParameters`, `forceConsistentCasingInFileNames`, - `isolatedModules`, `verbatimModuleSyntax`. +- **TypeScript**: Use strictest practical rules via `@tsconfig/strictest` + (extended from `tsconfig.json`). The project also extends + `@tsconfig/node26` for Node 26's `target`/`module`/`lib` defaults; + `tsconfig.json` is the type-check base (`noEmit: true`, includes + `src/`), and `tsconfig.build.json` extends it to add the emit-only + options (`declaration`, `sourceMap`, `outDir`, `target: es2024`, + `rewriteRelativeImportExtensions: true`) and to exclude test files + from emission. The `verbatimModuleSyntax: true` and + `allowImportingTsExtensions: true` overrides live in + `tsconfig.json` (the latter only works because the base has + `noEmit: true`). The strictest flags the project relies on (inherited + from `@tsconfig/strictest`) are: `strict`, `noImplicitAny`, + `noImplicitThis`, `alwaysStrict`, `strictNullChecks`, + `strictFunctionTypes`, `strictBindCallApply`, + `strictPropertyInitialization`, `noImplicitReturns`, + `noFallthroughCasesInSwitch`, `noUncheckedIndexedAccess`, + `noImplicitOverride`, `noUnusedLocals`, `noUnusedParameters`, + `forceConsistentCasingInFileNames`, `isolatedModules`, + `verbatimModuleSyntax`, plus the additional strictest flags + (`exactOptionalPropertyTypes`, `noPropertyAccessFromIndexSignature`, + `noUncheckedIndexedAccess` etc.) that `@tsconfig/strictest` enables + beyond a plain `strict: true`. - **EditorConfig**: Use `.editorconfig` from typescript-lib-starter-tiny. - **oxfmt**: Rust-based formatter, Prettier-compatible. Replaces Prettier. Config in `.oxfmtrc.json` (same shape as `.prettierrc`). @@ -34,6 +49,8 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte - `eslint/sort-keys` — handler/case order is semantic, not alphabetical. - `eslint/id-length` — `T`, `R`, `U`, `V` are standard TS generics. - `import/no-named-export` — false positive on library entry re-exports. + - `typescript/method-signature-style` — method signatures in + `interface` declarations are conventional TS ergonomics. - Stylistic rules superseded by oxfmt (oxfmt is the canonical formatter; these rules either conflict with its output or duplicate features oxfmt already provides): @@ -48,8 +65,6 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte `import { type X, Y }` is intentional for grouping. - `unicorn/prefer-export-from` — conflicts with how the barrel `src/index.ts` re-exports through `src/match.ts`. - - `typescript/method-signature-style` — method signatures - in `interface` are conventional TS ergonomics. For `*.test.ts` files additionally: `no-unused-expressions` (for `expectTypeOf(...)` calls), `no-empty-file` (we have a single import per test file in some cases), `import/no-nodejs-modules` (we use @@ -58,9 +73,9 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte - **Import sorting**: built into oxfmt (no separate plugin). - **cspell**: Basic spelling configuration. Words dictionary in `cspell.json` covers tooling names (`oxlint`, `oxfmt`, `oxc`, `nodenext`, - `oxfmtrc`, `oxlintrc`, `EDITMSG`, `typescriptteam`, - `gitmoji`, `dbaeumer`, `msvc`) and a few library names (`tslib`, - `typefest`, `lefthook`). + `oxfmtrc`, `oxlintrc`, `tsgolint`, `EDITMSG`, `typescriptteam`, + `gitmoji`, `dbaeumer`, `msvc`, `publint`, `attw`, `arethetypeswrong`, + `knip`) and a few library names (`lefthook`). - **Lefthook**: Pre-commit checks (run in parallel, lefthook v2 schema). Single source of truth for the underlying commands is in `package.json#scripts`; the lefthook config only describes _what to @@ -82,16 +97,27 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte CI and via the explicit `lefthook run outdated` command. - **Additional Dev Dependencies**: - - lefthook - - check-outdated + - lefthook (git hook manager) + - check-outdated (outdated dependency warnings) + - knip (unused dependency / export / file detection) + - publint (publish-time `package.json` validation) + - @arethetypeswrong/cli (attw, publish-time `.d.ts` validation) + - @tsconfig/strictest, @tsconfig/node26 (extended from in + `tsconfig.json`) - c8 (coverage for `node --test`) - expect-type (type-level assertions in tests) - oxfmt, oxlint (with their native bindings as `optionalDependencies` so the correct binding is selected per platform automatically) + - oxlint-tsgolint (type-aware linter backend, also with native + bindings as `optionalDependencies`) + - cspell (spell checking) - typescript (TS 7) - @types/node - - tslib (available for future runtime helper imports) - - type-fest (utility types) + + `tslib` and `type-fest` were removed (commit `ec98223`): knip + flagged them as unused, and they were not appropriate for an + ES2024-targeting ESM-only library (`tslib` is a runtime helper for + old ES3/ES5 targets; `type-fest` was never imported). ## 2. Build & Test @@ -121,10 +147,6 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte - `target: es2024`, `lib: ["es2024"]` (Node 26 supports all ES2024 features natively). - **Additional Dev Dependencies** (for types and tests): - - `tslib` — available for runtime helper imports; not currently - used by source (kept for future use per the original spec). - - `type-fest` — utility types; not currently used by source - (kept for future use per the original spec). - `@types/node` — types for `node:test`, `node:assert`, `node:fs`. - **Node Version**: `>=26` (engines field). Pinned via `.node-version` for fnm/nvm/volta/mise auto-switching and CI @@ -207,7 +229,10 @@ script does not belong in the standard pipeline. - `test:unit`: Run unit tests with `node --test --strip-types "src/**/*.test.ts"` (no preceding typecheck). - `test:ci`: Run tests in CI mode with c8 coverage (text + lcov + html - reporters), uploading `coverage/` as an artifact. + reporters), uploading `coverage/` as an artifact. The command is + `c8 --reporter=text --reporter=lcov --reporter=html node --test +--strip-types "src/**/*.test.ts"` (the glob is required on Node 26 + for the same reason `test` uses one — see below). ### BUILD @@ -223,8 +248,11 @@ script does not belong in the standard pipeline. ### CHECK -- `check`: Run all checks in order — `check:oxlint`, `check:oxfmt`, - `check:tsc`, `check:cspell`, `check:knip`, `check:outdated`. +- `check`: Run all checks in order — `check:tsc`, `check:oxlint`, + `check:oxfmt`, `check:cspell`, `check:knip`, `check:outdated`. + `check:tsc` is first so a type error short-circuits the rest + (faster feedback than letting oxlint/oxfmt run and then failing on + tsc at the end). - `check:oxlint`: `oxlint ${LEFTHOOK_FILES:-src}` — lints `src/` by default; when invoked from the lefthook pre-commit hook with `LEFTHOOK_FILES` set to the staged-files list, lints only those @@ -256,7 +284,8 @@ script does not belong in the standard pipeline. - `check:tsc`: `tsc` (noEmit is set in `tsconfig.json`). - `check:cspell`: `cspell lint ${LEFTHOOK_FILES:-.}` — walks the project root by default; from lefthook, only the staged files. -- `check:outdated`: `check-outdated --ignore-pre-releases --ignore-packages @oxfmt/binding-*,@oxlint/binding-*`. The oxc native bindings are declared as `optionalDependencies` so the correct one is selected per platform automatically; the `*`-platform bindings show as "not installed" on the current platform and are explicitly ignored here. +- `check:outdated`: `check-outdated --ignore-pre-releases +--ignore-packages @oxfmt/binding-darwin-arm64,@oxfmt/binding-darwin-x64,@oxfmt/binding-linux-arm64-gnu,@oxfmt/binding-linux-arm64-musl,@oxfmt/binding-linux-x64-gnu,@oxfmt/binding-linux-x64-musl,@oxfmt/binding-win32-x64-msvc,@oxlint/binding-darwin-arm64,@oxlint/binding-darwin-x64,@oxlint/binding-linux-arm64-gnu,@oxlint/binding-linux-arm64-musl,@oxlint/binding-linux-x64-gnu,@oxlint/binding-linux-x64-musl,@oxlint/binding-win32-x64-msvc,@oxlint-tsgolint/darwin-arm64,@oxlint-tsgolint/darwin-x64,@oxlint-tsgolint/linux-arm64,@oxlint-tsgolint/linux-x64,@oxlint-tsgolint/win32-arm64,@oxlint-tsgolint/win32-x64`. The oxfmt, oxlint, and oxlint-tsgolint native bindings are declared as `optionalDependencies` so the correct one is selected per platform automatically; bindings for non-current platforms show as "not installed" and are explicitly ignored here. - `check:knip`: `knip --include dependencies,exports,files` — finds unused dependencies, value exports, and source files. The scoped `--include` list intentionally omits the `types` @@ -311,7 +340,7 @@ deliberate design choice; the rule of thumb is: - **pre-commit hook**: fast, file-scoped, offline, deterministic. Catches what _you just changed_ in under a second or two. - **pre-push hook**: runs the unit test suite (`npm test`, - including a full `tsc` over the project). ~500ms. Catches + including a full `tsc` over the project). ~3.5s. Catches runtime/logic bugs across the whole codebase before the push leaves your machine. The `tsc` step is technically redundant with pre-commit but validates against unstaged changes too. @@ -321,17 +350,22 @@ deliberate design choice; the rule of thumb is: `npm run test:ci` on every push and PR. The ground truth — if CI passes, the codebase is clean. -| Script | pre-commit | pre-push | `npm run check` | CI build | CI publish | -| ----------------- | :--------: | :------: | :-------------: | :------: | :--------: | -| `check:tsc` | ✅ | ✅ | ✅ | ✅ | — | -| `check:oxlint` | ✅ | — | ✅ | ✅ | — | -| `check:oxfmt` | ✅ | — | ✅ | ✅ | — | -| `check:cspell` | ✅ | — | ✅ | ✅ | — | -| `test` | — | ✅ | — | ✅ | — | -| `check:knip` | — | — | ✅ | ✅ | — | -| `check:outdated` | — | — | ✅ | ✅ | — | -| `publish:publint` | — | — | — | — | ✅ | -| `publish:attw` | — | — | — | — | ✅ | +| Script | pre-commit | pre-push | `npm run check` | `npm run fix` | CI build | CI publish | +| ----------------- | :--------: | :------: | :-------------: | :-----------: | :------: | :--------: | +| `check:tsc` | ✅ | ✅ | ✅ | — | ✅ | — | +| `check:oxlint` | ✅ | — | ✅ | — | ✅ | — | +| `check:oxfmt` | ✅ | — | ✅ | — | ✅ | — | +| `check:cspell` | ✅ | — | ✅ | — | ✅ | — | +| `test` | — | ✅ | — | — | ✅ | — | +| `check:knip` | — | — | ✅ | — | ✅ | — | +| `check:outdated` | — | — | ✅ | — | ✅ | — | +| `fix:oxlint` | — | — | — | ✅ | — | — | +| `fix:oxfmt` | — | — | — | ✅ | — | — | +| `publish:publint` | — | — | — | — | — | ✅ | +| `publish:attw` | — | — | — | — | — | ✅ | + +(Each `test` row also runs `check:tsc` internally, so pre-push gets a +full `tsc` for free.) #### Why these splits? @@ -342,12 +376,13 @@ deliberate design choice; the rule of thumb is: on what you typed. - **`test` (and the `tsc` it includes) is in pre-push** because it - runs the whole test suite across the whole project (~500ms - for the current 6 tests). The pre-commit `LEFTHOOK_FILES` - convention doesn't apply to the test runner, so pre-commit - isn't the right home. Pre-push is the natural place: it runs - after all commits are made but before the push leaves the - machine, catching regressions that span multiple commits. + runs the whole test suite across the whole project (~3.5s + for the current 6 tests, including a full `tsc`). The pre-commit + `LEFTHOOK_FILES` convention doesn't apply to the test runner, + so pre-commit isn't the right home. Pre-push is the natural + place: it runs after all commits are made but before the push + leaves the machine, catching regressions that span multiple + commits. - **`check:knip` is NOT in pre-commit or pre-push** because it scans the whole project (not staged files) and takes ~4s.