Padova · IT
Python Shipped

Lockdiff: making lockfile diffs readable

SOURCE:// lockdiff

A small tool that pulls the signal out of tedious lockfile diffs, focusing on what actually matters: added, removed, and bumped packages.

Reading git diff package-lock.json has become a tedious job. In a real change, one bumped dependency say, drowns in thousands of lines of reordered keys, integrity hashes, and resolved URLs. The signal is in there. You just can’t see it. So I wrote a small tool that pulls the signal out.

lockdiff takes two lockfiles and tells you, in plain English, what was added, removed, or version-bumped between them. Plain three categories that actually matter when you’re reviewing a PR or trying to remember what last week’s uv lock did to your tree.

What is in a lock file?

A lockfile is the resolver’s homework exported as graph of packages. When you ask uv or npm to install your dependencies, it walks the graph, picks compatible versions for everything (your direct deps and every transitive dep underneath them) and records the result so the next install on a different machine produces the same tree. That’s all it is: a frozen snapshot of one valid resolution.

The shape of the snapshot varies. uv.lock is TOML, a flat array of [[package]] entries, each with a name, a version, a source, and a list of dependency names. One entry is then one package along with its version. package-lock.json is JSON and not flat (it’s keyed by install path, node_modules/express/node_modules/debug) because npm allows the same package to exist at multiple versions inside one tree, nested under whichever parent needed which version. Both are designed for machines to read, which is exactly why humans can’t.

The interesting thing for a diff tool then becomes the asymmetry between what the lockfile records and what a human cares about. A lock file tracks integrity hashes, resolved URLs, marker expressions, optional dependency groups. But a human agent reviewing a PR would care about three things: what’s new, what’s gone and what moved. Most of the file is noise, in the strict information-theoretic sense, for that question.

The shape of the thing

The parser module of the application turns a lockfile into a dictonary of packages. The differ module then takes two of those dicts and produces a DiffResult which are three lists: added, removed, bumped. The renderer turns the DiffResult into a string. Each layer knows nothing about the layers downstream of it. You can swap any of them without touching the others, which is the only architectural decision in the whole tool that mattered.

The CLI is a thin shell. It reads two paths, dispatch to the right parser by file extension, run the diff, print the result, exit 1 if anything changed and 0 if it didn’t. It is available as python package.

What it ended up being good for

In principle, catching the things you’d miss in a review:

The diffs are short enough to read top to bottom and decide whether you care.

Result

Here is how it looks in action, parsing both uv and npm lockfiles:

Demo — UV uv.lock diffing
UV Lock Diff
Demo — NPM package-lock.json diffing
NPM Lock Diff