Build and supply chain
The stack
| Concern | Tool | Why |
|---|---|---|
| Compiler | TypeScript 7.0.2 | Go-native. Pinned to ~7.0 — see below. |
| Bundler | tsdown (rolldown) | Native .d.ts, dual ESM+CJS, same vendor as oxc |
| Lint | oxlint + tsgolint | Type-aware via the real compiler, 12–18× ESLint |
| Format | oxfmt | Prettier-compatible, one binary |
| Tests | Vitest + fast-check | Runtime, type-level and property tests in one runner |
| Releases | Changesets + OIDC | Trusted publishing, no long-lived npm token |
No ESLint, no Prettier — the Rust toolchain does both jobs in a fraction of the time and a fraction of the node_modules.
TypeScript 7 has no JS API
This is the single most consequential fact about building on TS 7 today. Its package exports maps "." to a three-line version.cjs. There is no ts.createProgram.
Anything that does import ts from 'typescript' simply does not work: TypeDoc, ts-morph, api-extractor, dts-bundle-generator, tsd, typescript-eslint. The API is expected to return, in a different form, in TypeScript 7.1.
Two consequences visible in this repo:
- The API reference is generated by a small in-repo script rather than TypeDoc. Its completeness is guaranteed by reading the export inventory from the built package at runtime, so an undocumented export is a build failure.
- TypeScript is pinned to
~7.0.2, not^7.0.2.rolldown-plugin-dtsgates its tsgo path on an exactversionMajorMinor === "7.0"comparison and falls back to a package that is not in the dependency graph. A single unguarded minor bump breaks declaration emit and desynchronises the linter from the compiler.
The declaration pipeline has no exit code
tsdown drives rolldown-plugin-dts, which spawns the compiler with --noCheck and resolves on process close without reading the exit status.
Declaration-emit-only diagnostics — TS4023 ("cannot be named"), TS2742 ("inferred type cannot be named without a reference to X") — are exactly what deeply inferred curried generics provoke, and they are suppressed there. The plugin throws only if an entry's .d.ts is missing entirely; a type degraded to any would ship silently.
So pnpm check:dts-emit runs the real compiler with declaration emit and a real exit code, as a required CI job, plus a grep asserting no exported declaration contains : any. It is not hygiene — it is the only thing between this pipeline and shipping a broken product.
Zero ambient dependencies
tsconfig.json sets types: [], so the published .d.ts cannot depend on @types/* from devDependencies. That is why there is no Task.delay: setTimeout is a host API, and including it would have meant widening lib.
tsdown builds with platform: 'neutral' on the same premise.
Supply chain
Every GitHub Action is pinned to a full commit SHA with a version comment, and Renovate updates those pins — a pinned SHA nothing updates is a stale SHA.
| Control | Notes |
|---|---|
| gitleaks | MIT CLI, pinned + checksum-verified. Deliberately not gitleaks-action, which has been proprietary since v2.0.0 and is free only under a personal account. |
| OSV-Scanner | Reads pnpm-lock.yaml natively; no account, no token. |
| CodeQL | javascript-typescript. Found a real arity defect during development. |
| OpenSSF Scorecard | Published to the security tab. |
| Dependency review | Blocks copyleft licences that would contaminate MIT. |
| npm provenance | SLSA attestation via OIDC trusted publishing. |
Snyk was removed
It was wired in, given a token, and removed once it proved unable to say anything true here. Three failures compound:
- Snyk cannot parse pnpm 11's lockfile — pnpm writes a multi-document YAML file and Snyk's parser expects one document.
npm install --package-lock-onlycrashes on this dependency tree (@npmcli/arboristthrows), so no substitute lockfile can be generated.- Without a lockfile,
snyk test --file=package.jsonexits 0 with "no vulnerable paths found" while--print-depsshows the scanned tree assmullyan @ 0.0.0— zero dependencies.continue-on-error: truehid the hard parse failure for a full run and reported the job green.
A scan that reports clean because it scanned nothing is worse than no scan: it trains you to trust a signal carrying no information. OSV-Scanner covers the same advisory databases, correctly, on the real lockfile.
Releasing
The first publish had to be manual — trusted publishing cannot create a package, and since 2026-07-31 bypass-2FA tokens can no longer modify trusted-publishing configuration either. Everything after that is automated, with one deliberate manual step documented in Releasing.