8 Commits
Author SHA1 Message Date
tmu 0598a9eb4a 🔧 Expand oxfmt to format project config and docs files 2026-09-03 20:58:33 +00:00
tmu 7349d0dec2 🔥 Remove sort-package-json in favor of oxfmt's built-in sortPackageJson 2026-09-03 20:58:17 +00:00
tmu e45592ae00 🔧 Unify lefthook and package.json scripts via LEFTHOOK_FILES env var
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
2026-09-03 18:44:19 +00:00
tmu 45b6c141e8 🔧 Migrate lefthook config to v2 schema
The previous config was a hybrid of v1 (jobs: array) and v1
(top-level commands: block) that no longer validates under
lefthook 2.x. lefthook 2.0.0's schema has top-level 'commands:'
set to 'false'; the v1 jobs: array form still works, but the
orphan named-commands block at the top was silently invalid
(caught by 'lefthook validate').

Migrate to the v2-native 'commands:' (named) form under the hook:

  pre-commit:
    parallel: true
    commands:
      oxlint:
        glob: ...
        run: npx oxlint {staged_files}
      ...

Changes:
- Add 'min_version: 2.0.0' to declare the v2 schema explicitly
- Replace pre-commit.jobs: array with pre-commit.commands: (named)
- Inline the glob on each command (was a top-level property on each
  job in v1)
- Remove the orphan top-level 'commands:' block — it was never
  referenced by any hook, and v2 forbids it
- Update project-specs.md: drop the dangling reference to the
  lefthook 'commands:' block (it was the dead top-level one);
  describe the v2 commands: form

Verified:
- 'lefthook validate' reports 'All good'
- 'lefthook run pre-commit' executes all 5 hooks (oxlint, oxfmt,
  sort-package-json, typecheck, cspell); globs correctly skip hooks
  on non-matching files (e.g. lefthook.yml alone) and run them on
  TS/JS changes
- 'npm run check' exits 0
- 'lefthook dump' shows the normalized v2 config
2026-09-03 18:12:01 +00:00
tmu 9bc87aab87 ⬆️ Upgrade devDependencies to latest major versions
- @types/node 22.20.1 -> 26.4.1
- c8 10.1.3 -> 12.0.0
- check-outdated 2.16.1 -> 3.0.0
- cspell 8.19.4 -> 10.2.1
- lefthook 1.13.6 -> 2.1.12
- sort-package-json 3.7.1 -> 4.0.0
- type-fest 4.41.0 -> 5.9.0

Verified all configs remain compatible without changes:
- cspell 10 reads the existing JSON config (v0.2 format unchanged)
- lefthook 2 accepts the v1-style config (pre-commit.jobs[], commands:)
- c8 12 still wraps 'node --test --strip-types' and produces the same
  text/lcov/html reports
- check-outdated 3 keeps the --ignore-packages and --ignore-pre-releases
  flags we use

Full pipeline passes:
- npm run check exits 0 (oxlint, oxfmt, tsc, cspell, sort-package-json,
  check-outdated all green)
- npm test: 6/6 pass
- npm run test:ci: coverage report generated successfully
- lefthook run pre-commit: hooks execute correctly
2026-09-03 14:08:28 +00:00
tmu 0c3e51c768 ✅ Ignore coverage directory in .gitignore 2026-09-03 13:54:34 +00:00
tmu ab6eb0f34c 📚 Sync project-specs.md and package.json with current state
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
2026-09-03 13:42:37 +00:00
tmu cb5b302238 🔧 Remove Prettier from editor formatter config
Prettier is no longer a project dependency and oxc.oxc-vscode handles
formatting for all file types the project touches. Switch [json],
[jsonc], [markdown], [mdx], and [yaml] formatters from
esbenp.prettier-vscode to oxc.oxc-vscode (oxfmt under the hood,
Prettier-compatible for JS/TS and native for JSON/MD/YAML).

Drop 'esbenp' from cspell dictionary (no longer referenced).
2026-09-03 13:09:53 +00:00
13 changed files with 894 additions and 1145 deletions

No files matched your search

+1
View File
@@ -7,6 +7,7 @@ yarn-error.log*
pnpm-debug.log* pnpm-debug.log*
lerna-debug.log* lerna-debug.log*
coverage
node_modules node_modules
dist dist
dist-ssr dist-ssr
-1
View File
@@ -10,6 +10,5 @@ src/
.node-version .node-version
cspell.json cspell.json
lefthook.yml lefthook.yml
.sortpackagerc.json
LICENSE LICENSE
commit-message-template commit-message-template
+11 -1
View File
@@ -1,9 +1,19 @@
{ {
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"semi": true, "semi": true,
"singleQuote": false, "singleQuote": false,
"trailingComma": "all", "trailingComma": "all",
"printWidth": 80, "printWidth": 80,
"tabWidth": 4, "tabWidth": 4,
"endOfLine": "lf", "endOfLine": "lf",
"arrowParens": "always" "arrowParens": "always",
"sortPackageJson": true,
"sortImports": true,
"ignorePatterns": [
"dist",
"coverage",
"node_modules",
"package-lock.json",
"*.tsbuildinfo"
]
} }
+1 -6
View File
@@ -1,11 +1,6 @@
{ {
"$schema": "./node_modules/oxlint/configuration_schema.json", "$schema": "./node_modules/oxlint/configuration_schema.json",
"plugins": [ "plugins": ["typescript", "unicorn", "oxc", "import"],
"typescript",
"unicorn",
"oxc",
"import"
],
"categories": { "categories": {
"correctness": "error", "correctness": "error",
"suspicious": "error", "suspicious": "error",
-3
View File
@@ -1,3 +0,0 @@
{
"$schema": "https://json.schemastore.org/sort-package-json.json"
}
+14 -2
View File
@@ -9,11 +9,23 @@
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"[json]": { "[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[markdown]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"[mdx]": { "[mdx]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[yaml]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"editor.defaultFormatter": "oxc.oxc-vscode", "editor.defaultFormatter": "oxc.oxc-vscode",
+1 -3
View File
@@ -15,9 +15,8 @@ Pattern matching for TypeScript/ESM environments (F#-style, not regex).
- **node --test** + `--experimental-strip-types` — test runner (Node 22.6+, flag dropped on Node 24). - **node --test** + `--experimental-strip-types` — test runner (Node 22.6+, flag dropped on Node 24).
- **c8** — code coverage for `test:ci`. - **c8** — code coverage for `test:ci`.
- **oxlint** — Rust-based linter. - **oxlint** — Rust-based linter.
- **oxfmt** — Rust-based formatter (Prettier-compatible). - **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. - **cspell** — spell checking.
- **sort-package-json** — keep `package.json` keys alphabetized.
- **lefthook** — git pre-commit hooks. - **lefthook** — git pre-commit hooks.
### Requirements ### Requirements
@@ -34,7 +33,6 @@ Pattern matching for TypeScript/ESM environments (F#-style, not regex).
- Version updates via `npm version`. - Version updates via `npm version`.
- Publishing via GitHub Actions on tagged commits (see `.github/workflows/ci.yml`). - Publishing via GitHub Actions on tagged commits (see `.github/workflows/ci.yml`).
- Changesets is available as a devDependency for changelog automation if desired.
## Contribution guidelines ## Contribution guidelines
+12 -5
View File
@@ -9,13 +9,20 @@
"oxfmt", "oxfmt",
"oxc", "oxc",
"nodenext", "nodenext",
"nocheck",
"oxfmtrc", "oxfmtrc",
"oxlintrc", "oxlintrc",
"sortpackagerc",
"EDITMSG", "EDITMSG",
"esbenp", "typescriptteam",
"typescriptteam" "gitmoji",
"dbaeumer",
"msvc"
], ],
"ignorePaths": ["dist", "node_modules", "public", "coverage", "*.svg"] "ignorePaths": [
"dist",
"node_modules",
"public",
"coverage",
"*.svg",
".gitignore"
]
} }
+10 -24
View File
@@ -1,29 +1,15 @@
min_version: 2.0.0
pre-commit: pre-commit:
parallel: true parallel: true
jobs: commands:
- name: oxlint oxlint:
glob: "*.{ts,tsx,js,jsx,mjs,cjs}" glob: "*.{ts,tsx,js,jsx,mjs,cjs}"
run: npx oxlint {staged_files} run: sh -c 'LEFTHOOK_FILES="$0" npm run check:oxlint' {staged_files}
- name: oxfmt oxfmt:
glob: "*.{ts,tsx,js,jsx,mjs,cjs}" glob: "*.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,yaml,yml,md,mdx}"
run: npx oxfmt --check {staged_files} run: sh -c 'LEFTHOOK_FILES="$0" npm run check:oxfmt' {staged_files}
- name: cspell cspell:
run: npx cspell {staged_files} run: sh -c 'LEFTHOOK_FILES="$0" npm run check:cspell' {staged_files}
- name: sort-package-json
run: npx sort-package-json --check
- name: typecheck
run: npx tsc --noEmit
commands:
typecheck: typecheck:
run: npm run check:tsc run: npm run check:tsc
spell:
run: npm run check:cspell
sort:
run: npm run check:package
lint:
run: npm run check:oxlint
format:
run: npm run check:oxfmt
outdated:
run: npm run check:outdated
+503 -939
View File
File diff suppressed because it is too large. Load diff
+44 -33
View File
@@ -3,71 +3,82 @@
"version": "0.0.0", "version": "0.0.0",
"description": "Pattern matching for TypeScript/ESM environments (F#-style, not regex)", "description": "Pattern matching for TypeScript/ESM environments (F#-style, not regex)",
"keywords": [ "keywords": [
"pattern-matching",
"pattern",
"match",
"algebraic-data-types",
"adt", "adt",
"algebraic-data-types",
"match",
"pattern",
"pattern-matching",
"typescript" "typescript"
], ],
"license": "MIT",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://github.com/tmueller/tiny-pattern-ts.git" "url": "https://github.com/tmueller/tiny-pattern-ts.git"
}, },
"license": "MIT", "files": [
"sideEffects": false, "dist",
"README.md",
"LICENSE"
],
"type": "module", "type": "module",
"sideEffects": false,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js" "import": "./dist/index.js"
} }
}, },
"main": "./dist/index.js", "publishConfig": {
"types": "./dist/index.d.ts", "access": "public"
"files": [ },
"dist",
"README.md",
"LICENSE"
],
"scripts": { "scripts": {
"build": "tsc -p tsconfig.build.json", "build": "tsc -p tsconfig.build.json",
"check": "npm run check:oxlint && npm run check:oxfmt && npm run check:tsc && npm run check:cspell && npm run check:package && npm run check:outdated", "check": "npm run check:oxlint && npm run check:oxfmt && npm run check:tsc && npm run check:cspell && npm run check:outdated",
"check:cspell": "cspell .", "check:cspell": "cspell lint ${LEFTHOOK_FILES:-.}",
"check:outdated": "check-outdated", "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",
"check:oxfmt": "oxfmt --check src", "check:oxfmt": "oxfmt --check ${LEFTHOOK_FILES:-.}",
"check:oxlint": "oxlint src", "check:oxlint": "oxlint ${LEFTHOOK_FILES:-src}",
"check:package": "sort-package-json --check",
"check:tsc": "tsc --noEmit", "check:tsc": "tsc --noEmit",
"clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"", "clean": "node -e \"fs.rmSync('dist', { recursive: true, force: true })\"",
"fix:oxfmt": "oxfmt src", "fix:oxfmt": "oxfmt ${LEFTHOOK_FILES:-.}",
"fix:oxlint": "oxlint --fix src", "fix:oxlint": "oxlint --fix src",
"fix:package": "sort-package-json --write",
"test": "npm run check:tsc && node --test --strip-types src/", "test": "npm run check:tsc && node --test --strip-types src/",
"test:ci": "c8 --reporter=text --reporter=lcov --reporter=html node --test --strip-types src/", "test:ci": "c8 --reporter=text --reporter=lcov --reporter=html node --test --strip-types src/",
"test:unit": "node --test --strip-types src/", "test:unit": "node --test --strip-types src/",
"use:git-commit-message": "cp commit-message-template .git/COMMIT_EDITMSG || true" "use:git-commit-message": "cp commit-message-template .git/COMMIT_EDITMSG || true"
}, },
"devDependencies": { "devDependencies": {
"@oxfmt/binding-linux-x64-musl": "^0.66.0", "@types/node": "^26.4.1",
"@oxlint/binding-linux-x64-musl": "^1.81.0", "c8": "^12.0.0",
"@types/node": "^22.10.0", "check-outdated": "^3.0.0",
"c8": "^10.1.3", "cspell": "^10.2.1",
"check-outdated": "^2.13.0",
"cspell": "^8.19.3",
"expect-type": "1.4.0", "expect-type": "1.4.0",
"lefthook": "^1.11.12", "lefthook": "^2.1.12",
"oxfmt": "^0.66.0", "oxfmt": "^0.66.0",
"oxlint": "^1.81.0", "oxlint": "^1.81.0",
"sort-package-json": "^3.1.0",
"tslib": "^2.8.1", "tslib": "^2.8.1",
"type-fest": "^4.40.1", "type-fest": "^5.9.0",
"typescript": "^7.0.2" "typescript": "^7.0.2"
}, },
"optionalDependencies": {
"@oxfmt/binding-darwin-arm64": "^0.66.0",
"@oxfmt/binding-darwin-x64": "^0.66.0",
"@oxfmt/binding-linux-arm64-gnu": "^0.66.0",
"@oxfmt/binding-linux-arm64-musl": "^0.66.0",
"@oxfmt/binding-linux-x64-gnu": "^0.66.0",
"@oxfmt/binding-linux-x64-musl": "^0.66.0",
"@oxfmt/binding-win32-x64-msvc": "^0.66.0",
"@oxlint/binding-darwin-arm64": "^1.81.0",
"@oxlint/binding-darwin-x64": "^1.81.0",
"@oxlint/binding-linux-arm64-gnu": "^1.81.0",
"@oxlint/binding-linux-arm64-musl": "^1.81.0",
"@oxlint/binding-linux-x64-gnu": "^1.81.0",
"@oxlint/binding-linux-x64-musl": "^1.81.0",
"@oxlint/binding-win32-x64-msvc": "^1.81.0"
},
"engines": { "engines": {
"node": ">=26" "node": ">=26"
},
"publishConfig": {
"access": "public"
} }
} }
+294 -127
View File
@@ -8,107 +8,225 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
## 1. Development Environment ## 1. Development Environment
- **TypeScript**: Use strictest rules (via npm package "@tsconfig/strictest", additional strictures) - **TypeScript**: Use strictest practical rules. Inline in `tsconfig.json` (not
- **EditorConfig**: Use `.editorconfig` from typescript-lib-starter-tiny via `@tsconfig/strictest`). The rule set is: `strict`, `noImplicitAny`,
- **Prettier**: For code formatting, integrated with ESLint `noImplicitThis`, `alwaysStrict`, `strictNullChecks`, `strictFunctionTypes`,
- **ESLint**: Strictest type-checked rules, integrated with Prettier `strictBindCallApply`, `strictPropertyInitialization`, `noImplicitReturns`,
- **Import Sorting**: Via Prettier or ESLint `noFallthroughCasesInSwitch`, `noUncheckedIndexedAccess`, `noImplicitOverride`,
- **cspell**: Basic spelling configuration `noUnusedLocals`, `noUnusedParameters`, `forceConsistentCasingInFileNames`,
- **Lefthook**: Pre-commit checks for: `isolatedModules`, `verbatimModuleSyntax`.
- **EditorConfig**: Use `.editorconfig` from typescript-lib-starter-tiny.
- Type checking - **oxfmt**: Rust-based formatter, Prettier-compatible. Replaces Prettier.
- Spelling Config in `.oxfmtrc.json` (same shape as `.prettierrc`).
- Package sorting - **oxlint**: Rust-based linter. Replaces ESLint. Config in `.oxlintrc.json`
- Linting with `typescript`, `unicorn`, `oxc`, `import` plugins. Categories enabled
- Formatting as errors: `correctness`, `suspicious`, `restriction`. As warnings: `perf`,
- Outdated Packages `style`. `nursery` is off.
- **oxlint rules disabled by design** (in `.oxlintrc.json`):
- `eslint/no-undefined` — we use `undefined` as the no-match sentinel.
- `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.
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
`node:test`/`node:assert`/`node:fs` intentionally), `eslint/no-magic-numbers`
(literals in tests are fine).
- **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`).
- **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
run on which files_. Hooks that should run on staged files
(`oxlint`, `oxfmt`, `cspell`) set the `LEFTHOOK_FILES` env var to
the staged-files list via a `sh -c` wrapper, and the npm script
uses `${LEFTHOOK_FILES:-<default>}` to default to the full project
when invoked manually:
- `pre-commit.commands.oxlint` (glob `*.{ts,tsx,js,jsx,mjs,cjs}`):
`sh -c 'LEFTHOOK_FILES="$0" npm run check:oxlint' {staged_files}`
- `pre-commit.commands.oxfmt` (glob `*.{ts,tsx,js,jsx,mjs,cjs}`):
`sh -c 'LEFTHOOK_FILES="$0" npm run check:oxfmt' {staged_files}`
- `pre-commit.commands.cspell`:
`sh -c 'LEFTHOOK_FILES="$0" npm run check:cspell' {staged_files}`
- `pre-commit.commands.typecheck`:
`npm run check:tsc` (no file args needed)
`outdated` is intentionally NOT a pre-commit check (it can flag
upstream patch releases that aren't actionable locally); it runs in
CI and via the explicit `lefthook run outdated` command.
- **Additional Dev Dependencies**: - **Additional Dev Dependencies**:
- lefthook - lefthook
- sort-package-json
- check-outdated - check-outdated
- 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)
- typescript (TS 7)
- @types/node
- tslib (available for future runtime helper imports)
- type-fest (utility types)
## 2. Build & Test ## 2. Build & Test
- **Build Tool**: Vite (ESM only, no CJS) - **Build Tool**: TypeScript 7 (`tsc`) — no bundler, no Vite. The build
- **Testing**: Vitest is a plain `tsc -p tsconfig.build.json` invocation that emits ESM
- **TypeScript Build Output**: `dist` directory JavaScript and `.d.ts` declarations to `dist/`. ESM only, no CJS.
- **Additional Runtime Dependencies**: - **Testing**: Node's built-in `node --test` with `--strip-types` (Node
- tslib 22.6+, unflagged on Node 24/26). Test files are co-located with
- type-fest source as `*.test.ts`. No Vitest.
- **TypeScript Build Output**: `dist` directory (set in
`tsconfig.build.json#outDir`; `tsconfig.json` keeps `outDir` for
editor tooling but excludes tests from emission via
`tsconfig.build.json#exclude`).
- **Import extensions**: Source uses `.ts` extensions in imports
(e.g. `from "./match.ts"`) so `node --strip-types` resolves them
at test time. TypeScript's `rewriteRelativeImportExtensions` (in
`tsconfig.build.json`) rewrites these to `.js` in the emitted
`dist/*` output, so consumers see conventional ESM imports.
- **TypeScript Compiler Options Notes**:
- `allowImportingTsExtensions: true` is set in the root
`tsconfig.json` (works because the root config has `noEmit: true`).
- `rewriteRelativeImportExtensions: true` is set in
`tsconfig.build.json` to rewrite `.ts` to `.js` on emit.
- `module: nodenext`, `moduleResolution: nodenext` for ESM-first
Node packages.
- `verbatimModuleSyntax: true` enforces explicit `import type`.
- `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
(`actions/setup-node@v4` with `node-version-file: .node-version`).
## 3. Project Structure & Files ## 3. Project Structure & Files
- __.gitignore__: Ignore `dist`, `node_modules`, and other common files - **.gitignore**: Ignore `dist`, `node_modules`, `coverage`, and other
- **.npmignore**: Ignore `src` and other non-dist files common files.
- **LICENSE**: MIT - **.node-version**: Single line containing the Node major version
- **README.md**: Scaffolded (currently `26`). Used by version managers and CI.
- **commit-message-template**: From typescript-lib-starter-tiny - **.npmignore**: Ignore `node_modules/`, `coverage/`, `*.log`,
- **Target Environments**: Browser and latest LTS Node.js `*.tsbuildinfo`, `src/`, `.vscode/`, `.editorconfig`,
- **No React, No CJS, ESM only** `.oxfmtrc.json`, `.oxlintrc.json`, `.node-version`, `cspell.json`,
`lefthook.yml`, `commit-message-template`.
(Note: `dist/` is included via `package.json#files`, not by absence
from `.npmignore`.)
- **.oxfmtrc.json**: oxfmt configuration (Prettier-shaped).
- **.oxlintrc.json**: oxlint configuration.
- **.oxlintrc.json + .oxfmtrc.json** replace the old `.eslintrc.cjs`
and `.prettierrc`.
- **LICENSE**: MIT.
- **README.md**: Scaffolded.
- **commit-message-template**: From typescript-lib-starter-tiny.
- **Target Environments**: Node 26 LTS only (no browser target; this
is a pure Node library, no DOM, no DOM lib in tsconfig).
- **No React, No CJS, ESM only**.
## 4. Automation & Quality ## 4. Automation & Quality
- **Version Automation**: Use standard `npm version` for versioning - **Version Automation**: Use standard `npm version` for versioning.
- **Unused Dependency Check**: Use `check-outdated` with config - **Unused Dependency Check**: Use `check-outdated` (devDep, runs in CI
- **No commitlint, no conventional commits** and via the explicit `lefthook run outdated` command).
- **No commitlint, no conventional commits**. Commits use gitmoji
prefixes (e.g. `:sparkles:`, `:wrench:`, `:bug:`, `:fire:`,
`:white_check_mark:`, `:tada:`) for at-a-glance categorization.
## 5. Scripts (Clustered as in typescript-lib-starter-tiny, named similarly) ## 5. Scripts
### SETUP ### SETUP
- `use:git-commit-message`: Set up commit message template (if needed) - `use:git-commit-message`: Set up commit message template (if needed).
### TEST ### TEST
- `test`: Run typecheck and all tests - `test`: Run `tsc --noEmit` then `node --test --strip-types src/`.
- `test:unit`: Run unit tests with Vitest - `test:unit`: Run unit tests with `node --test --strip-types src/`
- `test:ci`: Run tests in CI mode (with coverage, fail-fast) (no preceding typecheck).
- `test:ci`: Run tests in CI mode with c8 coverage (text + lcov + html
reporters), uploading `coverage/` as an artifact.
### BUILD ### BUILD
- `build`: Build the project using Vite - `build`: Build the project using `tsc -p tsconfig.build.json`
(emits `dist/*.js` + `dist/*.d.ts` + sourcemaps, with `.ts`
imports rewritten to `.js`).
### CLEAN ### CLEAN
- `clean`: Clean build output - `clean`: Remove `dist/` via `node -e "fs.rmSync('dist', {recursive:true, force:true})"`.
- `clean:build`: Remove dist directory (Replaces `clean:build` from the original spec — same effect,
no `rimraf` dep needed.)
### CHECK ### CHECK
- `check`: Run all checks (lint, spell, typecheck, import/package sort, outdated) - `check`: Run all checks in order — `check:oxlint`, `check:oxfmt`,
- `check:eslint`: Run ESLint `check:tsc`, `check:cspell`, `check:outdated`.
- `check:prettier`: Check formatting with Prettier - `check:oxlint`: `oxlint ${LEFTHOOK_FILES:-src}` — lints `src/`
- `check:cspell`: Run cspell by default; when invoked from the lefthook pre-commit hook with
- `check:tsc`: TypeScript typecheck (no emit) `LEFTHOOK_FILES` set to the staged-files list, lints only those
- `check:package`: Check package.json sort files. This is the single source of truth for the oxlint command
- `check:outdated`: Check for unused/outdated dependencies and is shared between the manual `npm run check` and the pre-commit
hook. oxlint only understands JS/TS-family languages, so config
files (JSON/YAML/Markdown) are intentionally outside its scope;
they are checked only by oxfmt.
- `check:oxfmt`: `oxfmt --check ${LEFTHOOK_FILES:-.}` — formats the
whole project (`.`) by default, including JS/TS, JSON/JSONC,
YAML, Markdown, MDX and other supported file types. From lefthook
pre-commit, the `LEFTHOOK_FILES` env var scopes to the staged
files matching `*.{ts,tsx,js,jsx,mjs,cjs,json,jsonc,yaml,yml,md,mdx}`.
Built-in `sortPackageJson: true` keeps `package.json` keys
alphabetized (replaces the former `sort-package-json` tool).
Built-in import sorting (enabled via `sortImports: true`) replaces
any external import-sort plugin.
- `check:tsc`: `tsc --noEmit`.
- `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.
### FIX ### FIX
- `fix`: Run all fixers (eslint, prettier, package sort) - `fix:oxlint`: `oxlint --fix src`.
- `fix:eslint`: Auto-fix ESLint issues - `fix:oxfmt`: `oxfmt ${LEFTHOOK_FILES:-.}` — same scoping as
- `fix:prettier`: Auto-fix formatting with Prettier `check:oxfmt` (whole project by default, staged files from
- `fix:package`: Auto-fix package.json sort lefthook). Writes changes in place.
(There is no `fix` aggregator in the scripts; run the `fix:*` scripts
individually.)
### HOOKS ### HOOKS
- Lefthook will run relevant scripts on staged files for pre-commit (typecheck, lint, spell, sort, format, check:outdated) - Lefthook runs the relevant `check:*` scripts on staged files in
parallel for pre-commit. See `lefthook.yml`.
--- ---
## 6. Repository & CI/CD ## 6. Repository & CI/CD
- **Repository**: Hosted on GitHub - **Repository**: Hosted on GitHub.
- **Build Pipeline**: Use GitHub Actions for CI/CD - **Build Pipeline**: GitHub Actions (`.github/workflows/ci.yml`).
- On push and pull request: run build, lint, typecheck, test:ci, spell, check:outdated - `build` job on push and pull_request to `main` and on
- On release (tagged commit): publish to npm `workflow_dispatch`. Steps: `actions/checkout@v4`,
`actions/setup-node@v4` (with `node-version-file: .node-version`,
`cache: npm`), `npm ci`, `npm run build`, `npm run check`,
`npm run test:ci`, then upload `coverage/` as an artifact.
- `publish` job: only on `refs/tags/*`, depends on `build`. Steps:
`actions/checkout@v4`, `actions/setup-node@v4` (with
`node-version-file: .node-version` and `registry-url`),
`npm ci`, `npm run build`, `npm publish --access public`
with `NODE_AUTH_TOKEN` from secrets.
## 7. Versioning & Publishing ## 7. Versioning & Publishing
- **Version Update**: Use `npm version` to bump version after merging to main and before publishing - **Version Update**: Use `npm version` to bump version after merging
- **Publishing to npm**: Only publish from CI on tagged commits (e.g., after version bump and release notes) to main and before publishing.
- **Publishing to npm**: Only publish from CI on tagged commits.
- **Recommended Workflow**: - **Recommended Workflow**:
1. Develop and merge PRs to main 1. Develop and merge PRs to main
2. Run all checks via CI 2. Run all checks via CI
@@ -125,40 +243,42 @@ typescript-lib-starter-tiny => https://github.com/tmueller/typescript-lib-starte
- adt - adt
- typescript - typescript
> The library is for pattern matching (not regex), similar to F#'s pattern matching, for TypeScript/ESM environments. > The library is for pattern matching (not regex), similar to F#'s
> pattern matching, for TypeScript/ESM environments.
## 9. Code Coverage ## 9. Code Coverage
- **Configuration**: - **Tool**: c8 (V8-native coverage, no instrumentation step).
- **Configuration**: c8 has no project config; the report shape is
pinned in the `test:ci` script:
```ts ```jsonc
import { defineConfig } from 'vitest/config'; "test:ci": "c8 --reporter=text --reporter=lcov --reporter=html node --test --strip-types src/"
export default defineConfig({
test: {
coverage: {
reporter: ['text', 'html', 'lcov'],
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'test/**'],
},
},
});
``` ```
- Coverage reports: text summary, HTML, and lcov formats - **Reporters**: text summary, HTML, and lcov (matching the original
spec).
- Add coverage thresholds if desired - **CI**: `coverage/` is uploaded as a workflow artifact via
`actions/upload-artifact@v4` (see `.github/workflows/ci.yml`).
- **CI**: Ensure coverage is generated and optionally uploaded as an artifact or checked for minimum thresholds - **Optional**: Coverage thresholds can be added in c8 config when the
library surface stabilizes.
## 10. Source Structure & Tree Shaking ## 10. Source Structure & Tree Shaking
- **Source Directory**: All source code resides in `src/` and is exported via `src/index.ts`. - **Source Directory**: All source code resides in `src/` and is
exported via `src/index.ts`.
- **Configuration**: - **Configuration**:
- Ensure `sideEffects: false` in `package.json` - `sideEffects: false` in `package.json` (set).
- Use ESM-only exports - ESM-only exports; `package.json#exports` field maps `.` to
- Avoid top-level side effects in modules `{"types": "./dist/index.d.ts", "import": "./dist/index.js"}`.
- Prefer explicit exports in `index.ts` for best results - Avoid top-level side effects in modules.
- the human will implement the source code - Explicit re-exports in `index.ts` for best results.
- Source structure (current):
- `src/index.ts` — public barrel.
- `src/match.ts` — `match(value).with(...).exhaustive() / .otherwise(...)` builder.
- `src/pattern.ts` — `P.literal`, `P.type`, `P.when`, `P.any`, `P.shape` constructors and the `Matcher<T>` interface.
- `src/index.test.ts` — runtime + type-level tests using `node --test` + `expect-type`.
- The human will implement the source code.
## 11. Included Templates from typescript-lib-starter-tiny ## 11. Included Templates from typescript-lib-starter-tiny
@@ -207,90 +327,137 @@ Resolves #...
# Can use multiple lines with "-" for bullet points in body # Can use multiple lines with "-" for bullet points in body
``` ```
### README.md Structure (to scaffold) ### README.md Structure (scaffolded)
- Project title and description - Project title and description
- Development - Development
- Build: `npm run build` - Build: `npm run build`
- Test: `npm run test`, `npm run test:ci` - Test: `npm run test`, `npm run test:ci`
- Checks: `npm run check`, `npm run fix` - Checks: `npm run check`, `npm run fix:oxfmt`, `npm run fix:oxlint`
- Tooling section listing TypeScript 7, node --test, c8, oxlint, oxfmt,
cspell, lefthook
- Requirements: Node.js >= 26
- VSCode integration - VSCode integration
- Debugging - Debugging
- Running tests - Running tests
- Document workflows like - oxc.oxc-vscode provides oxlint and oxfmt in-editor
- Version updates - Workflows
- Changelog automation - Version updates via `npm version`
- Publishing - Publishing via GitHub Actions on tagged commits
- Contribution guidelines - Contribution guidelines
- Commit signing (GPG) - Commit signing (GPG)
- How to set up commit message template - How to set up commit message template (`npm run use:git-commit-message`)
- Reference to commit-message-template - Reference to commit-message-template
- Type-level tests use `expect-type`'s `expectTypeOf(...)` inside
`node --test` cases
## 12. Project Initialization & Commit Strategy ## 12. Project Initialization & Commit Strategy
- Start by initilizing git with a main branch - Start by initializing git with a `main` branch.
- Initial commit: add an empty README.md - Initial commit: empty README + LICENSE.
- create a feature branch: `feature/setup` - Create a feature branch: `feature/setup`.
- For each technology or tool added (and its configuration), create a separate commit: - For each technology or tool added (and its configuration), create a
- Prepend each commit message with a matching gitmoji (e.g., :sparkles: for new features, :wrench: for config, etc.) separate commit:
- Example commit messages: - Prepend each commit message with a matching gitmoji (e.g.
- :tada: Initial commit with empty README `:sparkles:` for new features, `:wrench:` for config,
- :sparkles: Configured Vite (Vite-specific setup) `:bug:` for fixes, `:fire:` for removals,
- :sparkles: Configured TypeScript (may be merged with Vite if dependent) `:white_check_mark:` for tests, `:tada:` for initial commit).
- :wrench: Configured ESLint - Example commit messages used in this project:
- :wrench: Configured Prettier - `:tada: Initial commit with empty README`
- ...and so on for each technology/tool - `:wrench: Track .vscode/settings.json for workspace settings`
- Each commit should include only the relevant files and configuration for that technology/tool - `:construction_worker: Added GitHub Actions workflow for CI/CD`
- This approach ensures a clean, understandable project history and makes it easy to review or revert specific setup steps - `:test_tube: Added Vitest configuration with coverage` (later removed)
- `:sparkles: Scaffolded src/index.ts entry point for library code`
- `:wrench: Replace Vite/Vitest with TypeScript 7 and node --test`
- `:wrench: Replace ESLint and Prettier with oxlint and oxfmt`
- `:fire: Remove Vite scaffold leftovers`
- `:sparkles: Add initial pattern-matching API`
- `:white_check_mark: Add expect-type for type-level tests`
- `:wrench: Declare Node 26 as the supported runtime`
- `:bug: Use .ts extensions in imports for node --strip-types`
- `:wrench: Remove Prettier from editor formatter config`
- Each commit should include only the relevant files and configuration
for that technology/tool. This approach ensures a clean, understandable
project history and makes it easy to review or revert specific setup
steps.
## 13. Changelog Automation ## 13. Changelog Automation
- Use a tool like standard-version or changesets to automate changelog generation from commit messages or PRs. - Not currently configured. The intended workflow, when adopted, is a
- Ensure changelog is updated as part of the release process. changesets-driven release process: feature PRs include a changeset,
which gets consumed by a release workflow, producing a `CHANGELOG.md`
and a version bump on merge to main.
- The changelog should be updated as part of the release process.
## 14. Publishing Public ## 14. Publishing Public
- Configure npm publishing to be public by default. - npm publishing is configured to be public by default.
- Add `"publishConfig": { "access": "public" }` to package.json. - `publishConfig: { "access": "public" }` is set in `package.json`.
- Ensure CI/CD pipeline publishes with public access. - The CI/CD pipeline publishes with `--access public` on tagged commits.
## 15. VSCode Integration ## 15. VSCode Integration
- Add a `.vscode/settings.json` with the following content: - `.vscode/settings.json` uses `oxc.oxc-vscode` as the default
formatter for `[typescript]`, `[javascript]`, `[json]`, `[jsonc]`,
`[markdown]`, `[mdx]`, and `[yaml]` (oxfmt under the hood).
```json ```json
{ {
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"js/ts.tsdk.path": "node_modules/typescript/lib",
"[typescript]": { "[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[javascript]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"[json]": { "[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[jsonc]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true
},
"[markdown]": {
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"[mdx]": { "[mdx]": {
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}, },
"editor.defaultFormatter": "esbenp.prettier-vscode", "[yaml]": {
"editor.formatOnSave": true, "editor.defaultFormatter": "oxc.oxc-vscode",
"[javascript]": { "editor.formatOnSave": true
"editor.defaultFormatter": "esbenp.prettier-vscode", },
"editor.defaultFormatter": "oxc.oxc-vscode",
"editor.formatOnSave": true "editor.formatOnSave": true
}
} }
``` ```
- Add `.vscode/extensions.json` with recommended extensions: - `.vscode/extensions.json` recommends:
- `esbenp.prettier-vscode` (Prettier) - `oxc.oxc-vscode` (oxlint + oxfmt, replaces eslint/prettier/vitest)
- `dbaeumer.vscode-eslint` (ESLint)
- `streetsidesoftware.code-spell-checker` (cspell) - `streetsidesoftware.code-spell-checker` (cspell)
- `vitest.explorer` (for test integration) - `typescriptteam.native-preview` (TypeScript 7 nightly support;
- TODO: Native test extension with node test runner replaces the older `ms-vscode.vscode-typescript-next`)
- `ms-vscode.vscode-typescript-next` (for latest TS features, optional)
- Add `.vscode/tasks.json` for common tasks (optional): ```json
- Build, test, lint, typecheck, format, spell, check:outdated {
- Ensure VSCode uses workspace TypeScript version and Prettier for formatting "recommendations": [
"oxc.oxc-vscode",
"streetsidesoftware.code-spell-checker",
"typescriptteam.native-preview"
]
}
```
- `.vscode/tasks.json` is not currently provided; common tasks
(build, test, lint, typecheck, format, spell, check:outdated) are
run via the npm scripts in `package.json` from the integrated
terminal.
- VSCode uses the workspace TypeScript version via
`typescript.tsdk` (and the explicit `js/ts.tsdk.path`), with
`oxc.oxc-vscode` for formatting and linting.
+3 -1
View File
@@ -1,6 +1,8 @@
import { test } from "node:test";
import { strict as assert } from "node:assert"; import { strict as assert } from "node:assert";
import { test } from "node:test";
import { expectTypeOf } from "expect-type"; import { expectTypeOf } from "expect-type";
import { match, P, type Matcher } from "./index.ts"; import { match, P, type Matcher } from "./index.ts";
test("match returns a builder", () => { test("match returns a builder", () => {