Skip to content

feat: add a transitive dependency audit to the dashboard - #26

Merged
00o-sh merged 1 commit into
mainfrom
claude/renovate-abandoned-packages-dfxwoi
Aug 15, 2026
Merged

feat: add a transitive dependency audit to the dashboard#26
00o-sh merged 1 commit into
mainfrom
claude/renovate-abandoned-packages-dfxwoi

Conversation

@00o-sh

@00o-sh 00o-sh commented Aug 15, 2026

Copy link
Copy Markdown
Contributor

What & why

Renovate's per-fork dependency dashboards can only flag what appears in a package.json. An abandoned package reachable only transitively is therefore invisible to every dashboard in the org. Run against the 27 published forks today, that blind spot hides 34 of the 63 abandoned-with-dependencies packages sitting in their trees.

scripts/dep_audit.py closes it. It resolves each fork's full production tree and classifies every node, rendering a panel on the main dashboard plus a standalone dependencies.html next to topology.html.

How the tree is resolved

npm install --package-lock-only --omit=dev --ignore-scripts in a scratch directory, then the resolved graph is read out of the lockfile. npm walks registry metadata only — no tarball is downloaded and no package lifecycle script ever executes, which matters given this workflow runs in the org's CI. The whole org resolves in about 25 seconds.

Reading each node's own dependency count from the lockfile rather than from a per-version registry request is deliberate. The per-version endpoint 404s on versions that exist ([email protected] is one), and a caught 404 silently looks like "zero dependencies", which misclassifies a time bomb as inert. The lockfile is npm's own resolution and can't disagree with itself.

The classification

It turns on whether an abandoned package can still rot, which is the distinction that decides what to do about it:

State Meaning Action
alive released within abandonmentThreshold leave it — a maintainer can respond
inert abandoned, zero runtime deps nothing beneath it to rot; exactly what the shared preset may suppress
time bomb abandoned with its own runtime deps nobody left to bump them — own it, never silence it

Being "on latest" is not a health signal when latest is itself frozen, so the audit reports last-release dates rather than version drift.

Where one name resolves to different versions across forks and those versions classify differently, the worst state wins. That keeps the rollup honest about real exposure and makes the totals independent of the order forks are audited in — verified by re-running with the fork list reversed.

The cross-check that earns its own section

A fork's tree may contain the abandoned upstream copy of a package this org already maintains, because the fork was never repointed at its sibling. That is 22 packages across 11 forks today — self-inflicted rot, and mechanical to fix. It is derived by intersecting each tree against the set of packages the org publishes, so it needs no maintenance as forks are added.

Current output

143 unique packages across the trees — 63 time bombs (34 invisible), 46 inert, 34 alive. The three classes partition the total exactly.

Design rules honoured

  • Derived, never recorded. Everything is computed at build time; nothing is written to a file and no central registry is introduced. Adding a fork requires no edit here.
  • Best-effort. A fork that fails to resolve degrades to a single "unresolved" row rather than failing the build (@unabandoned/browserify-sign is unpublished today and exercises this path). The entire section is omitted if npm is unavailable.
  • No new dependencies. Python stdlib plus the npm already present on the runner. The dev tree is untouched.

Verification

  • dep_audit.audit() run against all 27 published forks plus one unpublished package: 25.8s, correct degrade row, totals partition exactly, order-independent.
  • Rendered dependencies.html inspected — 3 tables balanced, 21 <div> open/close balanced, no unsubstituted placeholders, unresolved row present.
  • py_compile clean on all three scripts; render() signature and its call site updated together.
  • The builder's GitHub-discovery half could not be exercised here — api.github.com is blocked by this session's egress policy — so main will be the first end-to-end run. The audit is additive and independently tested, and the workflow's paths: trigger now includes the new script so a rebuild fires on merge.

Type of change

  • fix / feat / perf — consumer-facing
  • deps — dependency update (add the security label if it fixes a CVE)
  • chore / ci / build / docs / test / refactor — maintenance

Checklist

  • Commits follow Conventional Commits (commitlint passes)
  • npm test passes on Node 20 / 22 / 24 — n/a, this repo ships no npm package; the changed code is Python and the workflow that runs it
  • No new runtime dependency (or its addition is justified below) — none; stdlib only
  • Dev tree kept lean (prefer built-in node:test over new runners) — unchanged

Generated by Claude Code

Renovate's per-fork dependency dashboards can only flag what appears in a
package.json, so an abandoned package reachable only transitively is
invisible to every dashboard in the org. Across the 27 published forks
that blind spot currently hides 34 of the 63 abandoned-with-dependencies
packages in their trees.

scripts/dep_audit.py resolves each fork's full production tree and
classifies every node in it. Resolution uses `npm install
--package-lock-only --omit=dev --ignore-scripts`, so npm walks registry
metadata only: no tarball is downloaded and no lifecycle script runs. The
whole org resolves in about 25 seconds.

The classification turns on whether an abandoned package can still rot,
which is the distinction that decides what to do about it:

  alive      released within the threshold - someone can still respond
  inert      abandoned, zero runtime deps - nothing beneath it to rot,
             and exactly what the shared preset may suppress
  time bomb  abandoned with its own runtime deps - nobody left to bump
             them, so the subtree ages unwatched

Being "on latest" is not a health signal when latest is itself frozen, so
the audit reports last-release dates rather than version drift.

A derived cross-check gets its own section: a fork's tree may contain the
abandoned upstream copy of a package this org already maintains, because
the fork was never repointed at its sibling. That is 22 packages across
11 forks today, and it is mechanical to fix.

Everything is derived at build time and nothing is recorded in a file, per
the dashboard's design rule. A fork that fails to resolve degrades to one
"unresolved" row rather than failing the build, and the whole section is
omitted if npm is unavailable.
@00o-sh
00o-sh merged commit 0ab9520 into main Aug 15, 2026
2 checks passed
@00o-sh
00o-sh deleted the claude/renovate-abandoned-packages-dfxwoi branch August 15, 2026 05:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants