Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Install

Shell (macOS and Linux)

curl -fsSL https://github.com/mahkassem/fairlead/releases/latest/download/fairlead-installer.sh | sh

PowerShell (Windows)

powershell -c "irm https://github.com/mahkassem/fairlead/releases/latest/download/fairlead-installer.ps1 | iex"

npm, bun or pnpm

bun add -d fairlead

The package pins the version in package.json, so everyone on the project runs the same Fairlead.

The package downloads its binary when it installs. Bun runs no install script it doesn’t trust, so there the first fairlead command downloads it instead; add "trustedDependencies": ["fairlead"] to package.json to have bun install do it. Since 0.12.0: two first runs at once, such as parallel git hook jobs, no longer race on that download: each extracts on its own and the first to finish is used (#211).

Check it

fairlead --version
fairlead doctor

doctor prints the version, the platform and the config file Fairlead would use from the current directory. doctor --score rates how ready the repository is, out of 100, and names the fixes worth the most (Readiness score).

A first config: fairlead init

fairlead init             # write fairlead.toml from what the repository shows
fairlead init --dry-run   # print it instead
fairlead init --force     # replace a fairlead.toml that's already there
fairlead init --pack next # also extend a framework pack (since 0.11.0)

init reads the repository’s manifests, lockfiles and test files, and writes a fairlead.toml with a [[tests.runners]] entry for each runner it finds, each with a comment saying where it came from. A runner goes in only when its match finds a test file.

It findsIt writes
vitest, jest, ava or mocha in package.json, or bun test or node --test in the test scripts, one npm run step deepthat runner, through the lockfile’s package runner (bun x, pnpm exec, yarn, npx), on the standard test-file names, ava.files for AVA, and any other file that imports the runner (from "vitest"), such as a test.ts beside each function
go.mod at the rootgo test {packages} on **/*_test.go
pyproject.toml, setup.py, setup.cfg, pytest.ini or tox.inipytest on **/test_*.py and **/*_test.py, through uv run or poetry run when their lockfile is there
composer.jsonphp artisan test, Pest or PHPUnit on the test directories phpunit.xml or phpunit.xml.dist names, else tests/**/*Test.php
Cargo.toml, gradlew, pom.xml, a .csproj, or a Gemfile with spec/ (RSpec) or test/*_test.rb (minitest)a [[checks]] entry that runs the whole suite on any change in that language, which the import graph doesn’t read yet

[tests] match is written as { replace = [...] }, the runners’ patterns and nothing else, so a test file no runner here covers (a JavaScript test in a Python project, say) stays out of the plan instead of stopping it. It then loads and validates what it wrote, and prints the test files each runner matched. What it can’t tell, such as a runner’s options in scripts.test or a Go module below the root, it prints as a note. It never replaces a config without --force. When the manifests show a framework with a pack, Laravel or Next.js, it names the pack and writes it only with --pack.