Skip to content

Conversation

@pboling
Copy link
Contributor

@pboling pboling commented Dec 21, 2025

✨ --include-metadata (metadata.tools) & --enrich-components

CLI and wiring

  • --include-metadata
    • When provided, metadata.tools identifies this producer:
        - vendor: CycloneDX
        - name: cyclonedx-ruby
        - version: the gem’s version
    • Emitted for both JSON and XML, and only when the selected spec supports metadata (>= 1.2).
  • --enrich-components
    • Updated Cyclonedx::BomBuilder to add:
      • CLI: --enrich-components to opt-in enrichment.
      • Pass include_enrichment to build_bom(...).
    • Note: This does not alter default outputs; enrichment only applies with the flag.
  • Help and README updated.

JSON and XML emission

  • Updated Cyclonedx::BomHelpers:
    • build_bom supports include_enrichment and passes it to both JSON and XML builders.
    • build_json_bom adds bom-ref and publisher via BomComponent when include_enrichment: true.
    • build_bom_xml adds:
      • bom-ref attribute on using purl.
      • first_author if authors are present (first item split on commas/ampersands).
    • Added a small _get helper to read properties from either Hash or OpenStruct-like objects.

Component shape

  • Updated Cyclonedx::BomComponent:
    • Added optional keyword parameter include_enrichment: false to hash_val.
    • When true, include:
      • "bom-ref": purl (if present)
      • "publisher": first author (if present)
    • Made property access robust across Hash/OpenStruct.
    • Ensured hashes is an array with an object { alg, content } as expected by existing specs.

Tests

  • features/metadata_tools.feature (integration)
  • spec/cyclonedx/metadata_tools_spec.rb (unit, offline-safe)
  • Added spec/cyclonedx/component_enrichment_spec.rb:
    • Verifies JSON has bom-ref and publisher when include_enrichment: true and omits them otherwise.
    • Verifies XML has bom-ref attribute and when include_enrichment: true and omits otherwise.

✨ --gem-server: Configurable Gem Server URL

Added --gem-server flag to allow users to specify a custom gem server
for fetching gem metadata instead of using the hardcoded default.

CLI changes

  • Added --gem-server URL option in Cyclonedx::BomBuilder command-line parser
  • Stores custom server URL in @options[:gem_server] for use during BOM generation
  • When specified, passes the custom gem_server to get_gem() calls
  • Defaults to gem.coop when not specified, maintaining backward compatibility

Core implementation

  • Modified Cyclonedx::BomHelpers.get_gem to accept optional gem_server parameter
    • Defaults to 'https://gem.coop' when nil
    • Strips trailing slashes from gem_server URLs for consistency
    • Constructs gem metadata API URL using provided server
  • Updated get_gem call in bom_builder.rb (line 222) to pass @options[:gem_server]

Tests

Unit tests (spec/cyclonedx/bom_helpers_spec.rb):

  • Validates default behavior uses gem.coop when gem_server is not provided or nil
  • Verifies custom gem server URLs are used correctly
  • Tests trailing slash removal from custom server URLs
  • Confirms rubygems.org works as a custom server
  • Maintains existing error handling tests

Cucumber tests (features/gem_server.feature):

  • Validates default gem.coop behavior when --gem-server not specified
  • Tests custom gem server with https://rubygems.org
  • Tests custom gem server with trailing slash normalization
  • Verifies help text displays the --gem-server option

Use cases

Users can now:

Signed-off-by: Peter H. Boling [email protected]

- make setup, specs_list private methods

Signed-off-by: Peter H. Boling <[email protected]>
- When provided, metadata.tools identifies this producer:
  - vendor: CycloneDX
  - name: cyclonedx-ruby
  - version: the gem’s version
- Emitted for both JSON and XML, and only when the selected spec supports metadata (>= 1.2).
- Help and README updated.

- features/metadata_tools.feature (integration)
- spec/cyclonedx/metadata_tools_spec.rb (unit, offline-safe)

Signed-off-by: Peter H. Boling <[email protected]>
- Updated Cyclonedx::BomBuilder to add:
  - CLI: --enrich-components to opt-in enrichment.
  - Pass include_enrichment to build_bom(...).
- Note: This does not alter default outputs; enrichment only applies with the flag.

- Updated Cyclonedx::BomHelpers:
  - build_bom supports include_enrichment and passes it to both JSON and XML builders.
  - build_json_bom adds bom-ref and publisher via BomComponent when include_enrichment: true.
  - build_bom_xml adds:
    - bom-ref attribute on <component> using purl.
    - <publisher>first_author</publisher> if authors are present (first item split on commas/ampersands).
  - Added a small _get helper to read properties from either Hash or OpenStruct-like objects.

- Updated Cyclonedx::BomComponent:
  - Added optional keyword parameter include_enrichment: false to hash_val.
  - When true, include:
    - "bom-ref": purl (if present)
    - "publisher": first author (if present)
  - Made property access robust across Hash/OpenStruct.
  - Ensured hashes is an array with an object { alg, content } as expected by existing specs.

- Added spec/cyclonedx/component_enrichment_spec.rb:
  - Verifies JSON has bom-ref and publisher when include_enrichment: true and omits them otherwise.
  - Verifies XML has bom-ref attribute and <publisher> when include_enrichment: true and omits otherwise.

Signed-off-by: Peter H. Boling <[email protected]>
- Fix link to renamed LICENSE => LICENSE.txt

Signed-off-by: Peter H. Boling <[email protected]>
Signed-off-by: Peter H. Boling <[email protected]>
Added --gem-server flag to allow users to specify a custom gem server
for fetching gem metadata instead of using the hardcoded default.

- Added --gem-server URL option in Cyclonedx::BomBuilder command-line parser
- Stores custom server URL in @options[:gem_server] for use during BOM generation
- When specified, passes the custom gem_server to get_gem() calls
- Defaults to gem.coop when not specified, maintaining backward compatibility

- Modified Cyclonedx::BomHelpers.get_gem to accept optional gem_server parameter
  - Defaults to 'https://gem.coop' when nil
  - Strips trailing slashes from gem_server URLs for consistency
  - Constructs gem metadata API URL using provided server
- Updated get_gem call in bom_builder.rb (line 222) to pass @options[:gem_server]

Unit tests (spec/cyclonedx/bom_helpers_spec.rb):
- Validates default behavior uses gem.coop when gem_server is not provided or nil
- Verifies custom gem server URLs are used correctly
- Tests trailing slash removal from custom server URLs
- Confirms rubygems.org works as a custom server
- Maintains existing error handling tests

Cucumber tests (features/gem_server.feature):
- Validates default gem.coop behavior when --gem-server not specified
- Tests custom gem server with https://rubygems.org
- Tests custom gem server with trailing slash normalization
- Verifies help text displays the --gem-server option

Users can now:
- Use private gem servers: --gem-server https://internal.company.com
- Use rubygems.org directly: --gem-server https://rubygems.org
- Use alternate public mirrors
- Default to gem.coop without any configuration change

Signed-off-by: Peter H. Boling <[email protected]>
Copilot AI review requested due to automatic review settings December 21, 2025 11:30
@pboling pboling requested a review from a team as a code owner December 21, 2025 11:30
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds support for CycloneDX spec version selection via the --spec-version flag and introduces several new CLI options to enhance BOM generation capabilities. The changes include optional metadata embedding, component enrichment features, and configurable gem server selection.

Key Changes

  • Added three new CLI flags: --include-metadata (embeds tool identity in BOMs), --enrich-components (adds bom-ref and publisher fields), and --gem-server (allows custom gem repository configuration)
  • Changed default gem server from rubygems.org to gem.coop across the codebase
  • Refactored gem object field access to support both Hash and OpenStruct-like objects

Reviewed changes

Copilot reviewed 15 out of 17 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
lib/cyclonedx/bom_builder.rb Added CLI option parsing for new flags and spec version validation logic
lib/cyclonedx/bom_helpers.rb Implemented metadata.tools support, component enrichment, custom gem server configuration, and helper methods for object field access
lib/cyclonedx/bom_component.rb Refactored to use a fetch method for accessing gem object fields and added enrichment support
lib/cyclonedx/ruby.rb Reordered requires to resolve dependency issues
spec/cyclonedx/metadata_tools_spec.rb Added unit tests for metadata.tools emission in JSON and XML formats
spec/cyclonedx/component_enrichment_spec.rb Added unit tests for component enrichment features (bom-ref and publisher)
spec/cyclonedx/bom_helpers_spec.rb Added tests for gem server configuration including default, custom, and error handling scenarios
features/metadata_tools.feature Added Cucumber scenarios testing metadata inclusion in both JSON and XML BOMs
features/gem_server.feature Added Cucumber scenarios for custom gem server functionality
features/help.feature Updated help text to reflect new CLI options
README.md Updated documentation with new CLI flags and changed gem badge link
Gemfile, Gemfile.lock, features/fixtures/simple/Gemfile Changed gem source from rubygems.org to gem.coop
CODE_OF_CONDUCT.md Added Contributor Covenant Code of Conduct
.rubocop_todo.yml Updated RuboCop configuration to reflect new code metrics

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@pboling pboling changed the title ✨ Support spec version selection via --spec_version ✨ --spec_version, --include-metadata, --enrich-components Dec 21, 2025
@jkowalleck jkowalleck added the enhancement New feature or request label Dec 21, 2025
@pboling pboling changed the title ✨ --spec_version, --include-metadata, --enrich-components ✨ --spec-version, --include-metadata, --enrich-components Dec 21, 2025
@pboling pboling changed the title ✨ --spec-version, --include-metadata, --enrich-components ✨ --spec-version, --include-metadata, --enrich-components, --gem-server Dec 21, 2025
@pboling
Copy link
Contributor Author

pboling commented Dec 21, 2025

It isn't clear to me why this gem depends on activesupport. It seems to be something related to the Hash core extensions. I am going to try to narrow it down and remove the dependency in a new PR.

@pboling
Copy link
Contributor Author

pboling commented Dec 22, 2025

@jkowalleck @andrew Done, specs fixed, script fixed, everything fixed.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 17 out of 19 changed files in this pull request and generated 3 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +32 to +37
def _get(obj, key)
if obj.respond_to?(:[]) && obj[key]
obj[key]
elsif obj.respond_to?(key)
obj.public_send(key)
end
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The logic in the _get method has a bug. The condition obj.respond_to?(:[]) && obj[key] will return nil if the key exists but has a falsy value (nil, false, 0, empty string). The method should check for key existence rather than truthiness of the value. Consider using obj.respond_to?(:key?) && obj.key?(key) for Hash-like objects, or check the presence separately from retrieving the value.

Copilot uses AI. Check for mistakes.
Comment on lines +117 to +126
if @project_path
begin
@logger.info("Changing directory to Ruby project directory located at #{@provided_path}")
Dir.chdir @project_path
rescue StandardError => e
@logger.error("Unable to change directory to Ruby project directory located at #{@provided_path}. #{e.message}: #{Array(e.backtrace).join("\n")}")
abort
end
else
@logger.error("project_path could not be determined. path provided was: #{@options[:path]}")
Copy link

Copilot AI Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The condition if @project_path on line 117 is redundant. At line 114, @project_path is assigned File.expand_path(@options[:path]), and since we've already validated that @options[:path] exists (line 103) and is a directory (line 108), File.expand_path will never return nil. The else branch on lines 125-128 is unreachable code.

Suggested change
if @project_path
begin
@logger.info("Changing directory to Ruby project directory located at #{@provided_path}")
Dir.chdir @project_path
rescue StandardError => e
@logger.error("Unable to change directory to Ruby project directory located at #{@provided_path}. #{e.message}: #{Array(e.backtrace).join("\n")}")
abort
end
else
@logger.error("project_path could not be determined. path provided was: #{@options[:path]}")
begin
@logger.info("Changing directory to Ruby project directory located at #{@provided_path}")
Dir.chdir @project_path
rescue StandardError => e
@logger.error("Unable to change directory to Ruby project directory located at #{@provided_path}. #{e.message}: #{Array(e.backtrace).join("\n")}")

Copilot uses AI. Check for mistakes.
Copy link
Member

@jkowalleck jkowalleck left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hm, i find the review hard. it is a mix of multiple scopes, goals, and therefore, i'd decline the review.

please divide this PR into multiple PRs - each with its own scope.

Think like this: if I wanted a change (feature/fix/etc), then I would write one ticket/request per change, and therefore each change will have its own discussion of expected outcome/cases/non-cases/etc, its own pull request, its own domain experts, its own review - and therefore its own pace.
Mixing multiple things in one PR makes review cumbersome and slow. - which blocks rapid evolution.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thats a copy of https://github.com/CycloneDX/.github/blob/master/CODE_OF_CONDUCT.md ?

Anyway, it is not needed, as the original one appliesalready - see https://github.com/CycloneDX/cyclonedx-ruby-gem?tab=coc-ov-file#readme

so, could this file be removed, then?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like it!

# frozen_string_literal: true

source 'https://rubygems.org'
source 'https://gem.coop/'
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yep,
moving away from the money-driven ting to an alternative, free, community-driven one - 👍

# CycloneDX Ruby Gem

[![Gem Version](https://img.shields.io/gem/v/cyclonedx-ruby?logo=rubygems&logoColor=white)](https://rubygems.org/gems/cyclonedx-ruby)
[![Gem Version](https://img.shields.io/gem/v/cyclonedx-ruby?logo=rubygems&logoColor=white)](https://bestgems.org/gems/cyclonedx-ruby)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?
the button linking to the primary download source is very common,
why link to some download stats page instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a preference to not link to RG.O. If you prefer the old way I'll leave it alone! I like the additional information on bestgems, which is also a community resource, but I'm not strongly inclined either way.

`-o, --output bom_file_path` Path to output the bom file
`-f, --format bom_output_format` Output format for bom. Supported: xml (default), json
`-s, --spec-version version` CycloneDX spec version to target (default: 1.7). Supported: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7
`--include-metadata` Include metadata.tools identifying cyclonedx-ruby as the producer
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i dont see a reason to hide this feature behind a feature switch.
it should be always the case: add the tool itself to metadata - whatsoever.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree, will make it built-in default for spec versions that support it.

Permission to modify and redistribute is granted under the terms of the Apache 2.0 license. See the [LICENSE] file for the full license.

[License]: https://github.com/CycloneDX/cyclonedx-ruby-gem/blob/master/LICENSE
[License]: https://github.com/CycloneDX/cyclonedx-ruby-gem/blob/master/LICENSE.txt
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this fix is out of scope, and was superseded by #51

# JSON at CycloneDX 1.2 to a custom path
cyclonedx-ruby -p /path/to/ruby/project -f json -s 1.2 -o bom/out.json

# Include producer metadata and validate
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[...] and validate

where comes the validate from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's vestigial, it was a really big rebase getting it off of the validation feature. I'll pull this apart into separate PRs, and make metadata inclusion a built-in default for spec versions that support it (v1.2+).


$stdout.sync = true

require "rubygems"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed now?

Copy link
Contributor Author

@pboling pboling Dec 22, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right to question that. It should not be needed. It wasn't working without it on my machine, but I think it may just be an edge case of attempting to run it from the source checkout versus a real installed gem that has the benefit of the bin script wrapper (which already does require "rubygems") and the installed gems lookup being ahead of other scripts. Since I don't have the gem installed locally, and am just working on the source I needed it to get it to run, until I built and installed the gem from the source. For normal usage it is not needed at all, and I forgot I had added it for testing.

Will remove.

Co-authored-by: Copilot <[email protected]>
Signed-off-by: |7eter l-|. l3oling <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants