# Verify Before NPM Publish

> (project) Before publishing pangu, pack it and run the examples app against that tarball to prove the about-to-ship build is consumable by a real install

- Skill: `vinta/verify-before-npm-publish` (Agent Skill)
- Install (CLI): `npx skillmds@latest add vinta/verify-before-npm-publish`
- Raw SKILL.md: https://api.skillmd.com/api/skills/vinta/verify-before-npm-publish/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: vinta (https://skillmd.com/u/vinta)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/vinta/verify-before-npm-publish

---


# Verify before publish

The `examples/` app is a real consumer: its own `package.json` pins a published `pangu`, its own `tsconfig.json` resolves under `NodeNext`, and its checks reach pangu through `node_modules` and the `exports` map. That is exactly the path pangu's own build never takes, because the build resolves `src/` directly, sibling file to sibling file. So a `.d.ts` with a bare relative import, or a file the `files` list forgot to ship, reaches npm green.

This skill briefly repoints that consumer at the **freshly packed tarball** instead of its published pin, runs every example check against it, and restores the pin. The packed tarball is the only honest artifact. Repointing is also what lets the check run before publish at all: pangu's next version is not on the registry yet, so the pin cannot resolve until the tarball stands in for it.

## Run the verification

Everything below runs as one command so the `trap` restores `examples/package.json` whether the checks pass, fail, or error midway. Never split it.

The checks capture their exit code explicitly instead of leaning on `set -e` to abort. Some runners (Claude Code's Bash tool among them) invoke this in a shell where `set -e` does not stop the script, and a bare `echo "VERIFY OK"` after an unguarded `npm run verify` then prints a pass over a real failure. Report the `VERIFY OK` / `VERIFY FAILED` line, never the tool's own exit status.

```bash
set -eu

# Build and pack the exact bytes that would go to npm
npm run build
TARBALL_DIR="$(mktemp -d)"
npm pack --pack-destination "$TARBALL_DIR"
TGZ="$(ls "$TARBALL_DIR"/pangu-*.tgz)"

# Every file under dist/ lives in an entry folder. A file directly in dist/ is a code-split chunk that a build config change let back in
if tar -tzf "$TGZ" | grep -E '^package/dist/[^/]+$'; then echo "VERIFY FAILED (stray file in dist/)"; exit 1; fi

# From here the examples pin is modified, so guarantee its restoration on any exit
cp examples/package.json "$TARBALL_DIR/package.json.orig"
trap 'cp "$TARBALL_DIR/package.json.orig" examples/package.json; rm -rf "$TARBALL_DIR" examples/node_modules examples/package-lock.json' EXIT

# Repoint the consumer at the tarball, install, and run every example check against it
node -e "const fs=require('fs'),f='examples/package.json',p=JSON.parse(fs.readFileSync(f));p.dependencies.pangu='file:'+process.argv[1];fs.writeFileSync(f,JSON.stringify(p,null,2)+'\n')" "$TGZ"
npm install --prefix examples --no-audit --no-fund

rc=0
npm run verify --prefix examples || rc=$?
if [ "$rc" -eq 0 ]; then echo "VERIFY OK"; else echo "VERIFY FAILED (exit $rc)"; fi
exit "$rc"
```

## Reading the result

- `VERIFY OK` on the last line → the tarball is clear to publish.
- `VERIFY FAILED` → do not publish. The `trap` has already restored the pin and dropped the throwaway install; diagnose with the table below, fix in `src/`, and rerun.

Either way, confirm the pin came back before moving on: `git diff --quiet examples/package.json` must exit 0. A dirty `examples/package.json` means the run was interrupted before the trap fired; restore it with `git checkout -- examples/package.json`.

## What each failure means

| Symptom                          | Cause                                                                                    |
| -------------------------------- | ---------------------------------------------------------------------------------------- |
| `TS2834` in a `dist/**/*.d.ts`   | a relative import in `src/` is missing its `.js` extension                               |
| `TS2339` on an inherited method  | a base-class `.d.ts` import failed to resolve, collapsing the subclass to an error type  |
| `Cannot find module` at run time | `package.json` `files` doesn't ship the referenced file, or an `exports` path is wrong   |
| `stray file in dist/`            | a multi-entry `vite.config.ts` pass emitted a shared chunk; give each entry its own pass |

To read the shipped artifact directly, unpack the tarball: `tar -xzf "$TGZ" -C "$TARBALL_DIR"` exposes `package/dist/**/*.d.ts` and `package/package.json`.

## What the run exercises

`npm run verify` in `examples/` chains the runtime entrypoints (`verify:commonjs`, `verify:esm`, `verify:cli`), the browser page (`verify:browser`, which serves `verify-browser.html` and drives the UMD `<script>` tag and the ESM `import` in headless Chromium), and the type check (`typecheck`, whose `tsconfig.json` checks `verify-types.ts` under the `require` condition and `verify-types.mts` under the `import` condition, covering both branches of the dual-package `exports` map). Every one runs against the installed tarball, not the published pin.

