13 Commits
Author SHA1 Message Date
tmu 3b58b06081 🚀 Release 0.9.0
CI / release-gate (push) Successful in 2s
CI / build (push) Successful in 38s
CI / compat (push) Successful in 26s
CI / maintain (push) Failing after 14s
CI / publish (push) Successful in 20s
2026-09-29 21:37:53 +00:00
tmu 36e317bf17 🔀 Merge feature/ts-compat-ci into main
CI / release-gate (push) Successful in 2s
CI / build (push) Successful in 29s
CI / maintain (push) Failing after 13s
CI / compat (push) Successful in 25s
CI / publish (push) Skipped
2026-09-29 21:24:16 +00:00
tmu 0e56fff59e 📝 Check off the CI TypeScript-floor task
The backlog item asked for a 5.0 baseline; the floor is 5.9 (set by type-fest
and the library's own gate), so the text now says "the TypeScript floor".
2026-09-29 21:24:05 +00:00
tmu 24cc007ab0 🔥 Drop redundant not.toBeAny assertions in the fixture
The exact-equality `toEqualTypeOf` assertions already reject `any`, so the
`.not.toBeAny()` guards were redundant. Refresh the fixture header and
development/ci.md to match.
2026-09-29 21:23:15 +00:00
tmu ebe58a9167 📝 Say compat is CI-only and run by hand locally
The pipeline bullet's "(push to `main` / tag)" read like a local push hook.
State plainly that `compat` has no local tier — it is not in `check`, `verify`,
`test:ci` or any hook — and is run manually with `build && test:compat`.
2026-09-29 21:15:01 +00:00
tmu 00ed6bee1e ✅ Assert handler params in the compat fixture
Give the primitive-union handlers a parameter and assert its member type, and
assert the tagged-union widening handler's parameter too. A matcher without
handler-parameter assertions cannot catch a wrong or `any` handler parameter:
the matcher's own `(shape: T) => R` type is built from `T` and the handler
return, so it stays correct even when `Member<T, K>` inversion is broken.
2026-09-29 21:10:15 +00:00
tmu 75807c4bd8 👷 Type-check the TS floor in CI
Add a `compat` job that type-checks the whole suite and a consumer fixture
against the minimum supported TypeScript (5.9), reusing the `dist/` artifact
`build` produced and gating `publish`. The compiler is resolved by npx, so it
never enters `devDependencies` or the local `check`/`verify` loop.

The fixture imports the package by name, resolving the emitted declarations
through the `exports` map; `expectTypeOf` / `.not.toBeAny()` make it reject an
`any`-typed declaration, which a bare compile would accept.

Correct the README consumer floor from >= 5.0 to >= 5.9 (set by `type-fest`)
and drop the `node10` resolution claim, which the exports-only entry never
satisfied.
2026-09-29 20:51:41 +00:00
tmu 45df45df4b 🚀 Release 0.8.3
CI / release-gate (push) Successful in 3s
CI / build (push) Successful in 36s
CI / maintain (push) Successful in 16s
CI / publish (push) Failing after 18s
2026-09-28 22:02:16 +00:00
tmu 21b628f91d 🔀 Merge chore/prune-backlog-and-upgrade-deps into main 2026-09-28 21:59:57 +00:00
tmu 3c802ad7df ⬆️ Bump the pi-lsp pin to 0.0.47
The agent-side LSP extension is declared in `.pi/settings.json`, not in
`package.json`, so neither `npm update` nor `maintain:outdated` sees it.
Bump it with `pi install npm:@spences10/pi-lsp@0.0.47 -l`, which
rewrites the pin and refreshes the gitignored `.pi/npm/` install cache,
then reformat the file: pi writes it back in its own two-space style,
which `check:oxfmt` rejects.

0.0.47 bounds the project-binary trust prompt — it follows tool
cancellation and times out after 30 s, returning a tool error instead of
leaving the session stuck in `Working`, and an allow-once decision now
survives an idle language-server restart. The read-only usage and the
`tsc --lsp` wiring are unchanged, so `development/tooling.md` only moves
the recorded version. No changelog note: the pin is agent tooling, not
shipped code, as when it was adopted (863198d).

The running pi session still has 0.0.46 loaded; restart pi to pick up
the new version.
2026-09-28 21:58:11 +00:00
tmu 5025fa3870 🔧 Drop the redundant knip entry pattern
knip derives the public entry from package.json `exports` and resolves
it to src/index.ts itself, so naming the file in `entry` is redundant:
maintain:knip reported it as a configuration hint, and the scan is now
silent. Verified the barrel is still treated as the entry — the scope
is clean with and without dist/, and with src/index.test.ts removed.

Supersedes the workaround recorded in development/tooling.md, which
silenced an unused-file report for the barrel that the current entry
derivation no longer produces. Rewrite the section to state the
behaviour and keep the rejected `paths` mapping.
2026-09-28 21:55:05 +00:00
tmu eb1acbd9d8 ⬆️ Upgrade dependencies
Bump every direct dependency to the latest registry version: cspell
10.3.5, oxfmt 0.71.0, oxlint 1.86.0, oxlint-tsgolint 7.0.2003,
@types/node 26.6.3, the LSP protocol types 3.18.4 and 85 transitive
entries. oxfmt is the only range widened (^0.70.0 -> ^0.71.0), because
0.x minors are breaking under semver and `npm update` stops at the
range; the rest moved within their existing ranges.

`npm run maintain:outdated` is clean and `npm run verify` is green with
no rule or format fallout. `maintain:knip` reports one configuration
hint — the redundant `src/index.ts` entry pattern in knip.json — but
knip did not move, so the hint predates this bump and stays advisory.
2026-09-28 21:47:05 +00:00
tmu df09d3ae61 📝 Prune the completed backlog items
Remove every done/cancelled item. Git history is the archive and each
item's rationale already lives in the README, in development/ or in a
released changelog entry; none was recorded only here, so no item had to
be rescued into development/ before it could go.

- drop the `v1.0:` and `Matcher:` projects too: their last open task is
  done, so those headers would stand empty
- summarise the change under `[Unreleased]` in the changelog
2026-09-27 20:41:59 +00:00
14 changed files with 641 additions and 403 deletions

No files matched your search

+32 -1
View File
@@ -119,6 +119,35 @@ jobs:
name: dist
path: dist/
# Consumer typecheck against the minimum supported TypeScript (README
# § Requirements), run over `dist/`'s emitted declarations and the whole
# suite. Deliberately a separate job, not a step in `build`: the compiler is
# a different major picked by `npx`, and it must never enter
# `devDependencies`, the local `check`/`verify` tiers, or the lockfile. See
# development/ci.md § TypeScript compatibility.
compat:
needs: build
runs-on: ubuntu-latest
# Same baked image as `build` — without it this job re-downloads Node
# per run (see docker/Dockerfile).
container:
image: gitea.e1nsnull.de/tmu/act-ci:26.8.2
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version-file: .node-version
cache: "npm"
- run: npm ci
# Reuse the exact `dist/` that `check`, `test:ci` and `publint` were
# run against, so the compat gate judges the shipped artifact and
# pays no rebuild.
- uses: actions/download-artifact@v4
with:
name: dist
path: dist/
- run: npm run test:compat
# Advisory scans (dead code, dependency freshness). Non-blocking: surfaced in
# the Actions tab for visibility, but must never gate a merge — so
# continue-on-error and intentionally NOT in `publish`'s `needs`.
@@ -142,7 +171,9 @@ jobs:
publish:
if: startsWith(gitea.ref, 'refs/tags/')
needs: build
# `compat` gates the release: an artifact that is not consumable at the
# claimed TypeScript floor must never ship.
needs: [build, compat]
runs-on: ubuntu-latest
# Same baked image as `build` — setup-node still owns the registry-url
# `.npmrc` rewrite here; only the Node download is skipped.
+1 -1
View File
@@ -1,3 +1,3 @@
{
"packages": ["npm:@spences10/pi-lsp@0.0.46"]
"packages": ["npm:@spences10/pi-lsp@0.0.47"]
}
+19 -1
View File
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [0.9.0] - 2026-09-29
- add a CI `compat` job that type-checks the suite and a consumer fixture
against the minimum supported TypeScript (5.9), consuming the built `dist/`
and gating `publish`
- correct the documented consumer floor to TypeScript >= 5.9 and drop the
unsupported `node10` resolution claim
## [0.8.3] - 2026-09-28
- upgrade dependencies: oxfmt 0.71 (the only range widened), oxlint 1.86,
oxlint-tsgolint 7.0.2003, cspell 10.3.5 and the rest within their existing
ranges; no rule or format fallout
- prune the completed items from the backlog; their rationale already lives in
the README, `development/` and the released changelog entries
## [0.8.2] - 2026-09-25
- write the README's Synopsis and Examples sections
@@ -108,7 +124,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- basic setup
[Unreleased]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.8.2...main
[Unreleased]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.9.0...main
[0.9.0]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.8.3...0.9.0
[0.8.3]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.8.2...0.8.3
[0.8.2]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.8.1...0.8.2
[0.8.1]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.8.0...0.8.1
[0.8.0]: https://gitea.e1nsnull.de/tmu/tiny-pattern-ts/compare/0.7.1...0.8.0
+8 -1
View File
@@ -20,6 +20,10 @@ the rules so agents and humans don't diverge.
- **Build:** `npm run build`
- **Test:** `npm run test`, `npm run test:ci`
- **Compat (CI-only, manual locally):** `npm run test:compat` — type-check the
suite + a consumer fixture against the minimum supported TypeScript; needs a
built `dist/` and network access (`npx`). Never part of a hook or
`check`/`verify`
- **Watch:** `npm run watch` - re-runs tests on file save, humans only
- **Checks:** `npm run check`, `npm run fix`
- **Doc tests:** `npm run create:doc-tests` — compile the `ts`-tagged fences
@@ -44,6 +48,7 @@ faster tiers catch less, slower tiers are more thorough":
| `npm run fix` | manual | Auto-resolve fixable issues (lint, format) | ~3s |
| `npm run maintain` | manual / CI (advisory) | `maintain:knip` + `maintain:outdated` (whole-project + network scans) | ~10s |
| CI build (auto) | on push to `main` / tag | `build` job (build + correctness + coverage + packaging) — see [.gitea/workflows/ci.yml](./.gitea/workflows/ci.yml) | ~30s+ |
| CI compat (auto) | in CI only | `compat` job — `test:compat` over the built `dist/`; never a local tier (run by hand) — see [compat/](./compat) | ~15s |
| CI maintain (auto, non-blocking) | on push to `main` | `npm run maintain` — reports, never fails the build | ~10s |
| CI publish (auto) | on tag | packaging checks + `publish:publint` / `publish:attw`, then the Gitea release page and `npm publish` (skipped, and the job failed, without `NPM_TOKEN`) | ~15s |
@@ -142,7 +147,9 @@ CI step. Pick the prefix that matches the script's lifecycle:
unit tests); `test:unit` skips the typecheck for fast local iteration;
`test:coverage` runs c8 over the hand-written tests only; `test:doc` runs the
generated doc examples without coverage; `test:ci` chains the two and fails
below 100% coverage on `src/` (CI-only; `verify` stays coverage-free).
below 100% coverage on `src/`; `test:compat` type-checks the suite + a
consumer fixture against the minimum supported TypeScript via `npx` (both
CI-only; `verify` stays coverage- and network-free).
- `watch:*` — long-running watchers for the manual inner dev loop. Aggregated by
`watch`.
- `maintain:*` — advisory repo-maintenance scans: read-only, but whole-project
+8 -3
View File
@@ -54,9 +54,14 @@ npm install tiny-pattern-ts
## Requirements
- **Node.js >= 26** (`engines` field; pinned via `.node-version`).
- **TypeScript >= 5.0** to consume the published declarations. The emitted `.d.ts`
use `const` type parameters (TS 5.0) and keep their relative `.ts` specifiers;
both resolve on TS >= 5.0 in `node10` / `node16` / `nodenext` / `bundler`.
- **TypeScript >= 5.9** to consume the published declarations. The floor is set
by the `type-fest` types the declarations use and is checked in CI against a
consumer fixture; see [`compat/`](./compat) and
[development/ci.md § TypeScript compatibility](./development/ci.md#typescript-compatibility).
The emitted `.d.ts` keep their relative `.ts` specifiers, which resolve under
`node16` / `nodenext` / `bundler`. The package exposes only an `exports` map
(no `main` / top-level `types`), so the legacy `node10` resolver does not
apply.
- The package is **ESM-only** (no CommonJS shim).
## Examples
+1 -25
View File
@@ -7,32 +7,8 @@ Backlog and tracking for tiny-pattern-ts. Managed in vscode-todotasks format.
Setup:
☐ Split off template into separate package => pi --session 01a07dde-7050-7054-bb36-1606d7eb2bc3 @low
v1.0:
✔ API surface is stable and fully typed @done
✔ Finalize public exports in `src/index.ts` @done
✔ Document all exported types and functions @done
✔ Add JSDoc for public APIs @done
✔ Test coverage meets threshold @done
✔ Achieve 100% branch coverage on `src/primitive-union.ts` @done
✔ Achieve 100% branch coverage on `src/index.ts` @done
Matcher:
✔ when using a union type as a property, the current behavior of tagged union matcher is @done
to pass never to handler parameters
→ new matcher function needed or can be fixed in tagged union matcher
✔ optional discriminant (`{ type?: "x" }`) is the same hole: the boolean/nullish change now admits the `undefined` tag, so the factory accepts the key, but `Extract<T, Record<K, V>>` still passes `never` to both the `x` and `undefined` handlers @done
Documentation:
✔ Bring README.md back to its previous form — synopsis and examples restored, in the correct place @done
→ previous section order: title, tagline, Synopsis, Description, Requirements, Examples, API, License, Contributing
→ previous Examples order: literal/exhaustive, typeof, structural/discriminated unions, when, any
☐ Create `examples/` directory with runnable snippets
✔ Add comparison section vs. other TS pattern-matching libs in Readme.md @done
✔ Why do we do this? => exhaustiveness encoded type safe @done
✔ Why this form? little syntax, data last, very small, autocomplete, strict typing in the handler; for more features use ts-pattern @done
✔ Write migration guide for users coming from discriminated unions @done (9/24/2026, 10:21:17 PM)
✔ Create backlog tasks for implementation @done (9/24/2026, 10:21:16 PM)
✔ Validate code fences in Markdown (start with README.md) — compile the TypeScript examples against `src/` so the docs cannot drift from the API @done
Maintenance:
☐ Serve CI coverage over a tiny self-hosted webserver (replace the zip artifact) @low
@@ -46,4 +22,4 @@ Maintenance:
☐ Add a minimal dir-listing webserver to the gitea docker setup for serving landing page (reuse existing reverse proxy)
☐ CI writes landing page to a shared volume keyed by project + tag (e.g. `/landing/tiny-pattern-ts/<tag>/`)
☐ Browse to `…/tiny-pattern-ts/index.html` in the browser
☐ Add testing with TypeScript 5.0 baseline in CI
✔ Add testing with the TypeScript floor in CI @done
+89
View File
@@ -0,0 +1,89 @@
// Consumer smoke test for the minimum supported TypeScript (see README
// § Requirements). It imports the package by name, so it resolves through the
// `exports` map to the emitted `dist/*.d.ts` — including the relative `.ts`
// specifiers they keep — rather than to the source. Run by `npm run test:compat`
// and the CI `compat` job only; never by `check` / `verify`.
//
// Why the `expectTypeOf` assertions: a compile that merely succeeds is a weak
// oracle. An `any`-typed declaration would compile, but `expect-type`'s exact
// equality rejects `any`, so the assertions prove the emitted types are real.
// Every handler *parameter* is asserted too, not just the matcher: the handler's
// member type comes from the `Member` inversion in the declarations, and a wrong
// or `any` parameter would otherwise slip through, since the matcher's own
// `(shape: T) => R` type is built from `T` and the handler returns. See
// development/ci.md § TypeScript compatibility.
import { expectTypeOf } from "expect-type";
import {
getPrimitiveUnionMatcher,
getPrimitiveUnionMatcherW,
getTaggedUnionMatcher,
getTaggedUnionMatcherW,
} from "tiny-pattern-ts";
type ResultCode = "ok" | "created";
const toStatus = getPrimitiveUnionMatcher<ResultCode>()({
ok: (s) => {
expectTypeOf(s).toEqualTypeOf<"ok">();
return "OK";
},
created: (s) => {
expectTypeOf(s).toEqualTypeOf<"created">();
return "CREATED";
},
});
expectTypeOf(toStatus).toEqualTypeOf<(shape: ResultCode) => string>();
// The widening twin keeps each handler's own return type in the union.
const toStatusW = getPrimitiveUnionMatcherW<ResultCode>()(
{
ok: (s) => {
expectTypeOf(s).toEqualTypeOf<"ok">();
return "OK" as const;
},
},
(rest) => {
expectTypeOf(rest).toEqualTypeOf<"created">();
return "CREATED" as const;
},
);
expectTypeOf(toStatusW).toEqualTypeOf<
(shape: ResultCode) => "OK" | "CREATED"
>();
type Contact =
| { kind: "email"; address: string }
| { kind: "phone"; number: string };
const format = getTaggedUnionMatcher<Contact>()("kind")({
email: (e) => {
expectTypeOf(e).toEqualTypeOf<{ kind: "email"; address: string }>();
return e.address;
},
phone: (p) => {
expectTypeOf(p).toEqualTypeOf<{ kind: "phone"; number: string }>();
return p.number;
},
});
expectTypeOf(format).toEqualTypeOf<(shape: Contact) => string>();
const formatW = getTaggedUnionMatcherW<Contact>()("kind")(
{
email: (e) => {
expectTypeOf(e).toEqualTypeOf<{ kind: "email"; address: string }>();
return e.address;
},
},
(rest) => {
expectTypeOf(rest).toEqualTypeOf<{ kind: "phone"; number: string }>();
return rest.number;
},
);
expectTypeOf(formatW).toEqualTypeOf<(shape: Contact) => string>();
export { format, formatW, toStatus, toStatusW };
+15
View File
@@ -0,0 +1,15 @@
{
"extends": "@tsconfig/strictest/tsconfig.json",
"compilerOptions": {
"lib": ["es2024"],
"module": "nodenext",
"target": "es2024",
"types": ["node"],
"skipLibCheck": false,
"noEmit": true,
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true
},
"include": ["../src", "../scripts", "*.ts"],
"exclude": ["../src/doc-test"]
}
+73
View File
@@ -6,6 +6,12 @@ the job graph; this file records why it is shaped the way it is.
## Pipeline
- **`build`** (push to `main` / tag) — build + correctness + packaging.
- **`compat`** (CI only) — type-check the suite and a consumer fixture against
the minimum supported TypeScript; consumes `build`'s `dist/` and gates
`publish`. It has **no local tier**: it never runs in a hook or in
`check` / `verify` / `test:ci`; run it by hand with
`npm run build && npm run test:compat`. See
[§ TypeScript compatibility](#typescript-compatibility).
- **`maintain`** (push to `main`, non-blocking) — `npm run maintain`; reports,
never fails the build.
- **`publish`** (tag) — packaging checks + `publish:publint` / `publish:attw`,
@@ -130,6 +136,73 @@ the `build` job; `npm run verify` stays coverage-free.
lists it under `--all`. It carries a file-level `/* c8 ignore start */` with
the reason. Adding runtime code there means removing that directive.
## TypeScript compatibility
#### Decision (2026-09)
A dedicated `compat` job runs `npm run test:compat` — the `npx`-pinned
TypeScript 5.9 compiler (`typescript@5.9.2`) over `compat/tsconfig.json` —
against the `dist/` artifact `build` produced, and `publish` requires it. The
floor is TypeScript 5.9, pinned in the `test:compat` script itself (the single
source of truth) and documented in [README § Requirements](../README.md#requirements).
#### Why
- The compiler is a **different major** from the repo's TypeScript 7, so it is
resolved by `npx` at run time. It must never appear in `devDependencies`: that
would install it for every local `npm ci` and drift the lockfile, which would
put a second compiler in the local `check` / `verify` loop and every editor.
- **CI-only is the point.** `npx` fetches over the network — like `maintain`'s
scans, a network-bound check is never a local feedback tier (see
[workflow.md § Feedback tiers](./workflow.md#feedback-tiers)). It is not in
`check`, `verify`, `test:ci` or any hook; locally it is run **by hand** with
`npm run build && npm run test:compat`.
- **`compat` consumes `build`'s artifact** rather than rebuilding, so it judges
the exact bytes `check`, `test:ci` and `publint` saw.
- **`compat` gates `publish`** because the types are the feature: an artifact
that is not consumable at the advertised floor must not ship.
- **The fixture is a consumer, not a unit test.** `compat/fixture.ts` imports
the package by name (`tiny-pattern-ts`), so it resolves through the `exports`
map to `dist/index.d.ts` and exercises the emitted declarations' relative
`.ts` specifiers — not the source. `expectTypeOf`'s exact-equality assertions
are load-bearing: a bare compile would also pass if a declaration collapsed to
`any`; they reject that.
#### Rejected
- **A `devDependencies` alias** (`npm:typescript@5.9`): installs the legacy
compiler locally, defeating "CI only".
- **A second lockfile / sub-project** (`compat/` with its own `npm ci`): a
pinned, reproducible matrix, but a whole extra lockfile to maintain for one
compiler. `npx -p` is enough.
- **A `paths` / `moduleSuffixes` redirect** to typecheck the _existing_ suite
against `dist/` without touching it: `paths` cannot remap the relative
`./index.ts` imports the tests use; `moduleSuffixes` only lets _missing_
source resolve to suffixed copies, so it would need generated `.compat.ts`
declarations staged into `src/` (plus excludes). Both spend more than the
fixture buys. See the [handover](../backlog.tasks) discussion.
- **Writing the fixture against source** (relative import): it would prove the
source compiles under 5.9, not that the _published_ declarations do, which is
the promise consumers rely on.
- **Replacing `attw`**: `attw` owns the full resolution matrix
(`node10`/`node16`/`nodenext`/`bundler`); `compat` answers only "does the
documented floor compile the artifact".
#### Known issue
- `@tsconfig/node26` cannot be extended: its `lib: ["es2025", ...]` and
`target: es2025` are rejected by 5.9 (`TS6046`). `compat/tsconfig.json`
extends only `@tsconfig/strictest` and sets `lib` / `target: es2024`, the
ceiling 5.9 accepts.
- `skipLibCheck: false` is deliberate — it is what makes the floor honest
(`type-fest` pins it at 5.9), rather than hiding a broken dependency d.ts
behind `true`.
- The version appears in both the `test:compat` script and the README; a floor
bump is a two-file change. The script is authoritative.
- `src/doc-test` is excluded from `compat/tsconfig.json`. The generated examples
are checked against the source by their own project; `test:compat` covers the
suite plus the fixture.
## Coverage serving
#### Decision (2026-09)
+2 -2
View File
@@ -107,8 +107,8 @@ Each factory is two overloads whose order is load-bearing:
- **Variance / `const` type parameters / `NoInfer` / `unique symbol` brands /
defaulted type-param guards.** None change inference or evaluation order;
`in`/`out` on the handler map broke contextual typing outright. `NoInfer`
specifically leaks into the emitted `.d.ts`, raising the consumer floor to
TypeScript 5.4 (README promises `>= 5.0`).
specifically leaks into the emitted `.d.ts`, which would raise the consumer
floor above the documented one (see [README § Requirements](../README.md#requirements)).
- **Union merge**, **overload merge with only the exhaustive arm last**,
**inferred universe**, **conditional `RequireKeys`**, **cases-first curried** —
decided against while the API was single-object; their reasons (reported
+13 -12
View File
@@ -196,27 +196,28 @@ joining the oxfmt-superseded rules already off.
are part of the public API.
- The narrower scope keeps the signal high without config-file boilerplate.
### `knip` lists `src/index.ts` as an entry
### `knip` does not list the library entry
#### Decision (2026-09)
`knip.json` declares `"entry": ["src/index.ts", "scripts/*.ts"]`.
`knip.json` declares `"entry": ["scripts/*.ts"]`; the library entry
`src/index.ts` is not listed.
#### Why
- Supplying `entry` **replaces** knip's default entry detection, which otherwise
derives the public entry from `package.json` `exports`. Adding `scripts/*.ts`
there therefore dropped the library entry, so knip resolved the package through
its `dist/index.js` output and reported the unreferenced source entry file
`src/index.ts` as an unused file.
- Naming the source entry restores the link between the public API and the
source graph without pointing knip at build output.
- knip derives the public entry from `package.json` `exports` and resolves it to
`src/index.ts` itself, so naming the file is a redundant pattern and
`maintain:knip` reports it as a configuration hint.
- Listing `scripts/*.ts` does not switch that derivation off: the scope stays
clean with or without `dist/`, and with the barrel's own test removed.
#### Rejected
- `"src/index.ts"` in `entry`: it silenced an `unused files` report for the
barrel, which the derivation above no longer produces; keeping it only adds a
hint.
- `paths` mapping `dist/index.*` back to `src/index.ts`: more config to model a
relation the explicit entry states directly, and it would break whenever the
build layout changes.
relation knip already resolves.
### `maintain:outdated` ignores `@types/node`
@@ -327,7 +328,7 @@ of a hand-rolled JSON-RPC client.
#### Decision (2026-09)
`@spences10/pi-lsp` is pinned to `0.0.46` and used read-only.
`@spences10/pi-lsp` is pinned to `0.0.47` and used read-only.
#### Why
+1 -1
View File
@@ -1,5 +1,5 @@
{
"$schema": "./node_modules/knip/schema.json",
"entry": ["src/index.ts", "scripts/*.ts"],
"entry": ["scripts/*.ts", "compat/*.ts"],
"ignoreDependencies": ["@runwisp/pubv"]
}
+376 -354
View File
File diff suppressed because it is too large. Load diff
+3 -2
View File
@@ -1,6 +1,6 @@
{
"name": "tiny-pattern-ts",
"version": "0.8.2",
"version": "0.9.0",
"description": "Exhaustive, type-safe pattern matching for TypeScript",
"keywords": [
"adt",
@@ -61,6 +61,7 @@
"maintain:outdated": "check-outdated --ignore-pre-releases --ignore-packages @types/node",
"test": "npm run check:tsc && node --test --strip-types \"src/**/*.test.ts\"",
"test:ci": "npm run test:coverage && npm run test:doc",
"test:compat": "npx --yes --package typescript@5.9.2 tsc --project compat/tsconfig.json",
"test:coverage": "c8 --all --include \"src/**/*.ts\" --reporter=text --reporter=lcov --reporter=html --100 node --test --strip-types $(git ls-files 'src/*.test.ts')",
"test:doc": "node --test --strip-types \"src/doc-test/__generated__/*.test.ts\"",
"test:unit": "node --test --strip-types \"src/**/*.test.ts\"",
@@ -88,7 +89,7 @@
"knip": "^6.34.0",
"lefthook": "^2.1.12",
"mdast-util-from-markdown": "^2.0.3",
"oxfmt": "^0.70.0",
"oxfmt": "^0.71.0",
"oxlint": "^1.83.0",
"oxlint-tsgolint": "^7.0.2001",
"publint": "^0.3.24",