Validates the emitted .d.ts declarations against multiple TypeScript
module-resolution scenarios. Same rationale as publint: it validates
the publishable artifact, not the source, so it belongs in the
publish: prefix, not check:.
- Add publish:attw script using --profile esm-only (the package is
intentionally ESM-only; CJS resolution is out of scope by design)
- Add step to CI publish job, after publish:publint and before
npm publish
- Document the script and the esm-only rationale in project-specs.md
and README.md
- Add 'attw' and 'arethetypeswrong' to cspell word list
- Remove unused 'stricter' word that was added speculatively before
publint validates the publishable artifact (dist/ vs package.json),
not the source. It should run only at publish time, in the CI publish
job, not on every commit.
- Rename check:publint -> publish:publint
- Remove from 'check' chain
- Add 'publish:publint' as a step in the CI publish job, right
before 'npm publish'
- Introduce a 'publish:' script prefix for publish-time-only scripts
- Document the prefix convention (check:, fix:, test:, publish:)
in project-specs.md (as a top-level subsection under Scripts)
and in the README
- Add 'publint' and 'stricter' to cspell word list
A new script should pick the prefix that matches its lifecycle, not
invent a new one. The 'publish:' prefix has no 'npm run publish'
aggregator by design (publishing is CI-only).
node --test src/ on Node 26+ treats src/ as a module path, not as a
directory to scan for test files, producing a 'Cannot find module'
error. Switch the test scripts to an explicit glob that lists all
*.test.ts files under src/.
Affects test, test:ci, test:unit.
- Replace hand-rolled strict flags with @tsconfig/strictest
- Pick up Node 26 module/lib/target from @tsconfig/node26
- Add exactOptionalPropertyTypes (the main strictness gain)
- Pin target to es2024 to remain conservative for the published build
lefthook and package.json had parallel command definitions for the
same tools (oxlint, oxfmt, cspell). Consolidate by making lefthook
call the npm scripts, with staged files passed via the
LEFTHOOK_FILES env var. The scripts use ${LEFTHOOK_FILES:-<default>}
so they default to the full project when invoked manually and to
the staged-files list when invoked from lefthook.
Changes:
- package.json#check:oxlint: `oxlint ${LEFTHOOK_FILES:-src}`
(lints src/ manually; staged files from lefthook)
- package.json#check:oxfmt: `oxfmt --check ${LEFTHOOK_FILES:-src}`
- package.json#check:cspell: `cspell lint ${LEFTHOOK_FILES:-.}`
(walks CWD manually; staged files from lefthook)
- package.json#check:tsc, check📦 unchanged (no file args)
- lefthook.yml: file-filtered hooks (oxlint, oxfmt, cspell) now use
`sh -c 'LEFTHOOK_FILES="$0" npm run check:*' {staged_files}` to
inject the staged-files list into the env. sort-package-json and
typecheck call npm scripts directly (no file args).
- project-specs.md: document the unification pattern
Why sh -c + env var instead of the simpler 'npm run ... -- {staged_files}':
'oxlint src file.ts' lints the whole src/ tree *plus* file.ts
(oxlint doesn't dedupe paths). Setting LEFTHOOK_FILES as an env
var (which lefthook's 'env:' config does not template) requires
the sh -c wrapper, but it gives the right semantics: when the
var is set, only the explicit files are checked; when unset,
the default (src/ or .) is used.
Verified:
- 'npm run check:oxlint' (no env) lints all of src/
- 'LEFTHOOK_FILES=src/match.ts npm run check:oxlint' lints only that file
- 'npx lefthook run pre-commit' with a staged TS file: cspell output
shows '1/1 src/match.ts' (only staged file, not whole project)
- All 5 hooks pass on a real staged change
- 'lefthook validate' reports 'All good'
- 'npm run check' exits 0
project-specs.md:
- Replace Prettier/ESLint/Vite/Vitest references with oxfmt/oxlint/oxc
and TypeScript 7 / node --test
- Drop @tsconfig/strictest note; rules are inlined in tsconfig.json,
enumerated explicitly
- Document oxlint rule disables (no-undefined, sort-keys, id-length,
no-named-export) and test-file overrides (no-unused-expressions,
no-empty-file, no-nodejs-modules, no-magic-numbers)
- Document Node 26 / .node-version / engines.node / CI follow
- Document allowImportingTsExtensions + rewriteRelativeImportExtensions
for the node --strip-types quirk
- Replace Vite/vitest coverage example with c8 + node --test
- Sync scripts section: drop check:eslint, check:prettier and their
fix:* counterparts; add check:oxlint/check:oxfmt/fix:oxlint/fix:oxfmt
- Add current source layout (index.ts, match.ts, pattern.ts, index.test.ts)
- Update VSCode integration section with actual settings.json and
extensions.json contents
- Document the actual commit history with gitmoji prefixes
- Fix 'initilizing' typo
- Drop 'changesets is a devDep' claims (changesets is not installed)
package.json:
- Move @oxfmt/binding-* and @oxlint/binding-* from devDependencies to
optionalDependencies so the right binding is selected per platform
(CI on Ubuntu gnu gets the gnu binding automatically, not the
local musl one)
- Extend check:outdated to ignore the platform-specific bindings (they
show as 'not installed' on the current platform, which is correct
- Add cspell words: gitmoji, dbaeumer, msvc (the latter for the Windows
binding variant). Drop the unused 'nocheck' word
README.md:
- Drop the 'changesets is available as a devDep' line; changesets
isn't installed
- Add .node-version (single line: '26') so fnm/nvm/volta/mise and the
VS Code Node version manager extension auto-switch when entering the
project directory
- engines.node in package.json: bump from '>=22.6' to '>=26' (npm-side
install-time declaration)
- Drop --experimental-strip-types flag from node --test invocations:
Node 26 has --strip-types unflagged
- .github/workflows/ci.yml: switch from hard-coded node-version: 24 to
node-version-file: .node-version in both build and publish jobs so CI
follows the .node-version file (single source of truth)
- .npmignore: add .node-version (developer-tool file, not for publish)
- .vscode/settings.json: pin js/ts.tsdk.path to node_modules/typescript/lib
so the editor uses the project's TS 7, not a different global install
- .vscode/extensions.json: swap ms-vscode.vscode-typescript-next for
typescriptteam.native-preview (the official TS team extension that
ships nightly TS support, including TS 7)
- cspell: add 'esbenp' and 'typescriptteam' to dictionary
- package-lock.json: regenerated for engines.node >=26
tsconfig.json target/lib are unchanged: es2024 is the highest stable ES
target TS 7.0.2 ships, and Node 26 supports all ES2024 features natively.
- Install expect-type@1.4.0 (devDependency)
- New src/index.test.ts exercises the public API with both runtime
assertions (node:assert/strict inside node:test) and type-level
assertions (expectTypeOf().toEqualTypeOf, toMatchTypeOf, toHaveProperty)
- Tests are excluded from tsc -p tsconfig.build.json (no .d.ts pollution
in dist/)
- Type-level checks happen during tsc --noEmit (run by 'npm run check' and
the test script); expectTypeOf assertions have no runtime side effect,
so they pair naturally with node --test in the same test() blocks
- cspell: add EDITMSG to dictionary (used in commit-message-template path)
- oxlint test-file overrides: silence no-nodejs-modules (we use node:
builtins) and no-magic-numbers (literals in tests are fine)
Coverage:
- match() returns a builder with with/exhaustive/otherwise
- P.literal narrows to its literal type, P.type to its typeof target
- exhaustive() returns the union of handler return types
- otherwise() falls back when no case matches
- exhaustive() throws when no case matches
- Drop .eslintrc.cjs and .prettierrc, add .oxlintrc.json and .oxfmtrc.json
- oxlint covers correctness/suspicious/perf/style/restriction categories
plus import and typescript plugins; oxfmt reads the same .prettierrc-style
options we had before
- Update lefthook.yml pre-commit jobs to run oxlint/oxfmt instead of
eslint/prettier
- Update .vscode/extensions.json (oxc replaces eslint/prettier/vitest) and
settings.json (oxc becomes the default formatter for TS/JS)
- Add 'oxlint', 'oxfmt', 'oxc', 'nodenext' to cspell dictionary
- Rewrite README 'Development' section to describe the new toolchain
- Use tsc 7 for build (vite no longer needed for a Node library)
- Use node --test with --experimental-strip-types for tests (vitest dropped)
- Use c8 for coverage instead of vitest's built-in coverage
- Add tsconfig.build.json separating typecheck from emit config
- Drop DOM lib, add strict noUncheckedIndexedAccess/noImplicitOverride
- Pin engines.node to >=22.6 (required for --experimental-strip-types)
- Update CI to Node 24 LTS (unflagged strip-types)
- Add @types/node for node:test / node:assert types