Skip to content

Repository files navigation

asinstalled

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 files quietly leaves out of the tarball;
  • a feature newer than the Node version engines claims 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.

Install

npm install --save-dev asinstalled

Requires Node 22 or newer to run; the package it checks can declare any floor. macOS and Linux.

Commands

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 build

Exit status: 0 when every check passed, 1 when any failed, 2 for a usage or setup error. Every command takes --dir <path> and --json.

check

  1. Packs the package exactly as npm publish would, prepack included.
  2. Confirms every file named in exports, main and bin is actually inside the tarball.
  3. Installs the tarball into an empty project.
  4. On each Node version under test, imports every declared entry point (by import or require, 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 the node on your PATH.

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.

docs

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.

published

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.

Security: what running asinstalled executes

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.

  • check runs npm pack, which runs the package's prepack and prepare scripts in your project directory.
  • It then runs npm install on the tarball in a temporary project. With npm's default configuration that runs the package's preinstall, install and postinstall scripts, and those of every dependency. (npm 11 warns about install scripts you have not approved, but runs them unless strict-allow-scripts is set.) asinstalled uses your npm and your npm configuration: if you enforce ignore-scripts or strict-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.
  • published downloads 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.

Use in CI

  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 docs

Where it sits

It complements, and does not replace:

  • publint and arethetypeswrong, which check your package.json and type declarations statically. asinstalled executes the installed package;
  • np and release-it, which run your release. asinstalled checks 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).

Limits, stated

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

Development

npm install
npm run check        # typecheck, lint and tests
npm run self-check   # asinstalled checks itself as installed, on its own engines floor

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

About

Test your npm package the way users receive it: pack it, install the tarball into a clean project, and run it on every Node version your engines field promises. Also checks docs against package.json, and the published artifact against the build.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages