Raised while fixing a TUI defect in bomly-cli#423: the interactive scan rendered a module's NodeID() under a column labelled "PURL", handing the viewer module:package.json#pkg:npm/app@1.0.0 — a string no consumer can parse as a package URL.
What is missing
"What package URL does this node publish" is a property of the node, and the three kinds answer differently:
- a dependency node's ID is its canonical package URL;
- a module's ID is the structural
module:<path>#<purl> grammar, and its package URL is a separate PURL() field;
- a manifest has no package URL at all — it is a file.
GraphNode exposes NodeID(), Kind(), NodeLocations(), NodeWarnings() and CloneNode(), but nothing for this. v0.9.0 added NodeCoordinates, NodeDisplayName and NodeVersion for exactly this class of question; this one was not included.
Why it matters
Every consumer that publishes a purl writes the three-case switch itself, and they disagree the moment one is missed. In the CLI today the same projection exists in internal/output, in internal/sbom, and — until this PR — wrongly in two TUI paths. The SBOM and JSON copies were right; the TUI copy shipped an unparseable value to users. That is the drift this accessor removes.
The two remaining CLI copies stay separate on purpose: an SBOM codec importing the CLI's output layer would be a layering inversion, so they converge on this accessor rather than on each other. Both are commented with this issue.
Suggested shape
// NodePURL returns the package URL a node publishes, or "" when it has none.
func NodePURL(node GraphNode) string
Additive, so a minor bump.
What still needs checking
- Whether a module whose coordinates cannot mint a package URL should return
"" or its generic fallback — NewModuleNode derives the PURL when it can and warns otherwise, so the empty case is reachable.
- Whether
Package.ID/PackageRef consumers want the same accessor, since they key on the dependency case only today.
Raised while fixing a TUI defect in bomly-cli#423: the interactive scan rendered a module's
NodeID()under a column labelled "PURL", handing the viewermodule:package.json#pkg:npm/app@1.0.0— a string no consumer can parse as a package URL.What is missing
"What package URL does this node publish" is a property of the node, and the three kinds answer differently:
module:<path>#<purl>grammar, and its package URL is a separatePURL()field;GraphNodeexposesNodeID(),Kind(),NodeLocations(),NodeWarnings()andCloneNode(), but nothing for this. v0.9.0 addedNodeCoordinates,NodeDisplayNameandNodeVersionfor exactly this class of question; this one was not included.Why it matters
Every consumer that publishes a purl writes the three-case switch itself, and they disagree the moment one is missed. In the CLI today the same projection exists in
internal/output, ininternal/sbom, and — until this PR — wrongly in two TUI paths. The SBOM and JSON copies were right; the TUI copy shipped an unparseable value to users. That is the drift this accessor removes.The two remaining CLI copies stay separate on purpose: an SBOM codec importing the CLI's output layer would be a layering inversion, so they converge on this accessor rather than on each other. Both are commented with this issue.
Suggested shape
Additive, so a minor bump.
What still needs checking
""or its generic fallback —NewModuleNodederives the PURL when it can and warns otherwise, so the empty case is reachable.Package.ID/PackageRefconsumers want the same accessor, since they key on the dependency case only today.