Open package.json and count the lines. Now open your lockfile (pnpm-lock.yaml, package-lock.json or whichever one your project has) and count those. The second number is usually somewhere between three hundred and a thousand, and it is the real answer to "how much code is in my project".

Almost none of it is yours. All of it runs under your name, on your server, against your users' data.

The idea

A is code you have agreed to run without reading. That is a good trade, because writing your own date library is a worse use of your life. The cost of the trade is that those packages keep changing: bugs get fixed, security holes get found and patched, and occasionally the authors decide to break something on purpose. Updating is how you stay on the version that has the fix. Not updating is a decision too, it just does not feel like one.

code
     4  .  2  .  7        the version number, three parts
     |     |     |
     |     |     +--  PATCH  bug and security fixes only
     |     |            4.2.7 -> 4.2.8   read the diff, ship it
     |     |
     |     +--------  MINOR  new features, your code still works
     |                  4.2.7 -> 4.3.0   usually safe, still test
     |
     +--------------  MAJOR  they removed or changed something
                        4.2.7 -> 5.0.0   read their upgrade guide first

How it works

That three-part scheme is called semantic versioning, and most of the JavaScript world follows it. It is a promise, not a guarantee. A patch release can still break you.

Security updates are the ones with a deadline. When a vulnerability is found in a package, it gets a public advisory with an identifier and a severity, and that publication tells attackers exactly what to look for. npm audit (or pnpm audit) reads your lockfile and lists any advisories affecting what you have installed. GitHub's Dependabot does the same thing continuously and opens a pull request with the fix already applied.

Severity is about the package, not about you. A high severity flaw in a build tool that never runs in production is less urgent than a moderate one in the library that parses incoming requests. Read what the advisory actually describes before you panic or before you ignore it.

What to do

  1. Turn on Dependabot security updates in your GitHub repository settings. It is two clicks, and it is the only part of this that starts without you. It opens a pull request; it does not merge it and it does not deploy. Nothing is patched until you merge and ship. Put a recurring reminder somewhere you will see it, because eleven unmerged Dependabot pull requests protect nobody.

  2. Do patch and minor updates in a batch, on a schedule you can keep. Once a month is plenty for a small project. Commit everything you have first, so the upgrade is the only thing in the diff and you can undo it in one move. Then find out what is actually behind before changing anything:

    code
    pnpm outdated --compatible
    pnpm update
    pnpm outdated --compatible

    On npm the equivalent is plain npm outdated, where you read the Wanted column rather than Latest; there is no --compatible flag and npm ignores it with a warning. --compatible is what makes this diagnostic mean anything in pnpm: plain pnpm outdated compares against each package's latest published version, so it keeps listing packages that sit outside the range in your package.json however many times you update, which looks like failure when nothing is wrong. With the flag, it lists only what pnpm update can actually move. If the third command still shows the same packages, something genuinely failed to move, usually an override holding it down or a peer dependency refusing the newer version. Upgrade those by name, one at a time, and read what the resolver objects to. Then run the app the way a user would, including the paths that take payment or send email.

  3. Do major upgrades one package at a time, in their own commit. Ask Claude Code to read the upgrade guide for that specific version jump, apply it, and explain each change. Then read the diff yourself. A migration an AI performed and you approved without reading is a change nobody has reviewed.

Where it breaks

Updates break working software. This is the honest tension: the safest version to run is the one you have tested, and the safest version to run is the one with the security fix, and those are different versions. Nobody resolves that for you. What makes it survivable is being able to undo, which is why the commit comes before the upgrade and why an upgrade never travels in the same commit as a feature.

Automated tools also over-report. Advisories fire on packages that only run during your build, on transitive dependencies you cannot upgrade directly, and on flaws that need conditions your app never creates. If you treat every alert as an emergency you will stop reading them within a month.

The opposite failure is slower and much worse. A project two years behind cannot take a security patch without also taking six major versions of everything, and at that point the upgrade is a rewrite.