Skip to content

fix(diff): detect upgrades when the purl carries the version#37

Open
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-dh2e0f
Open

fix(diff): detect upgrades when the purl carries the version#37
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-dh2e0f

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Problem

Component matching in diff() keys each component on its full purl, which in practice almost always embeds the version — e.g. pkg:npm/lodash@4.17.21. This is exactly the form the README's own examples and real CycloneDX/SPDX generators (Syft, Trivy, cdxgen, …) produce.

Because the version is part of the key, an upgraded dependency gets two different keys across the two SBOMs and is reported as a removal plus an addition rather than an upgrade — silently hiding it from the upgraded report, the tool's headline feature. The upgraded path only ever fired for the uncommon case of a purl-less component matched by name (which is what the existing tests happened to cover).

The prior test suite even rationalized the broken behavior as intended:

// Different purl = treated as add/remove (purl includes version)
// This is correct behavior — different purls are different packages

Two versions of the same package are not two different packages.

Fix

Key components on a version-independent purl identity — the pkg:type/namespace/name portion — by stripping the version (@…) plus any qualifiers (?…) and subpath (#…), which can also vary between builds. Components without a purl still fall back to their name. Version comparison for the upgrade itself continues to use the parsed version field, so upgrades are now detected whether or not the purl carries a version.

Percent-encoding makes this safe for scoped packages: an npm scope like @angular appears as %40angular in a purl, so the first literal @ reliably marks the version boundary.

Verification

  • npm test — 30 passing (was 29). The misleading upgrade test is rewritten to assert a real upgrade is detected from version-bearing purls, and a scoped-package (%40angular/core, with a ?type=module qualifier) major-bump case is added.
  • npm run lint / npm run typecheck — clean.
  • End-to-end via the built CLI on two CycloneDX SBOMs (lodash@4.17.204.17.21): now reports Upgraded: 1 instead of the previous Added: 1 / Removed: 1.

Before

Added:       1
Removed:     1
Upgraded:    0

After

Upgraded:    1
  ~ lodash: 4.17.20 → 4.17.21

🤖 Generated with Claude Code

https://claude.ai/code/session_014QNV8jcejY3qt1rMd7w7Q2


Generated by Claude Code

Component matching keyed on the full purl, which almost always embeds the
version (e.g. pkg:npm/lodash@4.17.21). A real-world upgrade therefore produced
two different keys and was reported as a removal plus an addition, hiding it
from the "upgraded" report entirely — the tool's headline feature.

Key components on the version-stripped purl identity (pkg:type/namespace/name),
also dropping qualifiers/subpath which can vary between builds, so upgrades are
detected whether or not the purl carries a version. Falls back to name when no
purl is present.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QNV8jcejY3qt1rMd7w7Q2
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