The `engines` field is advisory by default: an install run under an unpinned Node only emits a notice and still succeeds, so it rewrites package-lock.json with that older npm's resolution rules. That is not hypothetical — it happened while dropping the redundant platform bindings, and the resulting lockfile was rejected by `npm ci`. With engine-strict the mismatch is a hard failure instead, so the pin in .node-version is actually load-bearing for lockfile integrity. Verified: node 24 install now aborts with EBADENGINE, node 26 stays green (`npm run verify`, `npm ci` in sync), and npm auto-excludes .npmrc from the published tarball.
tiny-pattern-ts
Pattern matching for TypeScript/ESM environments (F#-style, not regex).
Development
- Build:
npm run build - Test:
npm run test,npm run test:ci - Watch:
npm run watch(re-runs tests on file save; the earliest feedback tier) - Checks:
npm run check,npm run fix - Verify (definition of done):
npm run verify—npm run check+ the unit suite in one shot; the whole-project correctness gate (excludes advisorymaintain) - Maintenance (advisory):
npm run maintain—knip+check-outdated; run on a maintenance / update-deps branch, not part of the feature loop - Individual fixes:
npm run fix:oxfmt,npm run fix:oxlint
Tooling
- TypeScript 7 — type checker and build (
tsc). - node --test +
--experimental-strip-types— test runner (Node 22.6+, flag dropped on Node 24). - c8 — code coverage for
test:ci. - oxlint — Rust-based linter. Type-aware rules are enabled via
options.typeAware: truein.oxlintrc.json(powered byoxlint-tsgolint). - oxlint-tsgolint — type-aware linting via typescript-go (declarative via
.oxlintrc.json, no CLI flag). Catches unsafe type assertions, unnecessary type parameters, and other type-system issues that regular oxlint can't see. Source-leveloxlint-disabledirectives are used to silence known false positives (e.g.,expectTypeOf()in test files). - oxfmt — Rust-based formatter (Prettier-compatible). Formats JS/TS, JSON/JSONC, YAML, Markdown, MDX, and more; built-in
package.jsonkey sorting replacessort-package-json. - cspell — spell checking.
- knip — finds unused dependencies, exports, and files. Scoped via
--include dependencies,exports,filesto skip the noisytypescategory (which produces false positives for libraries whose exported types are part of the public API). - publint — validates
package.jsonfor ESM publishing correctness. Runs on publish only (in CI), not as part ofnpm run check. - @arethetypeswrong/cli (
attw) — validates.d.tsdeclarations against multiple module-resolution scenarios. Runs on publish only with--profile esm-only(the package is intentionally ESM-only). - lefthook — git pre-commit hooks.
Tooling decisions
The choice and configuration of each tool above is the result of deliberate trade-offs, not defaults. The non-obvious ones:
tsconfig.jsonextends@tsconfig/strictest+@tsconfig/node26;tsconfig.build.jsonextends it to add the emit-only options (declaration,sourceMap,outDir,target: es2024,rewriteRelativeImportExtensions: true) and to exclude test files. This separation lets the editor and CI type-check from one config while the build emits from the other.- Source imports use
.tsextensions sonode --strip-typesresolves them at test time.rewriteRelativeImportExtensions: trueintsconfig.build.jsonrewrites them to.jsin the emitteddist/*output, so consumers see conventional ESM imports. - Type-aware oxlint is enabled declaratively via
options.typeAware: truein.oxlintrc.json(powered byoxlint-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-disabledirectives are used for known type-aware false positives (seesrc/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,filesintentionally omits thetypescategory, 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.attw --profile esm-onlyis semantically correct: this package is intentionally ESM-only (no CommonJS shim), so CJS resolution scenarios are out of scope by design, not a bug.- The
publish:prefix has no local aggregator.publintandattwvalidate the publishable artifact (dist/), not the source, and require a fresh build. They run only in the CIpublishjob immediately beforenpm publish— there is intentionally nonpm run publish. check:tscruns first in thenpm run checkchain so a type error short-circuits the rest (faster feedback than letting oxlint/oxfmt run and then failing on tsc at the end).verifyis the one-shot definition of done.npm run verifycomposesnpm run checkwith the unit suite (test:unit) into a single whole-project correctness gate, so a human or an agent reaches for one command instead of re-deriving the sequence. It deliberately usestest:unit(nottest) becausecheckalready runscheck:tsc— so tsc runs exactly once. It excludesmaintain(advisory) by design. CI is not switched to it: the build job runscheck+test:cito also collect coverage.- The
check:/maintain:split is correctness gates vs. advisory scans.npm run checkis the fast, offline, whole-project correctness ladder (tsc → oxlint → oxfmt → cspell) and can run anywhere, including the agent loop.knip(~4s, whole project) andcheck-outdated(~6.5s, queries the npm registry) are advisory, not correctness — a stale dependency or an unused export must not fail a feature PR — so they moved tonpm run maintain, kept out of pre-commit, and run in CI as a non-blocking job (see.github/workflows/ci.yml). Notecheck-outdatedexits non-zero whenever any dep is outdated, which is exactly why it must not gate merges. - The pre-commit hook sets the
LEFTHOOK_FILESenv var to the staged-files list, and the affected scripts use${LEFTHOOK_FILES:-<default>}to default to the whole project when invoked manually. This keepspackage.json#scriptsas the single source of truth for the underlying commands —lefthook.ymlonly describes what to run on which files. tslibandtype-festare deliberately not used.tslibis a runtime helper for old ES3/ES5 targets (the project targets ES2024);type-festwas never imported. knip caught both.
Requirements
- Node.js >= 26 (engines field; pinned via
.node-version).
VSCode integration
- Recommended extensions: see
.vscode/extensions.json(oxc, cspell). - TypeScript 7 is used via the
typescriptteam.native-previewextension. - oxc extension provides oxlint squiggles and oxfmt format-on-save.
Workflows
- Version updates via
npm version. - Publishing via GitHub Actions on tagged commits (see
.github/workflows/ci.yml); thepublishjob runspublish:publintandpublish:attwbeforenpm publish.
Script prefix convention
Script names follow a prefix convention that signals when they run:
check:*— read-only verification. Aggregated bynpm run check. Used in pre-commit hooks and CI's build job.fix:*— mutating counterpart ofcheck:*. Aggregated bynpm run fix. Use afternpm run checkto auto-resolve issues.test:*— test scripts.npm run testruns the full suite;test:unit/test:ciare scope-specific variants.maintain:*— advisory repo-maintenance scans (dead code, dependency freshness). Aggregated bynpm run maintain. Whole-project and/or network-bound, so not correctness gates: run on a maintenance branch, and in CI as a non-blocking job that reports without failing.publish:*— runs only at publish time, in the CIpublishjob (immediately beforenpm publish). There is no localnpm run publishscript — publishing is CI-only by policy.
Bare, prefix-free top-level commands are the entry points: build, clean, check, fix, test, watch, maintain, and verify. verify (check + test:unit) is the one-shot "whole-project correctness" gate; maintain is the advisory counterpart that never gates a merge.
Contributing
For maintainer and contributor docs — the script prefix convention, the feedback-tier system, the rules the tools don't enforce, and the publishing workflow — see CONTRIBUTING.md. AI coding agents: your entry point is AGENTS.md, which points back to CONTRIBUTING.md.
- Commit signing (GPG).
- Set up commit message template:
npm run use:git-commit-message. - See
commit-message-template. - Type-only tests use
expect-type'sexpectTypeOf(...)insidenode --testcases.