<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: M. Ahmad Malik</title>
    <description>The latest articles on DEV Community by M. Ahmad Malik (@ahmadmalik1376).</description>
    <link>https://dev.to/ahmadmalik1376</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4137718%2F684fa62a-a5d8-41db-9eb4-d84daf95dbfe.png</url>
      <title>DEV Community: M. Ahmad Malik</title>
      <link>https://dev.to/ahmadmalik1376</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ahmadmalik1376"/>
    <language>en</language>
    <item>
      <title>I built a tool that tells you which lines an npm upgrade will break</title>
      <dc:creator>M. Ahmad Malik</dc:creator>
      <pubDate>Tue, 22 Sep 2026 14:01:25 +0000</pubDate>
      <link>https://dev.to/ahmadmalik1376/i-built-a-tool-that-tells-you-which-lines-an-npm-upgrade-will-break-37j2</link>
      <guid>https://dev.to/ahmadmalik1376/i-built-a-tool-that-tells-you-which-lines-an-npm-upgrade-will-break-37j2</guid>
      <description>&lt;p&gt;Every project I work on has the same quiet backlog: dependency upgrades that nobody wants to start.&lt;/p&gt;

&lt;p&gt;Not because upgrading is hard. Because the only honest answer to &lt;em&gt;"will this break us?"&lt;/em&gt; is &lt;em&gt;"change the version, run the build, and find out."&lt;/em&gt; The changelog tells you what changed in the library. It doesn't tell you what changed &lt;strong&gt;for your code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;So I built &lt;a href="https://github.com/AHMADMALIK1376/bumpscan" rel="noopener noreferrer"&gt;bumpscan&lt;/a&gt;, a CLI that answers that question before you upgrade.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx bumpscan express@5
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;express  4.22.3 → 5.2.1
   types from @types/express@4.17.25 → @types/express@5.0.6

Breaks your code (3)
  ❌ your whole project  now needs Node 18.0.0 or newer
       💡 fix: check that your CI and servers run Node 18 or newer
  ❌ Request.param  method was removed
       src/server.ts:8:14  const id = req.param("id");
  ❌ Response.sendfile  method was removed
       💡 fix: maybe use sendFile
       src/server.ts:9:3  res.sendfile(`/data/${id}.json`);

3 breaking, 1 risky in 1 of your files · 13 other changes don't touch your code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That last line is the whole point. &lt;strong&gt;Most upgrades change a lot and touch you barely at all.&lt;/strong&gt; The hard part is finding the few lines that matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;There's no AI and no server. It runs entirely on your machine, in three steps.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Download both versions.&lt;/strong&gt; bumpscan reads the version you use today (from &lt;code&gt;node_modules&lt;/code&gt; or &lt;code&gt;package.json&lt;/code&gt;), resolves the version you want, and downloads both from npm.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Compare their public API.&lt;/strong&gt; It reads each version's type definitions (&lt;code&gt;.d.ts&lt;/code&gt;) into a flat list: every export, every property, every function signature. Then it compares the two lists and reports what was removed, renamed, made required, or changed shape.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Scan your code.&lt;/strong&gt; It parses your source and follows every place you use the package: imports, &lt;code&gt;require()&lt;/code&gt;, chains like &lt;code&gt;axios.create().get()&lt;/code&gt;, local variables, constructor calls, and even the parameters of callbacks you pass in. Only changes you actually touch are shown.&lt;/p&gt;

&lt;p&gt;It also checks the things that are &lt;strong&gt;not in the types at all&lt;/strong&gt;, which is what breaks most builds in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a package going &lt;strong&gt;ESM-only&lt;/strong&gt;, so &lt;code&gt;require()&lt;/code&gt; stops working&lt;/li&gt;
&lt;li&gt;a package raising its &lt;strong&gt;minimum Node version&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Three things that surprised me
&lt;/h2&gt;

&lt;p&gt;Getting a first version working was quick. Getting it to stop lying took longer. These three were the interesting ones.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. "Removed" often means "moved"
&lt;/h3&gt;

&lt;p&gt;My first run on axios reported nine breaking changes. When I checked them by hand, several were false. For example, &lt;code&gt;create&lt;/code&gt; looked removed from &lt;code&gt;AxiosStatic&lt;/code&gt;, but it had simply moved to a base interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AxiosStatic&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;AxiosInstance&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="err"&gt;…&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;AxiosInstance&lt;/span&gt; &lt;span class="kd"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Axios&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;CreateAxiosDefaults&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;AxiosInstance&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your code still works. Following &lt;code&gt;extends&lt;/code&gt; removed most of the early false positives. The one change that remained was real, and subtle: &lt;code&gt;CanceledError&lt;/code&gt;'s constructor went from &lt;code&gt;(message, code, config, request)&lt;/code&gt; to &lt;code&gt;(message, config, request)&lt;/code&gt;. The &lt;code&gt;code&lt;/code&gt; argument was dropped, and the order changed. That's the kind of break I'd never have spotted by reading a changelog.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;code&gt;@types/express&lt;/code&gt; is almost empty
&lt;/h3&gt;

&lt;p&gt;Express doesn't ship its own types, so bumpscan reads &lt;code&gt;@types/express&lt;/code&gt;. My first comparison of express 4 → 5 found &lt;strong&gt;one&lt;/strong&gt; change. That was obviously wrong.&lt;/p&gt;

&lt;p&gt;It turns out &lt;code&gt;@types/express&lt;/code&gt; holds almost none of the API itself. Nearly all of it lives in a dependency, &lt;code&gt;@types/express-serve-static-core&lt;/code&gt;. Before resolving dependencies, the snapshot had 32 entries. After: 498. Then it found the real removals, &lt;code&gt;req.param()&lt;/code&gt; and &lt;code&gt;res.sendfile()&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Matching those in your code needed one more step. In &lt;code&gt;app.get("/x", (req, res) =&amp;gt; …)&lt;/code&gt;, &lt;code&gt;req&lt;/code&gt; and &lt;code&gt;res&lt;/code&gt; aren't imports, they're callback parameters. bumpscan reads the handler's type from the function's own signature to work out what they are.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. On a Dependabot PR, you're comparing a version against itself
&lt;/h3&gt;

&lt;p&gt;bumpscan ships as a GitHub Action that comments on dependency pull requests. I tested it on a real PR in its own repo, and the comment said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;pacote&lt;/code&gt; 22.0.0 → 22.0.0 — nothing changed&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;On an upgrade PR, the checked-out &lt;code&gt;package.json&lt;/code&gt; &lt;strong&gt;already contains the new version&lt;/strong&gt;. So "before" and "after" were identical, on every PR, forever. The fix was to read the old version from the PR title (&lt;code&gt;bump pacote from 21.5.1 to 22.0.0&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;I'd never have found that without running it on a real pull request. Tests with fake data would all have passed.&lt;/p&gt;

&lt;h2&gt;
  
  
  More than one package at a time
&lt;/h2&gt;

&lt;p&gt;Run it with no arguments to check every dependency at once. It works in monorepos too (npm, yarn and pnpm workspaces):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx bumpscan
&lt;span class="go"&gt;
🧨 32 dependencies · 520 files scanned

  tinybench     2.9.0 → 6.2.0    ❌ 2 breaking  ⚠️ 3 risky
  execa         9.6.1 → 10.0.1   ❌ 1 breaking
  @biomejs/biome 1.9.4 → 2.5.14  ✅ safe for your code

  21 already up to date
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And &lt;code&gt;--fix&lt;/code&gt; rewrites the renames for you. It's deliberately limited: it only touches plain renames, the one kind of change that's always safe to rewrite. Removals need a human decision, and guessing would be worse than doing nothing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="gp"&gt;$&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;npx bumpscan chalk@5 &lt;span class="nt"&gt;--fix&lt;/span&gt;
&lt;span class="go"&gt;
Fixed (2)
  ✏️  src/ui.ts:1  Level → ColorSupportLevel
  ✏️  src/ui.ts:3  Level → ColorSupportLevel
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The GitHub Action
&lt;/h2&gt;

&lt;p&gt;Add this to &lt;code&gt;.github/workflows/bumpscan.yml&lt;/code&gt;, and every Dependabot or Renovate PR gets a comment listing the lines that break:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;bumpscan&lt;/span&gt;
&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;pull_request&lt;/span&gt;

&lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;
  &lt;span class="na"&gt;pull-requests&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;write&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;scan&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v4&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;AHMADMALIK1376/bumpscan@main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What it can't do
&lt;/h2&gt;

&lt;p&gt;Being upfront about this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;TypeScript and JavaScript only.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Packages with no types anywhere&lt;/strong&gt; (not shipped, no &lt;code&gt;@types/*&lt;/code&gt;) only get the package.json checks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Behaviour changes with the same signature are invisible.&lt;/strong&gt; If a function keeps its name and arguments but returns something different, bumpscan can't know. It finds the mechanical breakages, not the subtle ones.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npx bumpscan
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's MIT-licensed and on &lt;a href="https://github.com/AHMADMALIK1376/bumpscan" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. If it gets something wrong on your project, please open an issue with the package and version. The false positives are the most useful bug reports I can get.&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>typescript</category>
      <category>node</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
