Offline · read-only · CI-ready
Catch toolchain drift before you push.
lockstep cross-checks every place a Node, Bun, or package-manager version is declared — package.json, .nvmrc, asdf and mise files, the committed lockfile, and every GitHub Actions workflow — then reports the conflicts in one pass.
bunx github:srivtx/lockstep#main
Runs with Bun, with no install step. It is not on npm; bun add -g github:srivtx/lockstep installs the lockstep binary instead.
$ lockstep fixtures/driftedfixtures/drifted errors:2 warnings:2 info:1node .github/workflows/ci.yml:setup-node 20node .node-version 18.19.0node .nvmrc v20.11.0node engines.node >=18packageManager packageManager pnpm@9.12.0ERROR LOCK-001 package.json:engines.node Node version sources disagree across majors: Node 18 (.node-version=18.19.0, engines.node=>=18); Node 20 (.github/workflows/ci.yml:setup-node=20, .nvmrc=v20.11.0).ERROR LOCK-002 package.json:packageManager packageManager declares "pnpm" but the committed lockfile(s) belong to npm (package-lock.json).WARNING LOCK-004 package-lock.json A lockfile is committed but no CI install step is frozen (use npm ci, --frozen-lockfile, or --immutable).WARNING LOCK-006 package.json:packageManager packageManager declares "pnpm" but no workflow enables corepack or pins the package-manager version.INFO LOCK-007 package.json:engines.node engines.node (>=18) does not match the pinned CI Node version(s) 20.
What it checks
Every source that pins a runtime or a package manager, read directly from the project. Missing files are simply skipped, and nothing is ever written back.
engines- The
engines.nodeandengines.bunranges inpackage.json— the versions the project claims to support. packageManager- The manager and exact version Corepack reads from
package.json, such aspnpm@9.12.0. volta- The
volta.nodeandvolta.bunpins used by the Volta toolchain. .nvmrc- The Node version nvm installs for the directory.
.node-version- The Node version read by nodenv and asdf-compatible tools.
.tool-versions- Per-tool version pins for asdf.
mise.toml- Node and Bun versions declared for mise.
- Lockfiles
package-lock.json,yarn.lock,pnpm-lock.yaml,bun.lock, andbun.lockb, which reveal the manager actually in use.- GitHub Actions
- Every
.github/workflows/*.yml, includingsetup-node,setup-bun, andpnpm/action-setuppins and whether installs are frozen.
Quick start
Point it at one project. The summary comes first, then each declared source, then the findings. The exit code is the gate.
# run it without installing$ bunx github:srivtx/lockstep#main# audit another project$ lockstep --dir path/to/project# machine-readable output, and fail CI on warnings$ lockstep --json --fail-on warning
Every flag, the exit-code scheme, and JSON output are in Usage.
Rule explorer
Ten stable codes. Each finding carries one, a severity, and the file it points at.
LOCK-001LOCK-002packageManager conflicts with the committed lockfile.LOCK-003LOCK-004LOCK-005LOCK-006packageManager declared but CI never enables corepack.LOCK-007engines.node range does not include the CI-pinned Node.LOCK-008.nvmrc and .node-version differ only cosmetically.LOCK-009bun-version major disagrees with engines.bun.LOCK-010How each rule works, including the exact conditions that trigger it.
Documentation
The landing page is the pitch. The details live on four short pages.
- Rules
- The ten checks, the sources they read, and how a run is assembled.
- Usage
- Every CLI flag, the exit-code scheme, JSON output, and the library API.
- CI
- SARIF 2.1.0 and GitHub code scanning, with a workflow you can paste.
- FAQ
- Scope, limitations, and how lockstep compares to corepack, Volta, and Renovate.