The packaging bugs I kept shipping (and the tool I built to stop)

Published: (February 18, 2026 at 07:33 AM EST)
3 min read
Source: Dev.to

Source: Dev.to

What goes wrong

1. CJS/ESM format mismatch

Your package.json says:

{
  "exports": {
    "require": "./dist/cjs/index.cjs"
  }
}

but ./dist/cjs/index.cjs contains import/export statements—i.e. it’s actually ESM.
Webpack and esbuild don’t mind, but Node.js crashes with ERR_REQUIRE_ESM. This happens when a build tool outputs ESM syntax even though you targeted CJS.

2. Wrong condition ordering

{
  ".": {
    "import": "./dist/index.js",
    "types": "./dist/index.d.ts",
    "default": "./dist/index.js"
  }
}

"types" should be first. TypeScript resolves conditions top‑to‑bottom and stops at the first match. Here it matches "import" first, never sees "types", and falls back to inferring any for everything.

3. Missing declarations for subpaths

{
  "exports": {
    "./utils": "./dist/utils.js",
    "./hooks": "./dist/hooks.js"
  }
}

If dist/utils.d.ts (or dist/hooks.d.ts) doesn’t exist, consumers importing your-package/utils get any or “cannot find module.” Your own tests may pass because they import the source files directly, so you won’t notice until someone reports it.

The tool

I built tspub to catch these problems. It checks ~70 rules across exports, types, files, metadata, imports, and package size.

$ npx @tspub-dev/tspub check

exports/format-mismatch    ./dist/cjs/index.cjs contains ESM syntax
exports/types-first        "types" should be first in conditions
types/no-any-export        ./dist/index.d.ts exports 12 `any` types

3 problems (1 auto-fixable)

Notable rules

  • exports/cjs-esmodule-interop – detects CJS files using the __esModule flag that behave differently in Webpack, Node, and esbuild.
  • types/no-any-export – flags declaration files where exported types contain excessive any (usually a build‑tool misconfiguration).
  • exports/format-mismatch – reads file contents to verify format, not just the extension.
  • files/sensitive – catches .env, private keys, or credentials accidentally included in the published package.

Auto‑fix

$ npx @tspub-dev/tspub check --fix

This safely rewrites condition ordering and applies other trivial fixes.

Check any package without installing

A web version is available: tspub.dev/check/YOUR-PACKAGE lets you validate any npm package instantly.

Beyond checking

tspub also handles building, type‑declaration testing, scaffolding new packages, and publishing.

tspub init          # scaffold a correctly‑configured package
tspub build         # ESM + CJS + .d.ts generation
tspub check         # 70‑rule validation
tspub test-types    # type‑level test runner
tspub publish       # npm + GitHub releases

I built it as a single tool because juggling separate configs for building, linting, type testing, and publishing across many packages became unsustainable. The checking works standalone even if you don’t use the other features.


GitHub
npm: npm i -D @tspub-dev/tspub
Web: https://tspub.dev/

0 views
Back to Blog

Related posts

Read more »

Add`go fix` to Your CI Pipeline

Introduction Most Go programmers have never invoked go fix in their CI pipeline. It’s been a dormant command for over a decade, originally designed for pre‑Go...