feat: add a transitive dependency audit to the dashboard - #26
Merged
Conversation
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.
7 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.pycloses it. It resolves each fork's full production tree and classifies every node, rendering a panel on the main dashboard plus a standalonedependencies.htmlnext totopology.html.How the tree is resolved
npm install --package-lock-only --omit=dev --ignore-scriptsin 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:
abandonmentThresholdBeing "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
143unique packages across the trees —63time bombs (34invisible),46inert,34alive. The three classes partition the total exactly.Design rules honoured
@unabandoned/browserify-signis unpublished today and exercises this path). The entire section is omitted if npm is unavailable.npmalready 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.dependencies.htmlinspected — 3 tables balanced, 21<div>open/close balanced, no unsubstituted placeholders, unresolved row present.py_compileclean on all three scripts;render()signature and its call site updated together.api.github.comis blocked by this session's egress policy — somainwill be the first end-to-end run. The audit is additive and independently tested, and the workflow'spaths:trigger now includes the new script so a rebuild fires on merge.Type of change
fix/feat/perf— consumer-facingdeps— dependency update (add thesecuritylabel if it fixes a CVE)chore/ci/build/docs/test/refactor— maintenanceChecklist
commitlintpasses)npm testpasses on Node 20 / 22 / 24 — n/a, this repo ships no npm package; the changed code is Python and the workflow that runs itnode:testover new runners) — unchangedGenerated by Claude Code