Predictable TypeScript types for npm packages: check entry points, module kinds, and export bindings under every resolution mode Node and bundlers use, before you publish.
TypeScript package type-checking engine and CLI for auditing npm package entry points, module kinds, and export bindings across Node and bundler resolution modes.
| Package | What it is |
|---|---|
@systemfsoftware/arethetypeswrong-cli |
The attw command — checks a tarball, a directory, or a published package |
@systemfsoftware/arethetypeswrong |
The analysis engine, as a library for your own tooling |
@systemfsoftware/arethetypeswrong-recipes |
Synthetic packages, one per problem kind, that test the engine (not published) |
Add the CLI to the project you want to check, so your lockfile pins the version:
pnpm add -D @systemfsoftware/arethetypeswrong-cliNote
Prefer a lockfile-pinned install over npx. A tool whose job is auditing what your package resolves should not itself be resolved fresh from the registry on every run.
The CLI is also a flake output, built from this repository with the Node that runs it pinned:
nix run github:systemfsoftware/are-the-types-wrong#attw -- --pack .Run the check from the package you want to audit. --pack runs npm pack, analyzes the resulting tarball, and deletes it:
pnpm exec attw --pack .A healthy package prints a row per entry point and a column per resolution mode:
No problems found.
Entrypoint . ./package.json
node10 ✔ ✔
node16-cjs ✔ ✔
node16-esm ✔ ✔
bundler ✔ ✔
Problems replace the ✔ with ✘ and are named above the table, so the exit code can gate CI. Use a profile when your package deliberately supports only some resolution modes.
checkPackage returns an Effect, so compose it into your own program and let your edge interpret it once:
pnpm add @systemfsoftware/arethetypeswrongimport { checkPackage } from '@systemfsoftware/arethetypeswrong' import { createPackageFromTarballData } from '@systemfsoftware/npm-package' import { Effect } from 'effect' import * as FileSystem from 'effect/FileSystem'
const check = Effect.gen(function*() { const fs = yield* FileSystem.FileSystem const tarball = yield* fs.readFile('./my-package-1.0.0.tgz') const analysis = yield* checkPackage(createPackageFromTarballData(tarball))
// analysis.entrypoints — a resolution record per subpath // analysis.problems — what failed, with the position of the offending syntax return analysis })
Interpret it once, at the edge of your program: yield* it into a larger Effect, or run that Effect with NodeRuntime.runMain if it is a script that terminates. Keep one edge — runMain sets the exit code and installs the interrupt handlers, and wrapping it in a second runtime leaves the outer edge with no reach over the fibers doing the work.
The engine simulates how Node and TypeScript resolve each entry point under the node10, node16, and bundler modes:
- Entry point resolution — do
exports,main,types, andbintargets resolve to files that exist? - Module kind agreement — does a file's actual format (ESM, CJS, JSON) match what
typeand its extension imply? - Export parity — do default and named exports line up between the type entry point and the implementation?
- Unexpected module syntax —
require/module.exportsinside an ESM file, orimport/exportinside a CJS file. - CJS-only default export — a CommonJS file whose only export is a default, which
esModuleInteropconsumers receive wrapped. - Internal resolution errors — TypeScript's own resolution failures, reported with the failing specifier and mode.
Development setup, build, and test workflow: CONTRIBUTING.md.
Licensed under Apache-2.0.