Tools
Tools: The packaging bugs I kept shipping (and the tool I built to stop)
2026-02-18
0 views
admin
What goes wrong ## 1. CJS/ESM format mismatch ## 2. Wrong condition ordering ## 3. Missing declarations for subpaths ## The tool ## Check any package without installing ## Beyond checking I've published TypeScript packages that passed all my tests, built cleanly, and had types resolving perfectly in my editor then broke for consumers. The bug was always in the exports field. A require condition pointing to an ESM file. The "types" key in the wrong position. A subpath missing its .d.ts entirely. These failures are silent until someone files an issue. After debugging the same class of problem for the third time, I decided to automate the checks. Your package.json says "require": "./dist/cjs/index.cjs". But that file contains import and export statements it's actually ESM. Works fine in Webpack and esbuild (they don't care). Crashes in Node.js with ERR_REQUIRE_ESM. This happens because build tools sometimes output ESM syntax even when targeting CJS, especially with certain configurations. You'd never know unless you manually read the output file. Spot the bug? "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 types from the JS file everything becomes any. This one has bitten me multiple times because the exports field looks correct. Your main entry has index.d.ts. But your package exports 20 subpaths: If dist/utils.d.ts doesn't exist, TypeScript consumers importing from your-package/utils get any or "cannot find module." And you'll never know unless someone reports it, because your own project's tests use the source files directly. I built tspub to catch these. It checks 70 rules across exports, types, files, metadata, imports, and package size. Some rules I haven't seen in other tools: You can also auto fix certain issues: This handles safe fixes like condition reordering. I built a web version too: visit tspub.dev/check/YOUR-PACKAGE to check any npm package instantly. tspub also handles building (esbuild-based ESM/CJS + .d.ts), type declaration tests (.test-d.ts files), scaffolding new packages, and publishing to npm. I built it as one tool because managing separate configs for building, linting, type testing, and publishing across multiple packages was getting unsustainable for me. Your mileage may vary the checking works standalone even if you don't use the build or publish features. It's still early and I'd genuinely appreciate feedback: GitHub: link
npm: npm i -D @tspub-dev/tspub
Web: tspub.dev Templates let you quickly answer FAQs or store snippets for re-use. Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment's permalink. Hide child comments as well For further actions, you may consider blocking this person and/or reporting abuse CODE_BLOCK:
{ ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts", "default": "./dist/index.js" }
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
{ ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts", "default": "./dist/index.js" }
} CODE_BLOCK:
{ ".": { "import": "./dist/index.js", "types": "./dist/index.d.ts", "default": "./dist/index.js" }
} CODE_BLOCK:
{ "./utils": "./dist/utils.js", "./hooks": "./dist/hooks.js"
} Enter fullscreen mode Exit fullscreen mode CODE_BLOCK:
{ "./utils": "./dist/utils.js", "./hooks": "./dist/hooks.js"
} CODE_BLOCK:
{ "./utils": "./dist/utils.js", "./hooks": "./dist/hooks.js"
} COMMAND_BLOCK:
$ 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) Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
$ 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) COMMAND_BLOCK:
$ 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) COMMAND_BLOCK:
$ npx @tspub-dev/tspub check --fix Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
$ npx @tspub-dev/tspub check --fix COMMAND_BLOCK:
$ npx @tspub-dev/tspub check --fix COMMAND_BLOCK:
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 Enter fullscreen mode Exit fullscreen mode COMMAND_BLOCK:
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 COMMAND_BLOCK:
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 - exports/cjs-esmodule-interop detects CJS files using the __esModule flag that causes different behavior across Webpack, Node, and esbuild
- types/no-any-export flags declaration files where your exported types contain excessive any (usually a build tool misconfiguration, not intentional)
- exports/format-mismatch actually reads file contents to verify format, not just the extension
- files/sensitive catches .env, private keys, or credentials accidentally included in the published package - Does it catch real issues in your packages?
- False positives?
- Missing rules?
how-totutorialguidedev.toainodegitgithub