Skip to content

Repository files navigation

PackSmoke

Test the npm package users will actually install, not just the code in your repository.

CI npm version license: MIT node >=22

The Problem

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.

How PackSmoke Works

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

Quick Start

# Try it instantly — no installation required
npx --yes packsmoke .

# JSON output for CI
npx --yes packsmoke . --json

# Skip consumer installation
npx --yes packsmoke . --no-install

Or install globally:

npm install --global packsmoke
packsmoke .

Requirements

  • 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.

What Gets Checked

Pack Artifact

  • Real npm pack --json --ignore-scripts execution
  • Packed file count and structure

Entry Points

  • main field target exists in packed artifact
  • types/typings field 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 Validation

  • 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

Dependency Hygiene

  • file: protocol dependencies flagged as blockers
  • Self-referencing file: dependencies flagged as blockers
  • link: and workspace: protocols produce warnings

Sensitive Files

  • .env, .env.local, .env.production, etc.
  • .npmrc (defensive check)
  • id_rsa, id_ed25519, id_dsa
  • *.pem, *.key files
  • Checks are filename/path based only — no secret-content scanning

Clean Consumer Installation

  • Tarball installed into a fresh temporary directory
  • Installed package metadata verified
  • npm bin shims verified (including Windows .cmd shims)

Commands

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

Pretty Output

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

Failure Example

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 Output

--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"
}

Exit Codes

Code Meaning
0 All checks passed (warnings are OK)
1 One or more blockers found
2 CLI/configuration/internal error

Why PackSmoke?

Repository correctness != published artifact correctness.

Common packaging failures that repository tests miss:

  • dist/ is absent from the npm tarball
  • bin points at a file that wasn't packed
  • types points 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

Where PackSmoke Fits

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.

Safety Model

Requirements: npm >= 11

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.

Lifecycle Scripts Disabled

PackSmoke runs npm pack --ignore-scripts and npm install --ignore-scripts. This means:

  • Pack validates the artifact from its current filesystem state
  • If your prepack script runs tsc to 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.

Bin Shims

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.

Subprocesses

PackSmoke invokes npm safely and without a shell. It resolves the npm CLI JavaScript entry point by checking:

  1. npm_execpath environment variable (set by npm when running scripts)
  2. Bundled npm-cli.js adjacent to the running Node.js executable
  3. On POSIX only: direct npm command as a last resort

It then runs node <npm-cli.js> ... with shell: false. This avoids:

  • DEP0190 deprecation warnings (Node.js >= 22)
  • Shell injection through paths containing spaces or shell metacharacters
  • On Windows, no fallback to npm.cmd execution — fails cleanly if no safe resolution is found

PackSmoke runs these external commands:

  • npm pack --json --ignore-scripts
  • npm install --ignore-scripts --no-audit --no-fund --bin-links=true

It does not:

  • Run npm publish
  • Execute installed binaries
  • Run lifecycle scripts

Temporary Files

  • Tarballs created in OS temp directories
  • Consumer directories created in OS temp directories
  • All PackSmoke-owned temp files cleaned up in finally blocks
  • Uses process.exitCode (not process.exit()) to ensure cleanup always runs

CI Usage

- run: npx packsmoke . --json

Requires Node.js >= 22 and npm >= 11.

Limitations

  • Does not perform full exports condition 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)

Roadmap

  • Richer exports artifact existence checks
  • Additional package-manager / install environments
  • Workspace package selection
  • CI ergonomics improvements
  • Additional artifact contract checks

Complementary Tools

  • 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.

License

MIT

About

Test the npm package users will actually install, not just the code in your repository.

Topics

Resources

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages