Test your npm package the way your users receive it: packed, installed into a clean project,
and run on every Node version your engines field promises.
The package you test is not the package your users install. You run tests against your source
tree. Your users get a tarball, filtered by files, resolved through exports and bin,
running on whatever Node version engines admits, often the oldest. Every failure below passes
the maintainer's own test suite:
- an entry point or binary that
filesquietly leaves out of the tarball; - a feature newer than the Node version
enginesclaims to support; - a binary without a
#!line, so it cannot run as a command; - a README that documents a script that no longer exists, or a
git clone <this repo>placeholder; - a published version whose content is not what the tagged source builds.
asinstalled catches each of them, from the consumer's side.
npm install --save-dev asinstalledRequires Node 22 or newer to run; the package it checks can declare any floor. macOS and Linux.
npx asinstalled check # pack, install, run on the engines floor and current Node
npx asinstalled check --run "mycli --help" # also run a binary, and require exit 0
npx asinstalled check --script smoke.mjs # also run your own smoke script inside the consumer
npx asinstalled docs # check the Markdown against package.json
npx asinstalled published # check the registry artifact against a fresh buildExit status: 0 when every check passed, 1 when any failed, 2 for a usage or setup error.
Every command takes --dir <path> and --json.
- Packs the package exactly as
npm publishwould,prepackincluded. - Confirms every file named in
exports,mainandbinis actually inside the tarball. - Installs the tarball into an empty project.
- On each Node version under test, imports every declared entry point (by
importorrequire, as the package declares) and runs the configured binaries and smoke script, all from the installed copy and all with that exact Node binary, never thenodeon yourPATH.
The engines floor. By default it tests the lowest version your engines.node range admits,
plus the Node you are running. The floor build is downloaded from nodejs.org, checked against
the SHA-256 in Node's published SHASUMS256.txt, and cached. A range whose floor it cannot
determine (>20, <22, lts) is refused with an error rather than guessed. Choose versions
explicitly with --node 20.11.1,22.0.0,current, or pass a path to a node binary.
Binaries are only executed when you say how (--run); an unknown CLI run with no arguments
might start a server. Without --run they are checked for presence and a #! line.
Checks every Markdown file for references that no longer match package.json:
npm run <script> and npm test naming scripts that do not exist, npx <package> when that
binary is not declared, git clone with a placeholder instead of a URL, relative links to
missing files, and a public package whose docs never show how to install it.
Only text a reader is told to run is checked: fenced code blocks and inline code. A sentence describing a removed script is history, not an instruction, and changelogs are checked for broken links only.
A deliberate exception, such as documentation that quotes a bad command as an example, is
marked where a reader can see it: <!-- asinstalled-ignore --> on the line, or
<!-- asinstalled-ignore-next-line --> above it. Suppressed lines are counted and listed in
the report.
It always reports how many references it checked, so a run that matched nothing cannot pass for a clean one.
For the version in package.json, after a release:
- exists: the exact version is live on the registry;
- identical: its content equals a fresh build of your checkout. The registry tarball is
first verified against the registry's own
integrity, then both sides are decompressed and compared. The gzip layer is deliberately ignored: the same tarball compresses to different bytes under different zlib builds, which says nothing about what users install; - provenance: a SLSA provenance attestation is attached, and which repository and workflow it names;
- tag: the repository has a
v<version>tag.
Provenance is reported as attached, not cryptographically verified. For signature
verification, run npm audit signatures in a project that installs the package.
asinstalled checks a package by doing what a consumer does, so it runs that package's code. Use it on packages you trust, which normally means your own.
checkrunsnpm pack, which runs the package'sprepackandpreparescripts in your project directory.- It then runs
npm installon the tarball in a temporary project. With npm's default configuration that runs the package'spreinstall,installandpostinstallscripts, and those of every dependency. (npm 11 warns about install scripts you have not approved, but runs them unlessstrict-allow-scriptsis set.) asinstalled uses your npm and your npm configuration: if you enforceignore-scriptsorstrict-allow-scripts, so does it, and it then models consumers who do the same. - It imports every entry point and runs the binaries and smoke script you configure. That is code execution, by design.
- Every child process inherits your environment, including any tokens in it. In CI, run
asinstalled in a job that holds no publishing credentials: not in a job with an npm token,
and not in one granted
id-token: write. publisheddownloads the registry tarball, then hashes and decompresses it. It does not run it.- Node builds are downloaded from nodejs.org and checked against Node's published SHA-256 list. The list's GPG signature is not checked.
It is not a sandbox, and it does not safely inspect untrusted or malicious packages. For those, use a disposable container or VM that holds no credentials, or a dedicated supply-chain scanner.
consumer-path:
runs-on: ubuntu-latest
permissions:
contents: read # no publishing credentials in this job
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
- run: npm ci
- run: npx asinstalled check --run "mycli --help"
- run: npx asinstalled docsIt complements, and does not replace:
- publint and
arethetypeswrong, which
check your
package.jsonand type declarations statically.asinstalledexecutes the installed package; - np and release-it,
which run your release.
asinstalledchecks what that release will deliver, and what it did; - a CI matrix over Node versions, which usually runs your source on each version. That
can fail where the shipped package works (a source tree that needs a newer runtime flag) and
pass where it breaks (a file missing from
files).
npm only (no Yarn, pnpm or Bun installs yet). macOS and Linux Node builds. The checksum list is fetched over HTTPS but its GPG signature is not verified. Native dependencies are installed with the current Node and may not match the floor. Executing arbitrary README code blocks is out of scope (doc-detective does that).
npm install
npm run check # typecheck, lint and tests
npm run self-check # asinstalled checks itself as installed, on its own engines floorDevelopment runs TypeScript directly through Node's type stripping, so it needs Node 22.6 or newer; the published package runs on Node 22.0 and newer.