Skip to content

Package Checks ​

Two commands that look at a package the way a real consumer would, not the way its own build does: exports-doctor checks the paths declared in package.json against what's really on disk, readme-check typechecks code examples from README/docs against the package's own build. Both are read-only — neither writes anything, and neither executes an example's source code, only compiles it / resolves paths.

exports-doctor ​

Resolves every path package.json declares (main/module/types/typings/bin/exports, including nested condition objects, subpath maps, and fallback arrays) against what's actually on disk.

bash
devtoolz exports-doctor [dir] [options]

The command reads a single package.json in the given directory (current directory by default) rather than walking a file tree — so it has no --cwd/--ext/--ignore, only [dir].

Real output — a package whose bin file forgot its shebang, and whose ./cdn subpath resolves fine at runtime but never declares types anywhere:

🧰 devtoolz — chores, automated

Checked widget-kit's declared exports.

2 problems found:
  bin.widget-kit    ./dist/cli.js        missing '#!/usr/bin/env node' — won't run as a bin
  exports["./cdn"]  ./dist/cdn/index.js  runtime resolves fine, but no types declared for this entry at all

The second case is the sneakiest of them all: import from pkg/cdn genuinely works for any consumer, the package's own build passes without a single error, and yet nobody gets TypeScript hints for that path — no error, no warning, just silent nothing instead of types. Only checking it against what's actually declared under the same exports key catches it.

A case mismatch uses the same mechanics as case-check, just for package.json fields instead of source imports:

🧰 devtoolz, reporting for duty

Checked widget-kit's declared exports.

1 problem found:
  main  ./dist/index.js  wrong case — real path is ./dist/Index.js

What's checked ​

  • missing — the path doesn't resolve at all, under any case.
  • case-mismatch — it resolves, but under a different case — the real on-disk path is shown.
  • missing-shebang — a bin file exists but doesn't start with #!/usr/bin/env node, so it won't run as an executable.
  • types-missing — a subpath in exports has at least one runtime condition (import/require/…) that resolves fine, but no types is declared among that same subpath's conditions anywhere. Doesn't fire for a plain string value with no conditions object at all — for that shape TypeScript already falls back to a colocated .d.ts file by convention, the same way main does.

Options ​

[dir] ​

Package directory to check (positional argument, default: .).

Example:

bash
devtoolz exports-doctor                  # package.json in the current directory
devtoolz exports-doctor path/to/package  # a specific directory
devtoolz exports-doctor --json           # machine-readable output, for CI

readme-check ​

Extracts fenced ts code blocks from README/docs and actually typechecks them with the TypeScript Compiler API against the package's own tsconfig.json.

bash
devtoolz readme-check [dir] [options]

The virtual file a block's code gets dropped into lives right at the package root (next to package.json) — so both relative imports and a self-referencing import of the package's own published name (import { x } from 'my-package') resolve exactly as they would for a real consumer, as long as the package's moduleResolution supports it (bundler/node16/nodenext). Nothing ever runs — only compiles.

Real output — a README with two examples, one working and one deliberately wrong, both importing the package by its own published name:

🧰 devtoolz

Typechecked 2 code blocks across 1 file.

1 problem found:
  README.md:13:7  ts  Type 'string' is not assignable to type 'number'.

The first block (const widget = createWidget('main'); console.log(widget.label)) doesn't show up in the report — it genuinely compiles. The second, const count: number = createWidget('main').label, compiles too, but label is a string, not a number — hence the finding.

Each block is checked in isolation ​

A block is checked on its own, with no shared context from other blocks in the same file. That's a deliberate choice, not a shortcut: a doc snippet is routinely a fragment that assumes a variable set up somewhere in the surrounding prose or in a different, unrelated example ("inside your component:", "given a router instance, …") — in isolation that reads as an undefined name, without necessarily being a real doc bug. So the following classes of TypeScript diagnostics are deliberately dropped rather than shown as findings:

  • Cannot find name and its "did you mean" variant — an undefined name, most likely inherited from context outside the block;
  • Cannot find module './x' for a RELATIVE path — almost always a hypothetical file in the reader's own project ("your App.vue"), not something that could exist in the package itself;
  • "X is declared but its value is never read" — a doc fragment routinely declares a handler that's called somewhere else (a template, a different example), not in this same block;
  • a self-referencing import failing to resolve specifically because the package's OWN tsconfig.json doesn't set moduleResolution: bundler/node16/nodenext — that's a limitation of the package's own environment, not a doc bug.

Cannot find module 'pkg-name' for a BARE package name (self-reference), by contrast, remains a finding — that's the one historically valuable case this command exists for.

Options ​

--file <path> ​

Markdown file to check, relative to [dir] (repeatable). Default: README.md.

--lang <list> ​

Comma-separated fenced-block languages to check. Default: ts only. tsx is deliberately not in the default set — Vue packages typically don't configure JSX/React types, so a tsx block there is almost always illustrative pseudocode the package itself could never compile; opt in explicitly with --lang ts,tsx for a package that actually configures JSX.

--tsconfig <path> ​

tsconfig.json to read compiler options from (auto-detected in [dir] by default).

Example:

bash
devtoolz readme-check                          # ts blocks in ./README.md
devtoolz readme-check --file docs/guide.md      # another doc instead of/alongside README
devtoolz readme-check --lang ts,tsx             # include tsx blocks too