# tapjs: a repository that cannot install itself until it bootstraps

> tapjs/tapjs is the development workspace for a Node test runner built as a plugin system, and its README is mostly a map of the plugins. The parts worth reading are the ones where the project explains what it cannot do normally, like install before building itself or test itself.

**tapjs/tapjs** — Test Anything Protocol tools for node

- Repository: https://github.com/tapjs/tapjs
- Website: https://node-tap.org/
- Stars: 2,426 · Forks: 283
- Language: JavaScript
- License: NOASSERTION
- Published: 2026-09-28 · Updated: 2026-09-28 · Language: en
- Canonical page: https://hysenlabs.com/projects/tapjs-tapjs

## The repository is a workspace, not the package

The readme's first line says this is the workspace for the Node implementation of the test-anything protocol, and the manifest agrees: it is named for the workspace, marked private, and declares its packages by a single wildcard over one source directory, with the module type set to modern. The releases, meanwhile, are tagged with the name of the runner and its version, the newest at 21.8.0 published the same minute as the last recorded commit on the branch. So what ships to a user is assembled from here, and what a contributor sees is the assembly. The top-level listing makes that concrete: one source tree holding every package, a task-runner configuration and its ignore file, a lock file, and a changelog outside the release tags, which means the changelog is maintained by hand rather than generated at release time. The same listing holds two prettier configuration files, which is one more than most repositories need and one more than this one needs.

## Install will not work until you bootstrap, and the reason is given

The development command list opens with one instruction and one parenthetical that explains it more honestly than most projects manage:

```
npm run bootstrap
```

The parenthetical says a plain install will not work until you have done this, because the generated TypeScript eats its own tail. That is a self-referential build: the test class is generated from a set of plugins, and the plugins type-check against the generated class. The rest of the list follows the cycle. A build step is required after any plugin or core change, per-package builds go through a prepare command scoped to one workspace, adding or removing a workspace means re-running the install, and there are separate commands for running every test in every workspace, for running them while writing snapshots, and for building and serving the documentation. One of those per-package commands carries a literal brace placeholder that relies on the shell expanding it, which is a small trap for anyone copying it into a script.

## A library that cannot test itself lives in its own repository

The contents map lists a dozen packages, and one of them has a note attached that is the best sentence in the document. The process information package, which tracks process metadata and code coverage, is hosted outside the monorepo, and the reason given is that it cannot be tested by a version of the runner that uses itself without bootstrap paradoxes. So the coverage measurement tool lives in its own repository precisely because a test runner cannot be its own first customer. Everything else stays inside. The entry module sets up the root runner and exposes an alias to the command-line one, a parser handles the wire format itself, a configuration package handles config files, argument parsing, environment variables and validation, and a comparison library does the matching and formatting that the assertion methods lean on heavily. A thin YAML wrapper exists so that JavaScript values are represented consistently inside YAML diagnostics.

## Type checking is disabled here on purpose, and only here

The closing section of the readme explains the last piece of the cycle. You bootstrap once to build the test class with the default plugin set, and then the other libraries can build properly, unless the build script or the default plugin set changes, in which case you bootstrap again. Because the core package, the generated test class and every plugin are mutually dependent, the readme says in capitals that they must set the skip-lib-check option in their TypeScript configuration, and immediately adds that it should not be used in other packages. That is a correct and narrow instruction, and it is also the kind of setting that escapes a monorepo through copy and paste. The whole paragraph is worth quoting rather than paraphrasing if you are setting up a generated-code cycle of your own, because it states the exception and its boundary in the same breath.

## Thirteen default plugins and four optional ones

The plugin architecture is the substance of the project and the readme maps it exhaustively. The default set covers lifecycle hooks before, before each, after and after each, where the after plugin is noted as adding both a teardown method and an after method that are now the same thing; process spawning and standard input; assertions such as equality and pattern matching; snapshot matching; test-directory fixtures; module mocking for both requires and imports; interception and capture, described in the readme's own words as a very scaled-down minimal form of a well-known mocking library, with a pointer to the optional plugin if you want more; and filtering, which adds a only marker plus two command-line options. Four more are optional: network mocking, a controllable clock, a sandbox that restores itself at the end of a test, and a TypeScript loader that replaces the usual runtime compiler.

## A dependency forked for one pull request, and a tarball at the root

Two entries in the manifest describe how this project handles dependencies that are not moving fast enough. One is a fork of a Node runtime loader named after the pull request that introduced it, pinned at a specific patch level, which is a fingerprint left by a fix that could not be released upstream. The other is a build tool dependency pinned at a single commit. Those two sit in a list that also contains an async-hook domain helper, a promise-identity check, a console-patching module and a distribution-manifest reader, all of which are the kind of small utility you end up owning. In the top-level listing, the documentation generator arrives as a committed archive with its version in the filename, alongside three configuration files and a stylesheet, so the pinned documentation toolchain is reproducible at the cost of a binary in version control.

## A documentation site generator inside a test-runner workspace

The command that builds and serves the documentation is one line, and the dependency list behind it is a small static-site stack: a static site generator in its second major version, its navigation and syntax-highlighting plugins, a table-of-contents plugin, a templating engine, a markdown parser with anchor support, and a search library that indexes the built site after the fact. There is a React-based terminal interface library and its testing library in the same list, which tells you the command-line runner has an interactive interface worth snapshotting, and a coverage tool wired to the same suite. So the workspace builds three things with one command set: the runner and its plugins, the documentation for the plugin API, and a search index over that documentation. For a project whose own readme is a directory listing, having all three is what keeps the plugin surface navigable.

## Conclusion

This repository is the right thing to read if you want to understand how a test runner becomes a platform, because the plugin split here is unusually clean: lifecycle hooks, assertions, snapshots, mocking, interception and filtering are all separate packages that add methods to one object. Two things to know before you clone it. A plain install will not work until you run the bootstrap once, because the generated TypeScript depends on the build it produces, and the readme tells you so in those words. And type checking is switched off for the workspace's own libraries specifically to break that cycle, which is a reasonable trade inside the repository and a trap if you copy the setting into a package of your own.

## FAQ

### What is the tapjs repository?

The development workspace for the Node implementation of the test-anything protocol. It is a private monorepo whose packages live under one source directory, and the releases are tagged with the runner's package name and version, the newest at 21.8.0.

### Why does a plain install fail in tapjs?

The readme says a plain install will not work until you run the bootstrap command once, because the generated TypeScript eats its own tail. The generated test class and the plugins that type-check against it are mutually dependent, so one has to be built first.

### Which plugins ship with tapjs by default?

Thirteen: TypeScript support, the four lifecycle hooks, process spawning, standard input, assertions, snapshots, test-directory fixtures, module mocking, interception and capture, and filtering with a only marker and two command-line options. Four more are optional, covering network mocking, a controllable clock, a self-restoring mocking sandbox and an alternative TypeScript loader.

### Why is one tapjs package hosted outside the monorepo?

The process information package, which tracks process metadata and code coverage, lives in its own repository because the readme says it cannot be tested by a version of the runner that uses itself without bootstrap paradoxes.

### Does tapjs disable TypeScript checking?

Inside the workspace, yes, and only there. Because the core package, the generated test class and every plugin are mutually dependent, the readme requires them to set the skip-lib-check option and says explicitly that it should not be used in other packages.

## Sources

- [Issues](https://github.com/tapjs/tapjs/issues)
- [Project website](https://node-tap.org/)
- [README](https://github.com/tapjs/tapjs/blob/main/README.md)
- [Releases](https://github.com/tapjs/tapjs/releases)
- [tapjs/tapjs on GitHub](https://github.com/tapjs/tapjs)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/tapjs-tapjs
