vitest-command-line
v0.7.0
Published
Helpers and matchers for testing command-line tools with Vitest
Maintainers
Readme
vitest-command-line
Helpers and matchers for testing command-line tools with Vitest. vitest-command-line
gives you a small, typed API for running real subprocesses or injected wrapper
commands while capturing stdout, stderr, merged output, timing, and exit state in
one result object.
Benefits
- Test real CLIs with a simple
commandLine(...).run(...)API. - Capture
stdout,stderr, combined output, exit code, signal, timeout, and stream chunks in oneCommandResult. - Reuse
cwd,env,context, and timeout via options orwithOptions()for derived instances. - Kill stuck subprocesses reliably, including whole process trees when needed.
- Use built-in Vitest matchers like
toSucceed(),toHaveStdout(), andtoHaveTimedOut(). - Create disposable scratch directories and files for CLI fixtures and output assertions.
- Swap real subprocess execution for an injected wrapper runner when you want faster or more targeted tests.
Install
pnpm add -D vitest vitest-command-linevitest is a peer dependency because the matcher helpers extend Vitest's
expect.
Usage
This example runs a real CLI with defaults in one options object, uses
scratchDirectory() for temporary files, and the bundled custom matchers for
assertions.
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import {
commandLine,
extendMatchers,
scratchDirectory,
} from 'vitest-command-line';
extendMatchers();
describe('my-cli', () => {
const cli = commandLine({
command: ['node', './dist/cli.js'],
name: 'my-cli',
cwd: directory.path,
env: { FORCE_COLOR: '0' },
});
let directory = scratchDirectory();
beforeEach(async () => {
directory = scratchDirectory();
await directory.create();
});
afterEach(async () => {
await directory.remove();
});
it('writes a report file', async () => {
const reportFile = await directory.file('report.json');
const result = await cli.run(['build', '--format', 'json', '--output', reportFile.path], {
timeout: 5_000,
subprocessCleanup: 'process-tree',
});
expect(result).toSucceed();
expect(result).toHaveStdout(/build complete/i);
expect(reportFile).toHaveFileContents();
});Core API
commandLine({ command, name?, run?, cwd?, env?, ... })defines a command target; run-related keys are used as defaults for everyrun().command.run(args?, options?)runs the command and returns aCommandResult.command.withOptions(options?)returns a new command with additional or overridden run options (e.g.cwd,env,timeout).scratchDirectory()returns a disposableScratchDirectory. Callcreate()to materialize the directory on disk before usingfile(),files(),dir(), andremove().extendMatchers()installs custom Vitest matchers onexpect.
Local Development
pnpm install
pnpm build
pnpm test
pnpm test:coverage
pnpm lint
pnpm dev
pnpm make-releasepnpm build emits the publishable package to dist/. pnpm make-release builds,
stages the npm payload in publish/, copies dist, README.md, and LICENSE,
and then runs npm publish. For a non-publishing smoke test of the staged payload,
run node scripts/make-release.mjs . --dry-run.
Testing Notes
- The self-tests live in
src/*.test.tsand run with Vitest. - Some subprocess tests use Unix-style tools such as
/bin/echo,/bin/cat, and/bin/sh, so they currently assume a Unix-like environment.
License
MIT
Author
Created by Ben Houston and sponsored by Land of Assets.
