preflight

See what breaks before you upgrade.

Your tools tell you an update is available. preflight tells you which lines of your code it breaks.

preflight
$ preflight framer-motion 12 --cwd ./

  preflight v0.1.0   framer-motion → 12

  src/components/ui/settle-text.tsx
  3:10     motion            import
  3:18     Variants          import · type-only
  11:18    Variants          type · type-only
  18:20    Variants          type · type-only
  40:6     motion.p          jsx
  50:12    motion.span       jsx

  src/components/landing/hero.tsx
  7:10     motion            import
  95:10    motion.div        jsx
  101:11   motion.div        jsx
  102 usages · 3 distinct APIs · 16 of 166 files
  APIs in use: AnimatePresence, Variants, motion

Real output, run against a 166-file production Next.js repo.

How it works

01

Scans your code

Parses every source file into an AST and resolves each import back to its binding, so renamed imports, namespace access and shadowed variables all resolve correctly. Commented-out code and strings that look like imports are never counted.

02

Reads the changelog

Works out the version you are actually on from your lockfile, then pulls the release notes for every version between that and the one you are moving to.

03

Flags what is risky

Matches the APIs you actually use against what the changelog says changed, separating certain breaks from maybes. Certain breaks fail the check; maybes are flagged for review.

What I learned building it

Three things that only showed up by running it against real repositories.

TypeScript types were invisible

Babel's scope resolution only tracks value references. So import type { Variants } showed up as an import and then vanished — every line that actually used the type was invisible to the scanner. On a TypeScript codebase that is not a small gap: a changed type signature breaks your build just as hard as a renamed function. It took a third resolution pass, walking TS type positions separately and resolving each one back to its binding, before those lines showed up at all. A fixture test caught it before it shipped.

Not every package publishes releases

I built the changelog fetcher against the GitHub Releases API, pointed it at framer-motion, and got back HTTP 200 and an empty array. The project does not publish GitHub releases at all — it keeps its changelog in a CHANGELOG.md. So for the exact package I had spent the previous step scanning, the API had nothing to give. That is not a rare edge case, it is a meaningful share of npm. preflight reports it plainly instead of showing an all-clear, and a changelog-file fallback is now part of the next step.

Release tags disagree with each other

Across four ordinary dependencies I found four conventions: v1.3.25, 7.9.1, @clerk/nextjs@7.5.2 and framer-motion@12.0.0. The monorepo form is the dangerous one. clerk/javascript tags every package in a single repository, so @clerk/vue@2.4.22 and @clerk/nextjs@2.4.22sit side by side. Read a tag as “version 2.4.22” without checking which package it belongs to and you attach the wrong changelog to the wrong upgrade, then tell someone their code is safe. That check is a test now.

Status

working
The full pipeline: AST usage scanning, changelog fetching, usage-to-changelog matching (certain vs maybe), transitive dependencies from the lockfile, a consolidated report with a CI-readable exit code, and a reusable GitHub Action that posts the findings on every pull request and fails the check on certain breaks. 87 tests pass, covering renamed imports, shadowed bindings, TypeScript type positions, four release-tag conventions, API rate limits, missing repositories, and the matching layer.
not built
An optional AI fallback for changelog lines the rules cannot disambiguate (like a package named the same as an export), and performance caching for the transitive scan on large repos.
published
Live on npm as @aadi49/preflight. Run it with npx @aadi49/preflight or install it globally with npm install -g @aadi49/preflight.

Try it

Point it at a package and the version you are thinking about moving to.

preflight
$ npx @aadi49/preflight <package> <version>

Zero install — npx fetches it on the fly. Or install it globally with npm install -g @aadi49/preflight.

View source