Skip to content

fix(parser): derive component version from the purl when the version field is absent#38

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

fix(parser): derive component version from the purl when the version field is absent#38
dmchaledev wants to merge 1 commit into
mainfrom
claude/magical-ptolemy-ji4vfv

Conversation

@dmchaledev

Copy link
Copy Markdown
Contributor

Summary

A component's dedicated version field is optional in both CycloneDX (component.version) and SPDX (package.versionInfo), but the purl encodes the version authoritatively (e.g. pkg:npm/lodash@4.17.21). Today, when that field is missing, the parser leaves Component.version as undefined even though the version is right there in the purl. As a result:

  • text/markdown reports render the component as name@unknown, and
  • JSON / programmatic consumers (sbom.components[i].version) get undefined.

This PR extracts the version from the purl as a fallback for both formats.

Reproduction (before)

import { parse } from '@hailbytes/sbom-diff';
const sbom = parse(JSON.stringify({
  bomFormat: 'CycloneDX', specVersion: '1.5',
  components: [{ name: 'lodash', purl: 'pkg:npm/lodash@4.17.21' }], // valid: no `version` field
}));
console.log(sbom.components[0].version); // -> undefined   (report shows "lodash@unknown")

After this change it resolves to 4.17.21.

What changed

  • src/parser.ts — new extractVersionFromPurl() helper, used as a fallback in both parseCycloneDX and parseSPDX. The version follows the last unescaped @ (namespace/name segments must percent-encode literal @, so scoped npm purls like pkg:npm/%40angular/core@17.0.0 resolve correctly), is cut at any ?qualifiers / #subpath, and is URL-decoded.
  • src/types.ts — documented the fallback on Component.version.
  • src/__tests__/parser.test.ts — added coverage: purl-only version (incl. scoped + qualifiers/subpath), explicit field wins over purl, versionless purl / bare component stays undefined, and the SPDX externalRef case.

An explicit version/versionInfo field still takes precedence, so nothing changes for SBOMs that already carry it — the fix is purely additive and only fills a previously-empty field.

Scope / conflicts

Confined to parser.ts (component mapping + one helper). It does not touch diff.ts, reporter.ts, or cli.ts, so it is orthogonal to the in-flight purl/diff PRs (#20/#31/#37, which rework diff keying) and to the reporter/CLI PRs.

Verification

npm run lint, npm run typecheck, npm test (33 passing, +4 new), and npm run build all pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_01Xj1u5UNm7YzrnpJshe2fuV


Generated by Claude Code

… absent

A component's dedicated version field is optional in both CycloneDX
(component.version) and SPDX (package.versionInfo), but the purl encodes
the version authoritatively (e.g. pkg:npm/lodash@4.17.21). When the field
was missing the parser left Component.version undefined, so reports
rendered `name@unknown` and JSON/programmatic consumers got no version
even though it was present in the purl.

Extract the version from the purl as a fallback for both formats. The
version follows the last unescaped `@` (namespace/name segments must
percent-encode literal `@`, so scoped npm purls like
pkg:npm/%40angular/core@17.0.0 resolve correctly) and is cut at any
`?qualifiers` / `#subpath` and URL-decoded. An explicit version field
still takes precedence, and purls without a version leave it undefined.

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