blastradius
README.md
# blastr
[](https://www.npmjs.com/package/@justfeltlikerunning/blastradius)
[](./LICENSE)
[](https://buymeacoffee.com/justfeltlikerunning)
**See exactly which of your files and functions break before you upgrade an npm dependency.**
Every other tool tells you *that* an upgrade might break something — `npm outdated` lists versions, Dependabot/Renovate give crowd-sourced risk scores, `ncu --doctor` runs your tests and hopes. None of them tell you **which lines of your code actually touch the API that changed.** blastr does.
```
$ blastr chalk@5
blastr chalk 4.1.2 → 5.0.0
package-level changes:
! package "type" changed commonjs -> module (require()/import interop may break)
src/logger.ts
✖ ● 12:18 chalk.Instance [symbol-removed]
! ○ 40:8 chalk.Level [symbol-removed] ← formatBanner
summary 2 breaking, 0 possibly-breaking across 1 file(s)
confidence ● 1 confirmed ◐ 0 likely ○ 1 unknown
```
## Dogfooded on itself
blastr is developed with blastr. Running `blastr audit` on **its own** dependencies
ranks every pending upgrade by blast radius:
```
$ blastr audit
package upgrade brk psb files
es-module-lexer 1.7.0→2.3.0 0 3 1
cjs-module-lexer 1.4.3→2.2.0 0 2 1
vitest 2.1.9→4.1.10 0 0 0
typescript 5.6.3→7.0.2 0 0 0
...
ranked by blast radius — top rows are the risky upgrades to review first
```
…and drilling into the top one shows the exact sites — with confidence tiers and
the transitive call chain — that a `es-module-lexer` bump would break:
```
$ blastr es-module-lexer@2
./src/core/surface.ts
! ● 386:33 esParse [signature-changed]
! ● 388:14 esParse [signature-changed]
! ◐ 436:25 esParse [signature-changed] ← esmExports
! ○ 559:29 esParse [signature-changed] ← esmExports ← extractWithLexers
summary 0 breaking, 4 possibly-breaking across 1 file(s)
confidence ● 2 confirmed ◐ 1 likely ○ 1 unknown
```
(TypeScript 5.6→**7.0** shows `0` impact — blastr doesn't import the compiler API in
a way v7 changed, so that jump is safe. That's the point: it distinguishes the
scary-looking-but-safe from the actually-risky.)
## How it works
blastr joins two analyses nobody else connects:
1. **Version-to-version API diff.** It fetches the target version, extracts the public API surface of both versions (TypeScript declarations when present, runtime probing for untyped packages), and produces a precise set of removed / signature-changed / kind-changed symbols.
2. **Consumer usage tracing.** It statically resolves your imports to those changed symbols using the TypeScript compiler, finds every usage site, and expands through your own call graph to catch code that *transitively* depends on the break.
The result is a **deterministic blast-radius graph** — `changed symbol → your import → usage site → the functions that call it` — with provenance on every finding.
### Honesty by design
blastr proves the **presence** of impact, never its absence. A clean report means "no usages found," not "safe." Every finding carries a confidence tier:
| Tier | Meaning |
|------|---------|
| ● `confirmed` | the TypeScript checker resolved the identifier to the changed symbol in that exact package |
| ◐ `likely` | syntactic match without full type resolution (untyped JS, destructured `require`) |
| ○ `unknown` | your code uses a pattern static analysis can't see through (dynamic `require`, computed member access) — reported with the reason, never silently dropped |
## Install
```
npm install -g @justfeltlikerunning/blastradius # or: npx @justfeltlikerunning/blastradius <package>
```
## Usage
```
blastr <package>[@target] [options] analyze one upgrade against your code
blastr diff <package> <from> <to> standalone API diff (no repo needed)
blastr audit [--wanted] rank ALL outdated deps by blast radius
blastr mcp stdio MCP server for AI agents
--to <spec> target version/range/tag (default: latest)
--from <version> override the installed version to compare against
--cwd <dir> consumer repo root (default: current directory)
--depth <n> transitive expansion depth (default: 2; 0 = direct only)
--dynamic enable the runtime probe for untyped packages
--behavioral also scan the changelog for same-signature behavioral breaks
(needs ANTHROPIC_API_KEY; LLM-assisted, best-effort)
--json / --ndjson / --pretty output format (pretty defaults on a TTY)
```
**Exit codes:** `0` no impact · `1` impact found · `2` analysis error.
### Triage your whole upgrade backlog
```
$ blastr audit
blastr audit 8 outdated dependenc(ies) → latest
package upgrade brk psb files
chalk 4.1.0→5.6.2 1 0 1
express 4.19.2→5.1.0 1 2 3
ms 2.1.2→2.1.3 0 0 0
...
ranked by blast radius — top rows are the risky upgrades to review first
```
The answer to "which of my 20 Dependabot PRs actually matter."
### Accuracy: only real breaks
blastr classifies signature changes by **caller-compatibility** (jsii-style variance):
adding an optional parameter or an overload is *not* breaking and won't be flagged;
removing a parameter or adding a required one *is*. A real `zod` 3→4 run drops from
14 raw signature-change alarms to the 1 that actually matters — with a transparent
count of what was classified compatible, so nothing is silently hidden.
### Untyped packages
Packages that ship no `.d.ts` still get analyzed, via a tiered fallback:
1. **`@types` sidecar** — if `@types/<pkg>` exists on DefinitelyTyped, blastr borrows it for a full typed surface (so `express` upgrades analyze at `confirmed` confidence with real signatures).
2. **Module lexers** — names of the runtime exports.
3. **`--dynamic`** — load the package in a **sandboxed child process** (`--no-addons`, hard timeout, lifecycle scripts never run) to enumerate its real runtime surface.
## For AI agents
blastr is built to be driven by coding agents, not just humans.
- **`--json`** emits a stable, versioned schema (`schemaVersion: 1`). Inspect it with `blastr --print-schema`.
- **`--ndjson`** streams findings one JSON object per line for progressive consumption.
- **Exit codes** let an agent branch without parsing (`1` = there's a blast radius to review).
- **Results go to stdout, logs to stderr** — always safe to pipe.
- **MCP server:** run `blastr mcp` for a stdio Model Context Protocol server exposing a single `blastr_check` tool that returns the same report. Point Claude Code / any MCP client at it:
```json
{ "mcpServers": { "blastradius": { "command": "npx", "args": ["@justfeltlikerunning/blastradius", "mcp"] } } }
```
### In CI / on pull requests
`--markdown` emits a PR-comment-ready summary. Drop this into a workflow to get a
blast-radius comment on every dependency-bump PR:
```yaml
# .github/workflows/blastr.yml
name: blast-radius
on: pull_request
jobs:
blastr:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20 }
- run: npm ci
- name: Blast-radius audit
run: npx @justfeltlikerunning/blastradius audit --markdown > blastradius.md
- uses: marocchino/sticky-pull-request-comment@v2
with:
path: blastradius.md
```
Or feed findings into the **GitHub Security tab** as code-scanning alerts (SARIF):
```yaml
- run: npx @justfeltlikerunning/blastradius <pkg>@<target> --sarif > blastr.sarif
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: blastr.sarif
```
Use `--fail-on breaking` (or `possibly` / `any` / `none`) to control when the job fails.
## Configuration (optional)
Drop a `blastr.config.json` in your project root to set defaults and ignore paths:
```json
{
"ignore": ["**/test/**", "**/*.stories.tsx"],
"depth": 2,
"dynamic": false,
"audit": { "ignore": ["some-internal-pkg"] }
}
```
CLI flags always override config. `ignore` globs are excluded from usage scanning.
## What it is not
blastr checks the **API surface** — removed exports, changed signatures, entry-point and module-type changes. It cannot see purely *behavioral* changes that keep the same signature (a function that now throws on bad input, a changed default). Read the changelog for those; blastr tells you where the structural breaks land.
## License
MIT
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues