Imported from uhop/tape-six-proc (
AGENTS.md). Install upstream withnpx skills add uhop/tape-six-proc. Copyright stays with the author.
AGENTS.md — tape-six-proc
tape-six-procis a helper for tape-six that runs test files in separate processes (subprocesses) instead of worker threads. It works with Node, Deno, and Bun, and supports TypeScript natively without transpilation. The npm package name istape-six-procand the CLI command istape6-proc.
Setup
This project uses a git submodule (wiki):
git clone --recursive https://github.com/uhop/tape-six-proc.git
cd tape-six-proc
npm install
There is no build step.
Commands
- Install:
npm install - Test (Node):
npm test(runstape6-proc --flags FO) - Test (Bun):
npm run test:bun - Test (Deno):
npm run test:deno - Lint:
npm run lint(Prettier check) - Lint fix:
npm run lint:fix(Prettier write) - JS-check:
npm run js-check(TypeScript-as-linter viatsconfig.check.json— checks.jssources for unused vars / undeclared refs)
Project structure
tape-six-proc/
├── package.json # Package config; "tape6" section configures test discovery
├── tsconfig.check.json # js-check config (TypeScript as linter for .js sources)
├── bin/
│ ├── tape6-proc.js # CLI entry point (--self flag or delegates to tape6-proc-node.js)
│ └── tape6-proc-node.js # Main CLI: delegates to tape-six config utilities, runs TestWorker
├── src/ # each module is a .js + .d.ts sidecar pair (types + JSDoc live in the .d.ts)
│ ├── TestWorker.js # TestWorker class: spawns child processes, pipes stdout/stderr
│ └── streams/
│ ├── lines.js # TransformStream: splits text into lines
│ ├── parse-prefixed-jsonl.js # TransformStream: parses prefixed JSONL from stdout
│ └── wrap-lines.js # TransformStream: wraps plain lines as {type, name} objects
├── tests/ # Test files (test-*.js)
│ └── manual/ # Manual test files (hand-runnable demos; `tests/manual/test-chai.js` requires user-installed chai)
├── wiki/ # GitHub wiki documentation (submodule)
├── .github/
│ ├── workflows/ # CI: separate jobs for Node × {ubuntu, windows, macOS} × {22, 24, 26}, Bun × OS, Deno × OS
│ └── dependabot.yml # Tuned: grouped updates + `versioning-strategy: increase-if-necessary` (PRs only on out-of-range bumps)
├── ARCHITECTURE.md # Internal layout & control flow (authoring-side doc)
├── README.md
└── LICENSE
Code style
- ES modules throughout (
"type": "module"in package.json). - No transpilation — code runs directly in all target runtimes. TypeScript test files (
.ts) are also supported natively by modern Node, Deno, and Bun. - Typed via
.d.tssidecars — everysrc/module is a.js+.d.tspair. The.d.tscarries the types and JSDoc as the sole source of truth (.jsfiles carry no JSDoc); each.jsstarts with a// @ts-self-types="./<name>.d.ts"directive (honored by Deno; TypeScript resolves sidecars by adjacency). A newsrc/module needs both files.package.json#typespoints atsrc/TestWorker.d.ts. - Prettier for formatting (see
.prettierrc). - Semicolons — default Prettier behavior (see
.prettierrc). - Imports at the top of files, using
importsyntax. - The package name is
tape-six-procbut the CLI command istape6-proc.
Architecture
bin/tape6-proc.jsis the CLI entry point. With--selfit prints its own path (for cross-runtime usage). Otherwise it delegates tobin/tape6-proc-node.js.bin/tape6-proc-node.jsdelegates argument parsing, reporter setup, and file resolution totape-six/utils/config.js(getOptions,initReporter,initFiles,showInfo). It adds--runFileArgs(-r),--info,--help(-h), and--version(-v) options, then runs tests viaTestWorker.TestWorker(insrc/TestWorker.js) extendsEventServerfromtape-six. It spawns each test file as a child process using dollar-shell, pipes stdout through a JSONL parser, pipes stderr as wrapped lines, and drives a stdin control channel.- Each spawned process gets environment variables:
TAPE6_FLAGS,TAPE6_TEST,TAPE6_TEST_FILE_NAME,TAPE6_JSONL=Y,TAPE6_JSONL_PREFIX(a UUID prefix for JSONL lines),TAPE6_CONTROL=Y(marks a controlled child), andTAPE6_GRACE_TIMEOUT(the drain budget). - Stream pipeline per process:
stdout → TextDecoder → lines → parse-prefixed-jsonl → report. stderr:stderr → TextDecoder → lines → wrap-lines → report. stdin carries the control plane (see below). - Worker control channel. The child is spawned with
stdin: 'pipe'. To stop a worker the parent writes a line-delimitedterminatecommand and EOFs stdin; the child (the tape-six runtime, which opens the channel whenTAPE6_CONTROLis set) drains a running test throughreporter.terminate()— itst.signalfires and cleanup hooks run — then exits. This makesfailOnce(flagO) actually stop in-flight workers (not just stop scheduling new files), and enables a per-worker wall-clock deadline (TAPE6_WORKER_TIMEOUT). Completion is keyed off reading the child's top-levelend, not racing the child's own exit — which also fixes the Bun stdout-flush bug, since the child now exits parent-driven afterendhas been consumed. A child that won't drain withinTAPE6_GRACE_TIMEOUT(default 5000 ms) is force-killed (SIGTERM); a premature exit with noendis still reported as an error. The child-side listener lives intape-sixitself (src/utils/control-channel.js), so this requires atape-sixthat ships it.
Dependencies
tape-six— the core test library.tape-six-procimports:utils/config.js(getOptions,initFiles,initReporter,showInfo,printFlagOptions),test.js,utils/timer.js,State.js,utils/EventServer.js,utils/makeDeferred.js. The worker control channel also relies on tape-six's child-side listener (src/utils/control-channel.js, opened viaTAPE6_CONTROL) and itsgetGraceTimeout/getWorkerTimeoutconfig — so it needs atape-sixversion that ships them.dollar-shell— cross-runtime process spawning (spawn,currentExecPath,runFileArgs).
Writing tests
Tests are standard tape-six tests. They are run in isolated processes by tape6-proc:
import test from 'tape-six';
test('example', t => {
t.ok(true, 'truthy');
t.equal(1 + 1, 2, 'math works');
});
- Test files should be directly executable:
node tests/test-foo.jsornode tests/test-foo.ts - Test file naming convention:
test-*.js,test-*.mjs,test-*.cjs,test-*.ts - Tests are configured in
package.jsonunder the"tape6"section (same astape-six).
Key conventions
- Do not add dependencies unless absolutely necessary.
- Do not modify or delete test expectations without understanding why they changed.
- No comments that narrate the code. Don't write a comment that restates what the code does. Allowed, each as the shortest possible marker: JSDoc when requested or required; a reference for a non-trivial algorithm; a non-trivial decision or constraint — why it's this way, including footgun/ordering caveats that have a real reason. The bar is why, never what. Strip narrating comments opportunistically in files you're already editing.
- The
--selfflag prints the path totape6-proc.jsfor use in cross-runtime scripts (Bun, Deno). - The
--runFileArgs(-r) flag passes extra arguments to the spawned interpreter (mainly for Deno permissions). - Wiki documentation lives in the
wiki/submodule. - Environment variables use the
TAPE6_prefix (shared withtape-six). The control-channel tunables areTAPE6_GRACE_TIMEOUT(drain budget before force-kill, default 5000 ms) andTAPE6_WORKER_TIMEOUT(per-worker wall-clock deadline, default 0 = off); both are resolved by tape-six'sconfig.jsand inherited here viagetOptions. - Configuration is read from
tape6.jsonor the"tape6"section ofpackage.json(same astape-six). Per-runtime subsections (tape6.node/tape6.bun/tape6.deno/tape6.cli/tape6.browser) are auto-resolved via tape-six'sruntime.namedetection — pin a test file to a specific runtime by globbing it under that key. - BYO assertion / mock libraries. No third-party assertion lib ships as a devDep. CI smoke-tests for the
AssertionErrorrendering path usenode:assert(tests/test-assert.js).tests/manual/test-chai.jsis retained as a hand-runnable visual demo for users who want to see chai integration; usersnpm install chaiad-hoc when exercising it. js-checktooling:tsconfig.check.jsonruns TypeScript-as-linter (checkJs+noUnusedLocals+noUnusedParameters) over.jssources inbin/andsrc/; the.d.tssidecars join the same program via import adjacency, so they are validated too. tape-six'sEventServerOptionspass-through bags areany-indexed (since 1.14.1), so the keys the worker reads (flags,runFileArgs) type-check without a local augmentation andTestWorker.jsstays cast-free undercheckJs. Pure-Node-API only on the source side (@types/nodeis the only types entry); cross-runtime concerns are absorbed by thedollar-shelldependency and don't require@types/bun/@types/denohere.- Dependabot is tuned to skip PRs when a new version satisfies the declared caret range (only major bumps fire) and to bundle all matching updates per ecosystem into one PR per cycle. Security advisories are a separate, always-on channel and continue to fire regardless.