project-specs.md was a parallel document that restated most of what was already in package.json, README.md, and the config files themselves. The only content that wasn't already captured elsewhere was the 'why' behind a handful of non-obvious tooling choices, which now lives in README.md as a new 'Tooling decisions' subsection. This eliminates the drift problem between the two docs (the source of drift in the previous commit) by having one source of truth for 'what' (the config files) and one source for 'why' (README).
72 lines
6.2 KiB
Markdown
72 lines
6.2 KiB
Markdown
# 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`
|
|
- **Checks:** `npm run check`, `npm run fix`
|
|
- **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: true` in `.oxlintrc.json` (powered by `oxlint-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-level `oxlint-disable` directives 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.json` key sorting replaces `sort-package-json`.
|
|
- **cspell** — spell checking.
|
|
- **knip** — finds unused dependencies, exports, and files. Scoped via `--include dependencies,exports,files` to skip the noisy `types` category (which produces false positives for libraries whose exported types are part of the public API).
|
|
- **publint** — validates `package.json` for ESM publishing correctness. Runs on publish only (in CI), not as part of `npm run check`.
|
|
- **@arethetypeswrong/cli** (`attw`) — validates `.d.ts` declarations 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.json` extends `@tsconfig/strictest` + `@tsconfig/node26`**; `tsconfig.build.json` extends 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 `.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.
|
|
- **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.
|
|
- **`attw --profile esm-only`** is 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.** `publint` and `attw` validate the _publishable artifact_ (`dist/`), not the source, and require a fresh build. They run only in the CI `publish` job immediately before `npm publish` — there is intentionally no `npm run publish`.
|
|
- **`check:tsc` runs first** in the `npm run check` chain so a type error short-circuits the rest (faster feedback than letting oxlint/oxfmt run and then failing on tsc at the end).
|
|
- **`check:knip` and `check:outdated` are NOT in pre-commit** — knip scans the whole project (~4s, would noticeably slow the hook), and `check-outdated` queries the npm registry (~6.5s, network-dependent, advisory not correctness). Both run in `npm run check` and CI; pre-commit stays fast and offline.
|
|
- **The pre-commit hook sets the `LEFTHOOK_FILES` env var** to the staged-files list, and the affected scripts use `${LEFTHOOK_FILES:-<default>}` to default to the whole project when invoked manually. This keeps `package.json#scripts` as the single source of truth for the underlying commands — `lefthook.yml` only describes _what to run on which files_.
|
|
- **`tslib` and `type-fest` are deliberately not used.** `tslib` is a runtime helper for old ES3/ES5 targets (the project targets ES2024); `type-fest` was 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-preview` extension.
|
|
- 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`); the `publish` job runs `publish:publint` and `publish:attw` before `npm publish`.
|
|
|
|
## Script prefix convention
|
|
|
|
Script names follow a prefix convention that signals _when_ they run:
|
|
|
|
- `check:*` — read-only verification. Aggregated by `npm run check`. Used in pre-commit hooks and CI's build job.
|
|
- `fix:*` — mutating counterpart of `check:*`. Aggregated by `npm run fix`. Use after `npm run check` to auto-resolve issues.
|
|
- `test:*` — test scripts. `npm run test` runs the full suite; `test:unit` / `test:ci` are scope-specific variants.
|
|
- `publish:*` — runs only at publish time, in the CI `publish` job (immediately before `npm publish`). There is no local `npm run publish` script — publishing is CI-only by policy.
|
|
|
|
## Contribution guidelines
|
|
|
|
- Commit signing (GPG).
|
|
- Set up commit message template: `npm run use:git-commit-message`.
|
|
- See `commit-message-template`.
|
|
- Type-only tests use `expect-type`'s `expectTypeOf(...)` inside `node --test` cases.
|