Test the npm package users will actually install, not just the code in your repository.
You run npm publish and everything looks fine — until a user reports that your bin entry doesn't work, your types field points to a missing file, or your tarball ships a .env file with secrets. You tested your code, but you never tested the package artifact.
PackSmoke answers: "When a user runs npm install your-package, will it actually work?"
project
↓
npm pack
↓
real .tgz
↓
inspect
↓
clean npm install
↓
verify package contract
# Try it instantly — no installation required
npx --yes packsmoke .
# JSON output for CI
npx --yes packsmoke . --json
# Skip consumer installation
npx --yes packsmoke . --no-installOr install globally:
npm install --global packsmoke
packsmoke .- Node.js >= 22
- npm >= 11
PackSmoke requires npm >= 11 because npm 10 does not reliably suppress lifecycle scripts during npm pack --ignore-scripts. PackSmoke fails closed when it detects an older npm version.
- Real
npm pack --json --ignore-scriptsexecution - Packed file count and structure
mainfield target exists in packed artifacttypes/typingsfield target exists in packed artifact- README presence (warns if source has one but it's not packed)
- LICENSE presence (warns if source has one but it's not packed)
- Bin target files exist in packed artifact
- JavaScript bin files have valid Node shebang (
#!/usr/bin/env node) - Supports both string-form and object-form
bin
file:protocol dependencies flagged as blockers- Self-referencing
file:dependencies flagged as blockers link:andworkspace:protocols produce warnings
.env,.env.local,.env.production, etc..npmrc(defensive check)id_rsa,id_ed25519,id_dsa*.pem,*.keyfiles- Checks are filename/path based only — no secret-content scanning
- Tarball installed into a fresh temporary directory
- Installed package metadata verified
- npm bin shims verified (including Windows
.cmdshims)
| Command | Description |
|---|---|
packsmoke <dir> |
Run all checks against the target directory |
packsmoke check <dir> |
Alias for the default command |
packsmoke <dir> --json |
Output machine-readable JSON |
packsmoke <dir> --no-install |
Skip clean consumer installation |
PackSmoke
Package: [email protected]
PACK
✓ 12 files
ENTRY POINTS
✓ main -> dist/index.js
✓ types -> dist/index.d.ts
BIN
✓ bin "cli" -> dist/cli.js
✓ bin "cli" has valid Node shebang
HYGIENE
✓ no sensitive packed files
INSTALL
✓ clean consumer installation succeeded
✓ installed package metadata matches ([email protected])
✓ npm bin shim "cli" exists (.cmd)
Result: PASSED
PackSmoke
Package: [email protected]
PACK
✓ 8 files
ENTRY POINTS
✓ main -> dist/index.js
BIN
✗ bin "foo" -> dist/foo.js is missing from packed artifact
INSTALL
✓ clean consumer installation succeeded
✗ npm bin shim "foo" is missing
Result: FAILED
1 blocker
--json produces valid JSON on stdout with no ANSI, no banners, no log chatter:
{
"package": {
"name": "my-package",
"version": "1.0.0"
},
"tarball": {
"filename": "",
"files": 0,
"size": 0
},
"checks": [
{ "id": "manifest.load", "category": "MANIFEST", "status": "pass", "message": "loaded [email protected]" },
{ "id": "pack.create", "category": "PACK", "status": "pass", "message": "12 files" },
{ "id": "entry.main.exists", "category": "ENTRY POINTS", "status": "pass", "message": "main -> ./dist/index.js" },
{ "id": "install.success", "category": "INSTALL", "status": "pass", "message": "clean consumer installation succeeded" }
],
"summary": { "passed": 12, "warnings": 0, "failed": 0, "skipped": 0 },
"result": "pass"
}| Code | Meaning |
|---|---|
0 |
All checks passed (warnings are OK) |
1 |
One or more blockers found |
2 |
CLI/configuration/internal error |
Repository correctness != published artifact correctness.
Common packaging failures that repository tests miss:
dist/is absent from the npm tarballbinpoints at a file that wasn't packedtypespoints at a missing declaration- npm installs the tarball but doesn't create the expected shim
- a local
file:dependency leaks into release metadata - a sensitive filename is accidentally packed
Use PackSmoke when you want to know whether the actual npm tarball installs and exposes the files/bins users expect.
Use publint when you want deeper package metadata and module compatibility analysis.
Using both can make sense.
PackSmoke requires npm >= 11 at runtime. npm 10 does not reliably suppress lifecycle scripts (including prepare) during npm pack --ignore-scripts. PackSmoke checks the npm version before packing and exits with code 2 if npm < 11 is detected.
PackSmoke runs npm pack --ignore-scripts and npm install --ignore-scripts. This means:
- Pack validates the artifact from its current filesystem state
- If your
prepackscript runstscto build, those files need to exist before PackSmoke runs - This is intentional: PackSmoke catches packaging mistakes, not build issues
- npm normally excludes
.npmrc,package-lock.json, and other config files automatically; PackSmoke does not add them back
PackSmoke does not automatically run build or prepack scripts. Run your build step before PackSmoke if your package requires one.
PackSmoke verifies that npm-created bin shims exist after installation. On Windows, it checks for .cmd shims. It does not execute installed binaries — it only verifies that the shims were created correctly by npm.
PackSmoke invokes npm safely and without a shell. It resolves the npm CLI JavaScript entry point by checking:
npm_execpathenvironment variable (set by npm when running scripts)- Bundled
npm-cli.jsadjacent to the running Node.js executable - On POSIX only: direct
npmcommand as a last resort
It then runs node <npm-cli.js> ... with shell: false. This avoids:
DEP0190deprecation warnings (Node.js >= 22)- Shell injection through paths containing spaces or shell metacharacters
- On Windows, no fallback to
npm.cmdexecution — fails cleanly if no safe resolution is found
PackSmoke runs these external commands:
npm pack --json --ignore-scriptsnpm install --ignore-scripts --no-audit --no-fund --bin-links=true
It does not:
- Run
npm publish - Execute installed binaries
- Run lifecycle scripts
- Tarballs created in OS temp directories
- Consumer directories created in OS temp directories
- All PackSmoke-owned temp files cleaned up in
finallyblocks - Uses
process.exitCode(notprocess.exit()) to ensure cleanup always runs
- run: npx packsmoke . --jsonRequires Node.js >= 22 and npm >= 11.
- Does not perform full
exportscondition analysis (use publint for that) - Does not validate ESM/CJS compatibility across environments
- Does not scan file contents for secrets (only filenames)
- Does not execute installed binaries
- Requires npm >= 11 (see Safety Model)
- Richer
exportsartifact existence checks - Additional package-manager / install environments
- Workspace package selection
- CI ergonomics improvements
- Additional artifact contract checks
- publint — Focuses on package metadata correctness and compatibility across modern module environments. Excellent for linting your package.json and exports configuration.
- PackSmoke — Focuses on the physical packed artifact and clean-consumer installation contract. Verifies what actually gets shipped and whether it works when installed.
These tools are complementary, not competitors.
MIT