Skip to content

feat(sbom): mark devDependency components with CycloneDX scope "excluded"#12442

Merged
zkochan merged 1 commit into
pnpm:mainfrom
Saturate:feat/sbom-dev-scope-excluded
Jun 16, 2026
Merged

feat(sbom): mark devDependency components with CycloneDX scope "excluded"#12442
zkochan merged 1 commit into
pnpm:mainfrom
Saturate:feat/sbom-dev-scope-excluded

Conversation

@Saturate

@Saturate Saturate commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

pnpm sbom emits no scope on any component today, so a consumer like
Dependency-Track can't tell runtime dependencies from build-only ones. This
sets scope: "excluded" on CycloneDX components reachable only through
devDependencies. CycloneDX 1.7 defines excluded as "component usage for
test and other non-runtime purposes", which is exactly a devDependency.

Runtime-reachable components keep the default (scope omitted → required),
including installed optionalDependenciesoptional is the wrong value
there since per the spec it means "not installed". SPDX output is unchanged.

Tests

Unit tests cover DevOnly → excluded, ProdOnly → no scope, DevAndProd → no
scope; a command test checks a dev-only dep gets excluded while a prod dep
doesn't. Output still validates against the CycloneDX 1.6/1.7 schema.


Written by an agent (Claude Code, claude-fable-5).

Summary by CodeRabbit

  • New Features

    • Enhanced pnpm sbom (CycloneDX) to annotate components that are only reachable via development dependencies with scope: "excluded" and an npm development marker.
    • Runtime-reachable components continue to omit scope and default to runtime semantics (including optional runtime installs).
  • Tests

    • Added Jest coverage to verify scope/development-marker behavior for dev-only vs runtime components, including mixed dev+prod cases.
  • Documentation

    • Added a changeset clarifying the meaning and intent of CycloneDX excluded scope.

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 3534fb6d-ad07-4997-bc9d-2f2084c354c6

📥 Commits

Reviewing files that changed from the base of the PR and between cacedc2 and 7acb4ad.

📒 Files selected for processing (4)
  • .changeset/sbom-dev-scope-excluded.md
  • deps/compliance/commands/test/sbom/index.ts
  • deps/compliance/sbom/src/serializeCycloneDx.ts
  • deps/compliance/sbom/test/serializeCycloneDx.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • deps/compliance/commands/test/sbom/index.ts
  • deps/compliance/sbom/src/serializeCycloneDx.ts
  • deps/compliance/sbom/test/serializeCycloneDx.test.ts

📝 Walkthrough

Walkthrough

serializeCycloneDx.ts now imports DepType and sets scope: 'excluded' on CycloneDX components whose depType is DevOnly, along with the cdx:npm:package:development taxonomy marker. New unit tests in serializeCycloneDx.test.ts and an integration test in the commands test suite verify the behavior. A changeset documents the change.

Changes

CycloneDX dev scope exclusion

Layer / File(s) Summary
serializeCycloneDx: DevOnly → scope excluded
deps/compliance/sbom/src/serializeCycloneDx.ts
Imports DepType from @pnpm/lockfile.detect-dep-types and adds a conditional assigning scope: 'excluded' and the cdx:npm:package:development property to components whose depType equals DepType.DevOnly; other dep types are left without a scope field.
Unit & integration tests + changeset
deps/compliance/sbom/test/serializeCycloneDx.test.ts, deps/compliance/commands/test/sbom/index.ts, .changeset/sbom-dev-scope-excluded.md
Unit tests verify DevOnly components emit scope: "excluded" and DevAndProd components emit no scope; an integration test confirms typescript is excluded and is-positive is scopeless in lockfile-only CycloneDX output; changeset records the minor version bumps.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Hop hop, the dev deps now wear a badge,
excluded in CycloneDX, page by page.
Tests check typescript stays out of prod's gate,
While is-positive runs — it's runtime, first-rate!
A scope for each bunny, assigned just right~ 🌿

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: marking dev-only components with CycloneDX 'excluded' scope in pnpm sbom output.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

ESLint install failed. For unrecoverable errors, disable the tool in CodeRabbit configuration.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Saturate Saturate force-pushed the feat/sbom-dev-scope-excluded branch from b6f4c31 to cacedc2 Compare June 16, 2026 08:27
@Saturate Saturate marked this pull request as ready for review June 16, 2026 08:27
@Saturate Saturate requested a review from zkochan as a code owner June 16, 2026 08:27
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 16, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Grey Divider

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Grey Divider

Previous review results

Review updated until commit 7acb4ad

Results up to commit cacedc2


🐞 Bugs (0) 📘 Rule violations (0) 📎 Requirement gaps (0)

Great, no issues found!

Qodo reviewed your code and found no material issues that require review

Qodo Logo

@qodo-free-for-open-source-projects

Copy link
Copy Markdown

PR Summary by Qodo

sbom: mark dev-only CycloneDX components as scope="excluded"
✨ Enhancement 🧪 Tests 📝 Documentation 🕐 20-40 Minutes

Grey Divider

Walkthroughs

Description
• Mark CycloneDX components reachable only via devDependencies as scope: "excluded".
• Keep runtime-reachable components (prod/dev+prod/installed optional) at default required scope.
• Add unit and command-level coverage for scope behavior in CycloneDX output.
Diagram
graph TD
  A(["pnpm sbom"]) --> B["SBOM builder"] --> C["SbomResult (depType)"] --> D["serializeCycloneDx.ts"] --> E["CycloneDX JSON"] --> F{{"Dependency-Track"}}
  G["DepType enum"] --> D

  subgraph Legend
    direction LR
    _cli(["CLI entrypoint"]) ~~~ _mod["Module"] ~~~ _ext{{"External consumer"}}
  end
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Use CycloneDX `properties` to encode dev/prod reachability
  • ➕ Does not overload scope semantics if consumers interpret scope differently
  • ➕ Can encode richer states (dev/prod/optional/peer) without spec constraints
  • ➖ Non-standard; most consumers (e.g., Dependency-Track) won’t act on it by default
  • ➖ Harder to achieve interoperability across tooling
2. Split outputs into separate BOMs (prod BOM vs dev BOM)
  • ➕ Clear separation; avoids per-component scope interpretation entirely
  • ➕ Aligns well with distinct deployment contexts
  • ➖ More UX/CLI surface area and operational overhead for users
  • ➖ Consumers must support BOM comparison/merging to get a full picture
3. Mark dev deps as CycloneDX `scope: optional`
  • ➕ Uses a standard CycloneDX field
  • ➖ Spec semantics don’t match installed optionalDependencies vs dev-only usage
  • ➖ Likely to mislead consumers into thinking components are not installed

Recommendation: The current approach (map DevOnly to CycloneDX scope: "excluded" and omit scope otherwise) is the best interoperability tradeoff: it uses a standard field with spec-aligned semantics and requires no consumer-specific extensions. The alternatives either reduce compatibility (custom properties), increase operational complexity (multiple BOMs), or misrepresent semantics (optional).

Grey Divider

File Changes

Enhancement (1)
serializeCycloneDx.ts Set CycloneDX 'scope: "excluded"' for DevOnly components +12/-0

Set CycloneDX 'scope: "excluded"' for DevOnly components

• Imports 'DepType' and maps 'DepType.DevOnly' components to CycloneDX 'scope: "excluded"'. Leaves prod/dev+prod and installed optional dependencies without a scope field so they default to 'required'.

deps/compliance/sbom/src/serializeCycloneDx.ts


Tests (2)
index.ts Add command test asserting dev-only CycloneDX components are 'excluded' +24/-0

Add command test asserting dev-only CycloneDX components are 'excluded'

• Introduces an integration-style command test that runs 'pnpm sbom' in CycloneDX mode and asserts a dev-only dependency gets 'scope: "excluded"'. Also verifies a production dependency omits 'scope' (default required).

deps/compliance/commands/test/sbom/index.ts


serializeCycloneDx.test.ts Add unit coverage for dev-only vs runtime-reachable CycloneDX scope +20/-0

Add unit coverage for dev-only vs runtime-reachable CycloneDX scope

• Adds unit tests ensuring dev-only components serialize with 'scope: "excluded"' while prod components omit scope. Also covers the DevAndProd case to confirm runtime reachability keeps the default required scope.

deps/compliance/sbom/test/serializeCycloneDx.test.ts


Documentation (1)
sbom-dev-scope-excluded.md Document CycloneDX dev-only scope behavior in changeset +6/-0

Document CycloneDX dev-only scope behavior in changeset

• Adds a changeset announcing that 'pnpm sbom' now emits 'scope: "excluded"' for components reachable only through 'devDependencies'. Notes that runtime-reachable components keep the default required behavior (scope omitted).

.changeset/sbom-dev-scope-excluded.md


Grey Divider

Qodo Logo

Components reachable only through devDependencies now get
`scope: "excluded"` plus the `cdx:npm:package:development` property in
CycloneDX output. The `excluded` scope documents non-runtime/test usage
(valid in every exported spec version, 1.5/1.6/1.7); the property is the
CycloneDX npm-taxonomy marker emitted by `@cyclonedx/cyclonedx-npm`, so
both modern and existing consumers are covered. Runtime-reachable
components (ProdOnly, DevAndProd, and installed optionalDependencies)
omit both and default to `required`.
@Saturate Saturate force-pushed the feat/sbom-dev-scope-excluded branch from cacedc2 to 7acb4ad Compare June 16, 2026 08:52
@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jun 16, 2026

Copy link
Copy Markdown

Code review by qodo was updated up to the latest commit 7acb4ad

@zkochan zkochan merged commit dcededc into pnpm:main Jun 16, 2026
17 of 18 checks passed
@Saturate Saturate deleted the feat/sbom-dev-scope-excluded branch June 16, 2026 20:24
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