blast-radius
When you change a file, find out what else might break.
blast-radius is a fast CLI that traces every file that depends — directly or
transitively — on the code you're about to touch, and gives you a one-glance
risk verdict. Point it at a file and it answers the question every code review
asks: "how far does this change reach?"
MODERATE ██████████░░░░░░░░░░ 6 impacted files · 2 packages
3 direct, 3 indirect · depth 2 · 1 endpoint
── IMPACTED FILES · 6 IN 2 PACKAGES ──────────────────────
apps/storefront (3)
src/ (3)
App.tsx ◎ endpoint
LegacyButtonCard.jsx
PromoCard.tsx
packages/ui (3)
src/ (3)
Card.tsx
Toolbar.tsx
index.ts
Use it to:
- Gut-check a change before you start — is this a 2-file tweak or a 200-file ripple?
- Catch surprises in code review — surface the files a diff touches that aren't in the diff.
- Gate risky commits in CI or pre-commit hooks — fail the build when a change reaches too far.
It is built first and foremost for JavaScript and TypeScript repos (including
monorepos) — and that includes React: JSX/TSX is parsed natively, and JSX
component usage is tracked at the symbol level, so it can tell a file that
merely imports Button from one that actually renders <Button />. Vue and
Svelte script imports are supported too, with Python and Rust as beta adapters.
Quick start
Install via npm — no Rust toolchain required. The package pulls in a prebuilt binary for your platform (Linux x64/arm64, macOS x64/arm64, Windows x64) with all language features included:
The same prebuilt binaries are attached to each GitHub Release if you'd rather download one directly.
Or build from source (requires a Rust toolchain with cargo):
# From crates.io
# Straight from GitHub
# Or from a local clone
Then point it at any file in your repo:
# What depends on this component?
# What depends on a specific export?
# Check several files at once (e.g. everything in a commit)
By default it analyzes the current directory. Use --repo-root to point
elsewhere:
Use it in pre-commit hooks and CI
The most common setup is to run blast-radius on changed files so you (and
your reviewers) see the reach of a commit before it lands.
files takes a list of paths and reports each file's blast radius plus a
combined total. Pass - to read the list from stdin — the natural fit for
piping from git:
# What does my working-tree change reach?
|
# Gate a PR on everything it touches
|
It also receives staged filenames as arguments from hook managers like
lint-staged, Husky, Lefthook, and pre-commit. For example, with
lint-staged:
To turn the verdict into a gate, exit non-zero when a change reaches too far:
# Fail (exit code 2) if a change impacts more than 50 downstream files
# Or fail when the risk verdict hits "risky" or above
Exit codes: 0 no gate tripped, 1 analysis error, 2 gate tripped, 64
usage error (so CI can tell a misspelled flag apart from a tripped gate).
See docs/local-toolchain.md for ready-to-paste examples with lint-staged,
Lefthook, and the pre-commit framework.
GitHub Action
To comment a change's blast radius directly on pull requests (and optionally gate on risk), use the action:
# .github/workflows/blast-radius.yml
name: blast-radius
on: pull_request
permissions:
contents: read
pull-requests: write
jobs:
blast-radius:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
- uses: ehermanson/blast-radius@v0.7.3
with:
fail-on-risk: high # optional
It posts one sticky comment and updates it in place. See
docs/github-action.md for all inputs.
Reading the output
The default tree output leads with a risk verdict — minor, moderate,
risky, or high — plus a meter and the counts behind it. For larger blast
radii a hotspots chart follows, showing the directories with the most
impacted files, so you can see where the change lands before reading any paths.
The impacted files are then listed grouped by package and directory. Files
marked ◎ endpoint are leaves nothing else depends on (apps, routes, pages) —
a signal the change can reach something user-facing. Past 200 impacted files
the per-file lists collapse to directory rollups; pass -v to list every file.
The last line reports confidence — see below.
Pass --verbose (-v) to see the full root → cascade tree of exactly how the
impact propagates.
Confidence
The result ends with a confidence read so you know how much to trust it:
- high — every import edge on the impacted paths resolved cleanly.
- partial — N ambiguous edges on these paths — some edges the result was traced through couldn't be pinned to a single target (e.g. a symbol re-exported by several barrels), so a few listed files may be over-attributed.
Two repo-wide caveats can be appended because their targets are unknown, so they might hide additional consumers not listed:
- N unresolved imports — internal-looking imports that didn't resolve to a
file (often generated/virtual modules or build output). Quiet these with
.blast-radius.json(see Configuration). - N parse failures — files that couldn't be parsed and were skipped.
Commands
| Command | What it does |
|---|---|
file <path> |
Everything that depends on this file. |
export <path> <name> |
Everything that depends on a specific named export. |
files <path>... |
Blast radius for each file plus a combined total. - reads the list from stdin. |
graph |
Dump the whole-repo import graph (every file and resolved import edge). |
completions <shell> |
Print a completion script (bash, zsh, fish, elvish, powershell). |
Install completions by writing the script where your shell looks for them,
e.g. blast-radius completions zsh > ~/.zfunc/_blast-radius (zsh, with
~/.zfunc in fpath) or
blast-radius completions bash > /etc/bash_completion.d/blast-radius.
Global flags:
| Flag | Purpose |
|---|---|
--repo-root <dir> |
Repo to analyze (default: current directory). |
--format <tree|json|mermaid|dot> |
Output format (default: tree). |
--output <file> |
Write output to a file instead of stdout. |
--verbose, -v |
Show the full cascade tree. |
--quiet, -q |
No stdout output; exit codes and --output still apply. |
--color <auto|always|never> |
ANSI color handling (default: auto; NO_COLOR respected). |
--explain-unresolved |
Group unresolved internal imports by likely cause. |
--fail-threshold <n> |
Exit code 2 when more than n downstream files are impacted (the changed files themselves are not counted). |
--fail-on-risk <tier> |
Exit code 2 when the verdict is at or above tier. |
--version prints the version plus the language adapters compiled into the
binary, so you can tell a JS/TS-only source build from the full prebuilt one.
Output formats
tree— the default human-readable verdict, meter, and impacted-file list.json— structured output; the per-input-file breakdown lives in therootsarray. Carries a top-levelschema_version(currently1), bumped only on breaking shape changes — new fields may appear without a bump. The full field-by-field contract is indocs/json-output.md.mermaid— a Mermaid graph definition.dot— Graphviz DOT.
Language support
The default binary supports JavaScript and TypeScript (js, jsx, ts,
tsx) — React codebases are the primary target, with symbol-level JSX
component usage tracking and React.lazy()/dynamic import() detection —
including ESM imports/exports, CommonJS require/module.exports,
default and named exports, barrels, export *, side-effect imports
(import './setup'), tsconfig.json/jsconfig.json path aliases, baseUrl, extends
chains (including tsconfig.base.json-style shared configs), project
references (aliases in referenced configs like tsconfig.lib.json are
honored), package imports/exports, the browser entry field,
.js specifiers backed by TypeScript source files,
.d.ts declaration files, and cross-package resolution across workspace
packages.
Other languages are compiled in at build time with Cargo features (there is
no runtime --language flag — a binary scans whatever was built into it). The
prebuilt binaries (the blast-radius-cli npm package and GitHub Release
downloads) ship with all language features already compiled in, so this
only matters when building from source:
Vue and Svelte ride the same battle-tested JS/TS pipeline: <script> and
<script setup> blocks are extracted and parsed as TypeScript/JavaScript, so
component imports are tracked when they appear in script. Template-only
component references with no script import are not visible yet. The Python
and Rust adapters use real parsers and work well on conventionally structured
repos, but are labeled beta: see docs/language-support.md for their known
blind spots before trusting them on an unconventional layout.
Ruby and Java adapters were removed in 0.3.0 — their heuristic parsers were
not accurate enough on real codebases to trust, and a wrong "looks safe"
answer is worse than no answer (docs/language-support.md has the details;
0.2.1 is the last release that included them).
Configuration
An optional .blast-radius.json at the repo root lets a repository declare
tooling quirks the analyzer shouldn't hardcode. It can ignore import specifiers
that point at generated/virtual modules (CSS-in-JS codegen, route type stubs,
published dist output, etc.) so they don't count against the unresolved-import
confidence signal, and it can skip generated or vendored directories during repo
discovery:
{
// comments and trailing commas are allowed (parsed as JSONC, like tsconfig)
"discovery": {
// repo-relative files or directory prefixes to skip while walking
"exclude": ["generated/", "vendor/snapshot/"],
},
"unresolved": {
"ignore": ["styled-system/css", ".velite", "/+types/"],
},
}
discovery.exclude entries are repo-relative prefixes. unresolved.ignore
entries are matched as substrings of the import specifier. Asset imports
(.svg, .css, .json, images, …) and type-only imports are ignored
automatically. See examples/chakra-ui/.blast-radius.json.
Examples
The examples/ directory has runnable fixtures for each supported language:
| Fixture | Exercises |
|---|---|
monorepo-demo |
Aliases, barrels, CommonJS, transitive React usage |
vite-react-ts |
A real Vite React + TypeScript template |
chakra-ui † |
Chakra UI snapshot — large library-shaped React monorepo |
excalidraw † |
Excalidraw snapshot — large real-world React application |
python-demo / fastapi † |
Python package, relative, and __init__.py reexport imports |
rust-demo |
mod, pub use, crate:: / self:: imports |
component-demo |
Mixed Vue/Svelte component imports |
† chakra-ui, excalidraw, and fastapi are large real-world snapshots that
aren't committed to the repo. Fetch them on demand (pinned to a known upstream
commit) before running their examples:
Run against any of them with --repo-root:
# JS/TS monorepo fixture
# Large React monorepo, with the full cascade tree
# Large real-world React app (Excalidraw)
# Python (needs the feature compiled in)
# Rust
# Vue/Svelte
Development
This project expects a Rust toolchain with cargo available locally. Common
quality commands:
The Makefile has the full set, including per-language test/quality/stress
targets (make test-python, make stress-chakra, etc.). See docs/quality.md
for what each command validates and docs/language-support.md for the
multi-language architecture.