Skip to content

Add BugBot license scanning config#2515

Merged
justin808 merged 2 commits intomasterfrom
jg/license-scan-rake-task
Mar 4, 2026
Merged

Add BugBot license scanning config#2515
justin808 merged 2 commits intomasterfrom
jg/license-scan-rake-task

Conversation

@justin808
Copy link
Copy Markdown
Member

@justin808 justin808 commented Mar 4, 2026

Summary

  • Add .cursor/BUGBOT.md to configure Cursor BugBot for automated license scanning on PRs
  • When a PR modifies dependency files (package.json, Gemfile, etc.), BugBot will flag any new/upgraded dependency with GPL-2.0, GPL-3.0, or AGPL-3.0 licenses

The open-source ROR dependency tree is clean (all MIT/Apache-2.0/BSD). This config ensures it stays that way.

Test plan

  • BugBot config follows documented format
  • CI passes

🤖 Generated with Claude Code

Add `rake react_on_rails:scan_licenses` that scans Ruby gems (via
Bundler) and JS packages (via pnpm/yarn/npm) for disallowed copyleft
licenses (GPL-2.0, GPL-3.0, AGPL-3.0). Multi-licensed packages with
a permissive option are reported as warnings, not violations.

Also add .cursor/BUGBOT.md configuration for automated PR license
scanning via Cursor's BugBot.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 4, 2026

Walkthrough

This pull request adds a comprehensive dependency license scanning system for React on Rails. It includes a new LicenseScanner utility class that validates licenses for both Ruby gems and JavaScript packages, a Rake task for executing scans with JSON/text output formatting, BugBot integration for automated compliance checks, and corresponding test coverage.

Changes

Cohort / File(s) Summary
BugBot Configuration
.cursor/BUGBOT.md
Adds licensing rules for React on Rails PRs. Detects modifications to dependency files (package.json, pnpm-lock.yaml, yarn.lock, Gemfile, Gemfile.lock, gemspec) and triggers License Scan, creating blocking issues for GPL-2.0, GPL-3.0, or AGPL-3.0 licenses with compliance and security labels.
License Scanner Core
react_on_rails/lib/react_on_rails/license_scanner.rb
Introduces LicenseScanner class with DISALLOWED_LICENSES (GPL/AGPL family) and PERMISSIVE_LICENSES whitelists. Orchestrates scanning of RubyGems and JavaScript packages, evaluates licenses, and collects violations and warnings into Result and Violation Struct objects.
Rake Task
react_on_rails/lib/tasks/license_scan.rake
Adds react_on_rails:scan_licenses task that invokes LicenseScanner, formats output as JSON or text with colored styling, and exits with code 1 on violations. Includes helper methods for violation reporting and summary generation.
Test Coverage
react_on_rails/spec/react_on_rails/license_scanner_spec.rb
RSpec tests validating scanner functionality, license constant definitions, and proper handling of multi-licensed gems (e.g., MIT OR GPL treated as warning when permissive option exists).

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 Hop along the rails we go,
Licenses checked both high and low,
GPL we must refuse,
Permissive paths we'll gladly choose,
Compliance hops with noble cheer!

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'Add BugBot license scanning config' accurately describes the main change: adding BugBot configuration for automated license scanning, which aligns with the .cursor/BUGBOT.md file addition and the overall scanning infrastructure.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch jg/license-scan-rake-task

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.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Mar 4, 2026

Greptile Summary

This PR adds a rake react_on_rails:scan_licenses task that scans Ruby gems (via Bundler) and JS packages (via pnpm/yarn/npm) for disallowed copyleft licenses, and adds a Cursor BugBot configuration for automated PR-level license scanning.

The core scanning logic in LicenseScanner is well-structured — multi-license handling (warning vs. violation), lockfile-based package manager detection, and the SPDX-aware normalization are all correct. However, there is one critical bug and a few notable design issues:

  • Critical: The Rainbow fallback in license_scan.rake (lines 5–13) overrides Kernel#Rainbow to return a plain String. All output helpers immediately chain color/style methods (.bold, .red, .cyan, .yellow, .green) on the return value. Plain String in Ruby has none of these methods, so any environment without the Rainbow gem will raise NoMethodError before printing any output. The existing doctor.rake solves this correctly using a SimpleColorWrapper class — the same pattern should be applied here.
  • Helper methods (print_json_result, print_text_result, etc.) are defined at the top level of the rake file, adding them to Object globally, which risks name collisions with other rake tasks or gems.
  • LicenseScanner#scan does not reset accumulated state between calls, making repeated invocations on the same instance produce inflated counts.
  • The spec suite contains only integration tests against the live gem tree; JS parsing and license-classification logic have no isolated unit test coverage.

Confidence Score: 2/5

  • Not safe to merge as-is — the Rainbow fallback will crash at runtime on any system without the Rainbow gem installed.
  • Score lowered primarily because of the critical Rainbow fallback bug in license_scan.rake: returning a plain String and then calling .bold/.red/.cyan on it will raise NoMethodError in any environment where the rainbow gem is absent. Since this is the primary output path for the new feature, it makes the task unreliable. The core LicenseScanner logic itself is solid, but the presentation layer needs to be fixed before the task is production-ready.
  • react_on_rails/lib/tasks/license_scan.rake requires attention for the Rainbow fallback fix.

Important Files Changed

Filename Overview
react_on_rails/lib/tasks/license_scan.rake New rake task for license scanning; contains a critical bug where the Rainbow fallback returns a plain String, causing NoMethodError when chained color/style methods (.bold, .red, .cyan) are called without the Rainbow gem installed. Also defines helper methods in global Object scope.
react_on_rails/lib/react_on_rails/license_scanner.rb Core license scanning logic for Ruby gems and JS packages; well-structured with correct multi-license handling. Minor issue: scan() accumulates state on repeated calls due to no reset between invocations.
react_on_rails/spec/react_on_rails/license_scanner_spec.rb All 5 tests are integration tests against the live gem tree; no unit tests for JS parsing or license classification logic, and the diff-lcs warning assertion is conditionally skipped when the gem isn't present.
.cursor/BUGBOT.md New Cursor BugBot configuration for automated PR license scanning; straightforward documentation file with no code issues.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["rake react_on_rails:scan_licenses"] --> B["LicenseScanner#scan"]
    B --> C["scan_ruby_gems\n(Bundler.definition.resolve)"]
    B --> D["scan_js_packages"]
    D --> E["find_js_lockfile\n(pnpm/yarn/npm)"]
    E -->|"pnpm-lock.yaml"| F["pnpm licenses list --json"]
    E -->|"yarn.lock"| G["yarn licenses list --json"]
    E -->|"package-lock.json"| H["npx license-checker --json"]
    F --> I["parse_pnpm_licenses"]
    G --> J["parse_yarn_licenses"]
    H --> K["parse_npm_licenses"]
    C --> L["check_licenses"]
    I --> L
    J --> L
    K --> L
    L --> M{{"disallowed?"}}
    M -->|"No"| N["skip"]
    M -->|"Yes + permissive also present"| O["@warnings"]
    M -->|"Yes, no permissive"| P["@violations"]
    O --> Q["Result"]
    P --> Q
    N --> Q
    Q --> R{{"FORMAT=json?"}}
    R -->|"Yes"| S["print_json_result"]
    R -->|"No"| T["print_text_result\n⚠️ Rainbow fallback bug here"]
    Q --> U{{"violations.any?"}}
    U -->|"Yes"| V["exit(1)"]
    U -->|"No"| W["exit(0)"]
Loading

Last reviewed commit: 97049c7

Comment on lines +5 to +13
begin
require "rainbow"
rescue LoadError
# Fallback if Rainbow is not available
module Kernel
def Rainbow(text) # rubocop:disable Naming/MethodName
text
end
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rainbow fallback crashes on chained method calls

When rainbow is not available, this fallback returns a plain String from Rainbow(text). However, the code immediately chains Rainbow-specific methods on the result — for example Rainbow("\nReact on Rails License Scan").bold (line 54), Rainbow("=" * 40).cyan (line 55), Rainbow("VIOLATIONS…").red.bold (line 64), etc. Plain String in Ruby has no .bold, .red, .cyan, or .yellow methods, so any run without the Rainbow gem will immediately raise NoMethodError.

Compare with doctor.rake, which uses a SimpleColorWrapper class that absorbs all chained method calls via method_missing and returns self, then produces the original text via to_s. The same pattern should be used here:

begin
  require "rainbow"
rescue LoadError
  class Rainbow
    def self.method_missing(_method, text)
      SimpleColorWrapper.new(text)
    end

    def self.respond_to_missing?(_method, _include_private = false)
      true
    end
  end

  class SimpleColorWrapper
    def initialize(text)
      @text = text
    end

    def method_missing(_method, *_args)
      self
    end

    def respond_to_missing?(_method, _include_private = false)
      true
    end

    def to_s
      @text
    end
  end
end

Comment on lines +33 to +89
def print_json_result(result)
require "json"
output = {
status: result.violations.any? ? "fail" : "pass",
scanned: result.scanned_count,
violations: result.violations.map { |v| violation_hash(v) },
warnings: result.warnings.map { |v| violation_hash(v) }
}
puts JSON.pretty_generate(output)
end

def violation_hash(violation)
{
name: violation.name,
version: violation.version,
licenses: violation.licenses,
source: violation.source
}
end

def print_text_result(result)
puts Rainbow("\nReact on Rails License Scan").bold
puts Rainbow("=" * 40).cyan
puts "Scanned #{result.scanned_count} dependencies\n\n"

print_violations(result.violations) if result.violations.any?
print_warnings(result.warnings) if result.warnings.any?
print_summary(result.violations)
end

def print_violations(violations)
puts Rainbow("VIOLATIONS (disallowed licenses):").red.bold
violations.each do |v|
puts Rainbow(" ✗ #{v.name} #{v.version} (#{v.source})").red
puts " Licenses: #{v.licenses.join(', ')}"
end
puts
end

def print_warnings(warnings)
puts Rainbow("WARNINGS (multi-licensed with copyleft option):").yellow.bold
warnings.each do |v|
puts Rainbow(" ⚠ #{v.name} #{v.version} (#{v.source})").yellow
puts " Licenses: #{v.licenses.join(', ')} (permissive option available)"
end
puts
end

def print_summary(violations)
if violations.empty?
puts Rainbow("✓ No disallowed licenses found").green.bold
else
puts Rainbow("✗ #{violations.size} violation(s) found").red.bold
puts " Disallowed: #{ReactOnRails::LicenseScanner::DISALLOWED_LICENSES.join(', ')}"
end
puts
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Helper methods defined in global Object scope

The methods print_json_result, violation_hash, print_text_result, print_violations, print_warnings, and print_summary are defined at the top level of the file, which means they land on Ruby's Object class. This makes them available everywhere and can silently conflict with identically named methods in other rake files or gems. In particular, print_summary is a very generic name.

Consider wrapping these in a private module or namespace, similar to how doctor.rake could be refactored, or use a plain Ruby class/module:

module ReactOnRails
  module LicenseScanFormatter
    module_function

    def print_json_result(result)
      # ...
    end
    # ...
  end
end

and call them as ReactOnRails::LicenseScanFormatter.print_json_result(result) inside the task block. This matches the module-namespaced style used throughout the rest of the codebase.

Comment on lines +43 to +47
def scan
scan_ruby_gems
scan_js_packages
Result.new(violations: @violations, warnings: @warnings, scanned_count: @scanned_count)
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

scan accumulates state on repeated calls

@violations, @warnings, and @scanned_count are initialized in initialize but never reset before scanning. If scan is called more than once on the same instance, results will be doubled. This is not a problem in the current rake task (one instance, one call), but it makes the API surprising and could cause flaky tests if a test helper reuses a scanner instance.

Consider resetting at the start of scan:

def scan
  @violations = []
  @warnings = []
  @scanned_count = 0
  scan_ruby_gems
  scan_js_packages
  Result.new(violations: @violations, warnings: @warnings, scanned_count: @scanned_count)
end

Comment on lines +1 to +51
# frozen_string_literal: true

require_relative "spec_helper"
require "react_on_rails/license_scanner"

RSpec.describe ReactOnRails::LicenseScanner do
subject(:scanner) { described_class.new }

describe "#scan" do
it "scans ruby gems and returns a result" do
result = scanner.scan
expect(result).to be_a(described_class::Result)
expect(result.scanned_count).to be_positive
expect(result.violations).to be_an(Array)
expect(result.warnings).to be_an(Array)
end

it "reports no violations for the react_on_rails dependency tree" do
result = scanner.scan
expect(result.violations).to be_empty
end
end

describe "license classification" do
let(:result_struct) { described_class::Result }

describe "DISALLOWED_LICENSES" do
it "includes GPL and AGPL variants" do
expect(described_class::DISALLOWED_LICENSES).to include("GPL-2.0", "GPL-3.0", "AGPL-3.0")
end
end

describe "PERMISSIVE_LICENSES" do
it "includes common permissive licenses" do
expect(described_class::PERMISSIVE_LICENSES).to include("MIT", "Apache-2.0", "BSD-2-Clause")
end
end
end

describe "multi-licensed gems" do
it "treats dual MIT/GPL as a warning, not a violation" do
result = scanner.scan

gpl_violations = result.violations.select { |v| v.licenses.any? { |l| l.include?("GPL") } }
expect(gpl_violations).to be_empty

diff_lcs_warning = result.warnings.find { |w| w.name == "diff-lcs" }
expect(diff_lcs_warning.licenses).to include("MIT") if diff_lcs_warning
end
end
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All tests are integration tests with no unit coverage for parsing logic

Every example in this spec calls scanner.scan, which hits the live Bundler definition and the real gem specs. This means:

  • JS package parsing (parse_pnpm_licenses, parse_npm_licenses, parse_yarn_licenses) has zero test coverage.
  • The check_licenses classification logic (violation vs. warning) is only covered indirectly through the real gem tree.
  • The suite will fail or produce different results on a machine where diff-lcs is not installed (the diff_lcs_warning assertion is guarded by if diff_lcs_warning, effectively skipping the check when the gem isn't present).

Adding unit tests that feed crafted data to parse_* helpers and check_licenses directly would give reproducible, fast coverage of the actual logic.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 97049c7187

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

cmd = LICENSE_COMMANDS[package_manager]

output = `#{cmd}`
return nil unless $CHILD_STATUS&.success?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Use $? to detect JS command failures

The JS scan path never runs because fetch_js_licenses checks $CHILD_STATUS, which is nil unless English is explicitly required; in normal Ruby/Rails execution only $? is updated after backticks. As written, return nil unless $CHILD_STATUS&.success? always returns early, so JS dependencies are silently skipped and the task can report a passing scan while missing disallowed npm/pnpm/yarn licenses.

Useful? React with 👍 / 👎.

Comment on lines +177 to +181
DISALLOWED_LICENSES.any? { |d| license.start_with?(d) || license.casecmp(d).zero? }
end

def permissive_license?(license)
PERMISSIVE_LICENSES.any? { |p| license.casecmp(p).zero? }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Parse SPDX license expressions before matching

License classification assumes each entry is a single token, but npm/yarn data commonly includes SPDX expressions like MIT OR GPL-3.0 or GPL-2.0 OR MIT. With the current start_with?/exact-match checks, GPL may be missed when it is not the first token, and permissive alternatives are not recognized when bundled in one expression, causing false negatives or incorrect hard violations.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Bugbot Autofix is ON, but it could not run because Privacy Mode (Legacy) is turned on. To enable Bugbot Autofix, switch your privacy mode in the Cursor dashboard.

def Rainbow(text) # rubocop:disable Naming/MethodName
text
end
end
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Rainbow fallback crashes on method chaining calls

Low Severity

The Rainbow fallback returns a raw String from Rainbow(text), but the rake task chains methods like .bold, .red, .cyan, .green, and .yellow on the result. String doesn't have these methods, so this would raise NoMethodError if Rainbow is unavailable. The existing codebase already solves this correctly in doctor.rb and doctor.rake using a SimpleColorWrapper class with method_missing that returns self for chaining.

Fix in Cursor Fix in Web

end

def disallowed_license?(license)
DISALLOWED_LICENSES.any? { |d| license.start_with?(d) || license.casecmp(d).zero? }
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Compound SPDX expressions cause order-dependent license detection

Medium Severity

The disallowed_license? method uses start_with? which causes inconsistent handling of compound SPDX expressions from JS packages. "GPL-3.0 OR MIT" is flagged as a violation (not a warning, since permissive_license? won't match the compound string), while "MIT OR GPL-3.0" passes undetected entirely. Detection depends on license order within the expression, and matched compounds are misclassified as violations instead of warnings.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (2)
react_on_rails/spec/react_on_rails/license_scanner_spec.rb (1)

47-49: Make the multi-license assertion strict to validate intended behavior.

Line 48 is conditional, so this example passes even when diff-lcs warning is missing. Assert presence first, then assert licenses.

💡 Proposed fix
       diff_lcs_warning = result.warnings.find { |w| w.name == "diff-lcs" }
-      expect(diff_lcs_warning.licenses).to include("MIT") if diff_lcs_warning
+      expect(diff_lcs_warning).not_to be_nil
+      expect(diff_lcs_warning.licenses).to include("MIT")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/spec/react_on_rails/license_scanner_spec.rb` around lines 47 -
49, The test currently uses a conditional that lets it pass when the "diff-lcs"
warning is missing; change it to first assert that a warning for "diff-lcs"
exists (e.g., expect(result.warnings.find { |w| w.name == "diff-lcs" }).not_to
be_nil or a similar presence assertion on result.warnings) and then assert that
that found object's licenses include "MIT" (use the same diff_lcs_warning
variable to perform expect(diff_lcs_warning.licenses).to include("MIT")),
removing the conditional so the test fails when the warning is absent.
.cursor/BUGBOT.md (1)

8-8: Align BugBot rule wording with scanner coverage to avoid policy drift.

Line 8 only names base IDs, while the scanner also blocks -only / -or-later variants. Consider mirroring the full set (or referencing scanner constants) so automation/docs stay consistent.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.cursor/BUGBOT.md at line 8, The rule text currently lists only base SPDX
IDs ("GPL-2.0, GPL-3.0, AGPL-3.0") but the scanner also matches "-only" and
"-or-later" variants, so update the wording in the line that currently reads "If
any new or upgraded dependency has a license in {GPL-2.0, GPL-3.0, AGPL-3.0},
then:" to either expand the set to explicitly include GPL-2.0-only,
GPL-2.0-or-later, GPL-3.0-only, GPL-3.0-or-later, AGPL-3.0-only,
AGPL-3.0-or-later, or replace it with a reference to the scanner's license
constants so the doc and automation stay in sync.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@react_on_rails/lib/react_on_rails/license_scanner.rb`:
- Line 77: The JS license scan currently silently skips on parsing failures:
replace the early return on licenses_json (the "return if licenses_json.nil?" in
license_scanner.rb) with raising a descriptive error so a parse failure surfaces
as a failed scan, and change the rescue that swallows the parser exception
(around the code that handles parser errors at the later rescue block) to either
re-raise the original exception or raise a new ParsingError that includes the
original exception message and context; ensure the exception propagates to the
caller so CI can detect the failure rather than reporting a false pass.
- Around line 159-181: The current matching in normalize_license /
disallowed_license? / permissive_license? treats the license string as a single
token and misses SPDX expressions like "MIT OR GPL-2.0"; update normalization to
return a normalized set of SPDX license identifiers by splitting/tokenizing on
SPDX operators (OR, AND, WITH, "+", "+", commas, whitespace) and extracting
identifier tokens (case-insensitive), then change disallowed_license? and
permissive_license? to check any token against DISALLOWED_LICENSES /
PERMISSIVE_LICENSES (using case-insensitive equality or starts_with where
appropriate for license exceptions) so composite expressions correctly detect
copyleft or disallowed components.
- Around line 107-112: The subprocess success check in fetch_js_licenses is
using $CHILD_STATUS without requiring the English library which makes it
unreliable; change the check to use the builtin $? instead of $CHILD_STATUS
(e.g., replace "$CHILD_STATUS&.success?" with "$?.success?") so the method
correctly detects command success after running the
LICENSE_COMMANDS[package_manager] backtick invocation; alternatively, if you
prefer the $CHILD_STATUS name, require 'English' at the top of the file, but do
not rely on safe navigation with a possibly nil alias.

In `@react_on_rails/lib/tasks/license_scan.rake`:
- Around line 9-13: The fallback Rainbow method in Kernel currently returns a
plain String which breaks callers that chain color methods (e.g.,
Rainbow(...).bold); update Kernel#Rainbow to return an object that responds to
the color/format chain (at least the methods used: bold, red, yellow, green,
underline, etc.) and delegates to the underlying string, returning the plain
string when converted (to_s/inspect); implement this via a small wrapper (e.g.,
a simple class or struct with defined color methods or method_missing that
returns self or the wrapped string) so chained calls do not raise NoMethodError
and ultimately yield the original text.

---

Nitpick comments:
In @.cursor/BUGBOT.md:
- Line 8: The rule text currently lists only base SPDX IDs ("GPL-2.0, GPL-3.0,
AGPL-3.0") but the scanner also matches "-only" and "-or-later" variants, so
update the wording in the line that currently reads "If any new or upgraded
dependency has a license in {GPL-2.0, GPL-3.0, AGPL-3.0}, then:" to either
expand the set to explicitly include GPL-2.0-only, GPL-2.0-or-later,
GPL-3.0-only, GPL-3.0-or-later, AGPL-3.0-only, AGPL-3.0-or-later, or replace it
with a reference to the scanner's license constants so the doc and automation
stay in sync.

In `@react_on_rails/spec/react_on_rails/license_scanner_spec.rb`:
- Around line 47-49: The test currently uses a conditional that lets it pass
when the "diff-lcs" warning is missing; change it to first assert that a warning
for "diff-lcs" exists (e.g., expect(result.warnings.find { |w| w.name ==
"diff-lcs" }).not_to be_nil or a similar presence assertion on result.warnings)
and then assert that that found object's licenses include "MIT" (use the same
diff_lcs_warning variable to perform expect(diff_lcs_warning.licenses).to
include("MIT")), removing the conditional so the test fails when the warning is
absent.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: f0255f90-3f4d-4cfe-b5f5-6cdb62750fe3

📥 Commits

Reviewing files that changed from the base of the PR and between dc4e0a3 and 97049c7.

📒 Files selected for processing (4)
  • .cursor/BUGBOT.md
  • react_on_rails/lib/react_on_rails/license_scanner.rb
  • react_on_rails/lib/tasks/license_scan.rake
  • react_on_rails/spec/react_on_rails/license_scanner_spec.rb


package_manager = detect_package_manager(lockfile)
licenses_json = fetch_js_licenses(package_manager)
return if licenses_json.nil?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

JS scan currently fails open on parse errors, which can produce false “pass” results.

Line 77 returns early when parsing fails, and Lines 115-117 swallow the parser error. That skips JS dependency checks without surfacing a failure signal.

💡 Proposed fix
-    rescue JSON::ParserError
-      nil
+    rescue JSON::ParserError => e
+      raise "Unable to parse #{package_manager} license output: #{e.message}"
     end

Also applies to: 115-117

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/react_on_rails/license_scanner.rb` at line 77, The JS
license scan currently silently skips on parsing failures: replace the early
return on licenses_json (the "return if licenses_json.nil?" in
license_scanner.rb) with raising a descriptive error so a parse failure surfaces
as a failed scan, and change the rescue that swallows the parser exception
(around the code that handles parser errors at the later rescue block) to either
re-raise the original exception or raise a new ParsingError that includes the
original exception message and context; ensure the exception propagates to the
caller so CI can detect the failure rather than reporting a false pass.

Comment on lines +107 to +112
def fetch_js_licenses(package_manager)
cmd = LICENSE_COMMANDS[package_manager]

output = `#{cmd}`
return nil unless $CHILD_STATUS&.success?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

Ruby $CHILD_STATUS vs $? global variable subprocess status require English

💡 Result:

In Ruby, $CHILD_STATUS and $? are the same value: they both refer to the exit status of the last executed child process, exposed as a Process::Status object (or nil if there isn’t one). (ruby-doc.org)

The difference is naming and availability:

  • $? is the built-in “cryptic” special global variable (always available). (ruby-doc.org)
  • $CHILD_STATUS is the English alias for $?, and it’s only available after you load the standard library English:
    require "English"
    p $CHILD_STATUS  # same as $?
    (ruby-doc.org)

Citations:


🏁 Script executed:

# Locate and examine the license_scanner.rb file
fd "license_scanner.rb" -x cat -n {}

Repository: shakacode/react_on_rails

Length of output: 6170


Use $? for subprocess success checks—$CHILD_STATUS requires English.

Line 111 uses $CHILD_STATUS without loading the English standard library, where it is defined as a readable alias. Without it, $CHILD_STATUS is nil, making the check unreliable. The safe navigation operator masks but doesn't fix the issue: nil&.success? returns nil, causing the function to silently skip license scanning even when the command succeeds.

Proposed fix
-      return nil unless $CHILD_STATUS&.success?
+      return nil unless $?.success?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/react_on_rails/license_scanner.rb` around lines 107 - 112,
The subprocess success check in fetch_js_licenses is using $CHILD_STATUS without
requiring the English library which makes it unreliable; change the check to use
the builtin $? instead of $CHILD_STATUS (e.g., replace "$CHILD_STATUS&.success?"
with "$?.success?") so the method correctly detects command success after
running the LICENSE_COMMANDS[package_manager] backtick invocation;
alternatively, if you prefer the $CHILD_STATUS name, require 'English' at the
top of the file, but do not rely on safe navigation with a possibly nil alias.

Comment on lines +159 to +181
normalized = licenses.map { |l| normalize_license(l) }

disallowed = normalized.select { |l| disallowed_license?(l) }
return if disallowed.empty?

has_permissive = normalized.any? { |l| permissive_license?(l) }
if has_permissive
@warnings << Violation.new(name: name, version: version, licenses: licenses, source: source)
else
@violations << Violation.new(name: name, version: version, licenses: licenses, source: source)
end
end

def normalize_license(license)
license.to_s.strip
end

def disallowed_license?(license)
DISALLOWED_LICENSES.any? { |d| license.start_with?(d) || license.casecmp(d).zero? }
end

def permissive_license?(license)
PERMISSIVE_LICENSES.any? { |p| license.casecmp(p).zero? }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Current license matching misses SPDX expressions and can misclassify copyleft packages.

Lines 159-181 compare full strings, so expressions like MIT OR GPL-2.0 are not tokenized correctly. This can hide disallowed licenses or classify permissive+copyleft combos incorrectly.

💡 Proposed fix
-      normalized = licenses.map { |l| normalize_license(l) }
+      normalized = licenses
+        .flat_map { |l| split_license_expression(normalize_license(l)) }
+        .uniq
@@
     def normalize_license(license)
       license.to_s.strip
     end
+
+    def split_license_expression(license)
+      license
+        .gsub(/[()]/, " ")
+        .split(/\s+(?:OR|AND|WITH)\s+|,|\/|\|/i)
+        .map(&:strip)
+        .reject(&:empty?)
+    end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/react_on_rails/license_scanner.rb` around lines 159 - 181,
The current matching in normalize_license / disallowed_license? /
permissive_license? treats the license string as a single token and misses SPDX
expressions like "MIT OR GPL-2.0"; update normalization to return a normalized
set of SPDX license identifiers by splitting/tokenizing on SPDX operators (OR,
AND, WITH, "+", "+", commas, whitespace) and extracting identifier tokens
(case-insensitive), then change disallowed_license? and permissive_license? to
check any token against DISALLOWED_LICENSES / PERMISSIVE_LICENSES (using
case-insensitive equality or starts_with where appropriate for license
exceptions) so composite expressions correctly detect copyleft or disallowed
components.

Comment on lines +9 to +13
module Kernel
def Rainbow(text) # rubocop:disable Naming/MethodName
text
end
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Rainbow fallback is broken: it returns String but callers chain color methods.

When rainbow is unavailable, Line 10 returns plain text; later calls like Line 54 (Rainbow(...).bold) will raise NoMethodError.

💡 Proposed fix
 rescue LoadError
   # Fallback if Rainbow is not available
   module Kernel
+    class PlainRainbow
+      def initialize(text)
+        `@text` = text
+      end
+
+      def bold = self
+      def cyan = self
+      def red = self
+      def yellow = self
+      def green = self
+
+      def to_s
+        `@text`
+      end
+    end
+
     def Rainbow(text) # rubocop:disable Naming/MethodName
-      text
+      PlainRainbow.new(text)
     end
   end
 end

Also applies to: 54-55, 64-65, 73-75, 83-85

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@react_on_rails/lib/tasks/license_scan.rake` around lines 9 - 13, The fallback
Rainbow method in Kernel currently returns a plain String which breaks callers
that chain color methods (e.g., Rainbow(...).bold); update Kernel#Rainbow to
return an object that responds to the color/format chain (at least the methods
used: bold, red, yellow, green, underline, etc.) and delegates to the underlying
string, returning the plain string when converted (to_s/inspect); implement this
via a small wrapper (e.g., a simple class or struct with defined color methods
or method_missing that returns self or the wrapped string) so chained calls do
not raise NoMethodError and ultimately yield the original text.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 4, 2026

PR Review: Add dependency license scanning rake task

Overall this is a useful feature, but there are several issues ranging from a silent bug to test quality and code organization concerns.

Bug: Missing require "English" causes JS scanning to silently fail

license_scanner.rb uses $CHILD_STATUS (line 111) without require "English". Every other file in this codebase that uses $CHILD_STATUS has require "English" at the top — see git_utils.rb, utils.rb, service_checker.rb, task_helpers.rb, etc.

Without that require, $CHILD_STATUS is nil in Ruby (undefined globals return nil), so $CHILD_STATUS&.success? always returns nil (falsy). The result: fetch_js_licenses always returns nil and the JS package scan is silently skipped on every run. This is a correctness bug.

Fix: add require "English" at the top of the file (or use $? directly).

Global method pollution in the rake file

Helper methods print_json_result, violation_hash, print_text_result, print_violations, print_warnings, and print_summary are defined at the top level of the rake file. This pollutes the Ruby global namespace and can silently conflict with methods defined in other rake files loaded by the host application.

The standard pattern is to scope these inside a module, or keep them as private helpers inside the namespace block closure.

Kernel monkey-patch for Rainbow fallback

Patching Kernel to add a Rainbow method is more invasive than necessary and can interfere with user application code. A simple local no-op lambda would be less disruptive.

disallowed_license? has a redundant and inconsistently cased prefix check

The start_with? in disallowed_license? is case-sensitive, so "gpl-2.0-foo" passes through undetected while "GPL-2.0-foo" is caught — inconsistent with the casecmp fallback. Entries like GPL-2.0-only and GPL-2.0-or-later in DISALLOWED_LICENSES are also redundant because "GPL-2.0-only".start_with?("GPL-2.0") is already true. The start_with? approach also flags SPDX exception identifiers like GPL-2.0-WITH-Classpath-exception, which many compliance teams treat differently.

Consider using exact SPDX casecmp matching only and letting the explicit list be the source of truth.

Tests are vacuous for the multi-license case

The if diff_lcs_warning guard in the test means the expectation is never actually asserted if diff-lcs does not appear in warnings. This should use stubs/mocks to inject a known dual-licensed package and verify the classification logic directly, independent of the installed gem set.

There are also no unit tests for the core logic (check_licenses, disallowed_license?, permissive_license?, parse_pnpm_licenses, parse_npm_licenses, parse_yarn_licenses), so regressions in those methods would go undetected.

Minor: exit(1) in a rake task

Rake's idiomatic way to signal failure is abort "message" (prints to stderr and exits 1) or raising. Bare exit(1) bypasses rake's error handling and can produce confusing output when the task is chained.

cmd = LICENSE_COMMANDS[package_manager]

output = `#{cmd}`
return nil unless $CHILD_STATUS&.success?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

$CHILD_STATUS is defined by require "English" which is missing from this file. Without it, $CHILD_STATUS is always nil, so this guard always returns nil (falsy) and JS license scanning is silently skipped on every run.

Every other file in this codebase that uses $CHILD_STATUS includes require "English" at line 3. Add it here too, or use $? directly.

Comment on lines +5 to +13
begin
require "rainbow"
rescue LoadError
# Fallback if Rainbow is not available
module Kernel
def Rainbow(text) # rubocop:disable Naming/MethodName
text
end
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Patching Kernel is a very invasive fallback — it affects all objects in the process, not just this rake task. If the user's app already defines a Kernel#Rainbow method for something else, this will conflict.

A less invasive approach is a local lambda or a simple method defined inside the task's closure.

print_text_result(result)
end

exit(1) if result.violations.any?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

exit(1) bypasses Rake's error handling. The idiomatic approach for a rake task failure is abort "message" which prints to stderr and exits with code 1, or raise "message" which participates in Rake's task dependency error propagation.

Comment on lines +33 to +89
def print_json_result(result)
require "json"
output = {
status: result.violations.any? ? "fail" : "pass",
scanned: result.scanned_count,
violations: result.violations.map { |v| violation_hash(v) },
warnings: result.warnings.map { |v| violation_hash(v) }
}
puts JSON.pretty_generate(output)
end

def violation_hash(violation)
{
name: violation.name,
version: violation.version,
licenses: violation.licenses,
source: violation.source
}
end

def print_text_result(result)
puts Rainbow("\nReact on Rails License Scan").bold
puts Rainbow("=" * 40).cyan
puts "Scanned #{result.scanned_count} dependencies\n\n"

print_violations(result.violations) if result.violations.any?
print_warnings(result.warnings) if result.warnings.any?
print_summary(result.violations)
end

def print_violations(violations)
puts Rainbow("VIOLATIONS (disallowed licenses):").red.bold
violations.each do |v|
puts Rainbow(" ✗ #{v.name} #{v.version} (#{v.source})").red
puts " Licenses: #{v.licenses.join(', ')}"
end
puts
end

def print_warnings(warnings)
puts Rainbow("WARNINGS (multi-licensed with copyleft option):").yellow.bold
warnings.each do |v|
puts Rainbow(" ⚠ #{v.name} #{v.version} (#{v.source})").yellow
puts " Licenses: #{v.licenses.join(', ')} (permissive option available)"
end
puts
end

def print_summary(violations)
if violations.empty?
puts Rainbow("✓ No disallowed licenses found").green.bold
else
puts Rainbow("✗ #{violations.size} violation(s) found").red.bold
puts " Disallowed: #{ReactOnRails::LicenseScanner::DISALLOWED_LICENSES.join(', ')}"
end
puts
end
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These helper methods are defined at the top level of the rake file, polluting the global Ruby namespace. If any host application (or another gem) defines methods with the same names (print_text_result, print_summary, etc.), one will silently override the other.

Wrap them in a module or inline them directly into the task.

end

def disallowed_license?(license)
DISALLOWED_LICENSES.any? { |d| license.start_with?(d) || license.casecmp(d).zero? }
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Two issues with this check:

  1. Case inconsistency: start_with? is case-sensitive while casecmp is case-insensitive. A license string "gpl-2.0-foo" won't be caught by start_with?("GPL-2.0") even though the intent seems to be to catch it.

  2. Redundant DISALLOWED_LICENSES entries: Since start_with?("GPL-2.0") already matches "GPL-2.0-only" and "GPL-2.0-or-later", those entries in the constant are redundant. The start_with? approach also inadvertently flags "GPL-2.0-WITH-Classpath-exception" and similar SPDX exception identifiers.

Consider using only casecmp exact matching and making the DISALLOWED_LICENSES list the explicit source of truth for which identifiers are blocked.

expect(gpl_violations).to be_empty

diff_lcs_warning = result.warnings.find { |w| w.name == "diff-lcs" }
expect(diff_lcs_warning.licenses).to include("MIT") if diff_lcs_warning
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This assertion is conditional on diff_lcs_warning being present, so if the gem isn't in warnings (e.g., its license metadata changes or it isn't installed), the test passes vacuously — providing no safety net.

Use expect(diff_lcs_warning).not_to be_nil before the licenses assertion, or better, stub the gem data to make the test deterministic.

Keep only the .cursor/BUGBOT.md config, which is specific to this
repo's PR workflow.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@justin808 justin808 changed the title Add dependency license scanning rake task Add BugBot license scanning config Mar 4, 2026
Comment thread .cursor/BUGBOT.md

## License Scanning

If the PR modifies dependency files (package.json, pnpm-lock.yaml, yarn.lock, Gemfile, Gemfile.lock, gemspec), then:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The trigger pattern gemspec won't match any actual file in this repo — the files are named react_on_rails.gemspec and react_on_rails_pro.gemspec. Consider using a glob pattern like *.gemspec so gemspec changes are caught.

Suggested change
If the PR modifies dependency files (package.json, pnpm-lock.yaml, yarn.lock, Gemfile, Gemfile.lock, gemspec), then:
If the PR modifies dependency files (package.json, pnpm-lock.yaml, yarn.lock, Gemfile, Gemfile.lock, *.gemspec), then:

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 78d53e7d29

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .cursor/BUGBOT.md

## License Scanning

If the PR modifies dependency files (package.json, pnpm-lock.yaml, yarn.lock, Gemfile, Gemfile.lock, gemspec), then:
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

*P2 Badge Match all .gemspec files in dependency trigger list

The dependency-change condition currently lists gemspec as a literal filename, but this repo’s Ruby dependency manifests are *.gemspec files (for example react_on_rails.gemspec), so PRs that only update those files can skip this BugBot rule and miss the license scan entirely. Updating this trigger to explicitly cover *.gemspec prevents silent gaps in compliance checks for gem dependency changes.

Useful? React with 👍 / 👎.

@claude
Copy link
Copy Markdown
Contributor

claude Bot commented Mar 4, 2026

Review of BugBot License Scanning Config

Overall this is a lightweight, sensible addition. One functional issue and a couple of notes worth considering:

Bug: gemspec trigger won't fire

The trigger list includes gemspec as a literal filename, but the actual files in this repo are react_on_rails.gemspec and react_on_rails_pro.gemspec. An exact-name match on gemspec will never trigger, so gemspec changes will silently skip the license check. Suggesting *.gemspec in the inline comment.

Consideration: reliance on "built-in License Scan"

The instruction Run the built-in License Scan assumes BugBot has such a capability. As a natural-language-driven AI reviewer, BugBot may not have a deterministic license-scanning function — it will try to infer license info from available context. If the goal is a reliable compliance gate, a proper CI step (e.g., license_finder for Ruby, license-checker or licensee for JS) would be more dependable and auditable than relying on an LLM to get it right every time.

Minor: LGPL variants not listed

Depending on how the project treats linking, LGPL-2.0 and LGPL-2.1 can be worth flagging too. Not necessarily blocking (LGPL is much more permissive than GPL), but worth a deliberate policy decision either way.

Minor: lock file asymmetry

pnpm-lock.yaml and yarn.lock are listed but package-lock.json is not. The repo currently uses pnpm so this is fine today, but if the lockfile manager ever changes the trigger won't catch it. Low priority.

@justin808 justin808 merged commit 2e5dbbc into master Mar 4, 2026
10 checks passed
@justin808 justin808 deleted the jg/license-scan-rake-task branch March 4, 2026 08:36
justin808 added a commit that referenced this pull request Mar 5, 2026
…-renderer-duplicate-error-reporting

* origin/master:
  Consolidate changelog to keep only rc6 prerelease (#2533)
  Fix CSS module class name divergence with rspack SSR (#2489)
  Bump version to 16.4.0.rc.6
  Add BugBot license scanning config (#2515)
  Fix buildVM promise cleanup ordering (#2483) (#2484)

# Conflicts:
#	CHANGELOG.md
justin808 added a commit that referenced this pull request Mar 6, 2026
* origin/master:
  Fix private_output_path not configured on fresh Shakapacker installs (#2289)
  Bump the npm-security group across 1 directory with 3 updates (#2387)
  docs: use https links (#2518)
  Consolidate changelog to keep only rc6 prerelease (#2533)
  Fix CSS module class name divergence with rspack SSR (#2489)
  Bump version to 16.4.0.rc.6
  Add BugBot license scanning config (#2515)
  Fix buildVM promise cleanup ordering (#2483) (#2484)

# Conflicts:
#	CHANGELOG.md
justin808 added a commit that referenced this pull request Mar 6, 2026
* origin/master:
  Fix private_output_path not configured on fresh Shakapacker installs (#2289)
  Bump the npm-security group across 1 directory with 3 updates (#2387)
  docs: use https links (#2518)
  Consolidate changelog to keep only rc6 prerelease (#2533)
  Fix CSS module class name divergence with rspack SSR (#2489)
  Bump version to 16.4.0.rc.6
  Add BugBot license scanning config (#2515)
  Fix buildVM promise cleanup ordering (#2483) (#2484)

# Conflicts:
#	CHANGELOG.md
AbanoubGhadban pushed a commit that referenced this pull request Mar 7, 2026
## Summary

- Add `.cursor/BUGBOT.md` to configure Cursor BugBot for automated
license scanning on PRs
- When a PR modifies dependency files (package.json, Gemfile, etc.),
BugBot will flag any new/upgraded dependency with GPL-2.0, GPL-3.0, or
AGPL-3.0 licenses

The open-source ROR dependency tree is clean (all MIT/Apache-2.0/BSD).
This config ensures it stays that way.

## Test plan

- [x] BugBot config follows documented format
- [ ] CI passes

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
justin808 added a commit that referenced this pull request Mar 8, 2026
…upport

* origin/master: (38 commits)
  fix: use env-var-driven ports in Procfile templates to support multiple worktrees (#2539)
  Fix prettier formatting in auto-bundling doc
  Docs: Clarify .client/.server suffixes vs use client RSC directive (#2406)
  Warn against using .server/.client variants with RSC features
  Docs: move internal-only docs out of published docs trees (#2414)
  Fix crash when HTTPX::ErrorResponse is returned in get_form_body_for_file (#2532)
  Skip flaky external URLs in lychee checks (#2547)
  Update mise docs: prefer shell-level shims over conductor-exec (#2537)
  Document compression middleware compatibility with streaming SSR (#2544)
  Fix duplicate node-renderer message reporting in render failures (#2531)
  Fix private_output_path not configured on fresh Shakapacker installs (#2289)
  Bump the npm-security group across 1 directory with 3 updates (#2387)
  docs: use https links (#2518)
  Consolidate changelog to keep only rc6 prerelease (#2533)
  Fix CSS module class name divergence with rspack SSR (#2489)
  Bump version to 16.4.0.rc.6
  Add BugBot license scanning config (#2515)
  Fix buildVM promise cleanup ordering (#2483) (#2484)
  Fix streaming SSR hangs and silent error absorption in RSC payload injection (#2407)
  Ensure lefthook uses mise tools in non-interactive shells (#2512)
  ...

# Conflicts:
#	CHANGELOG.md
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.

1 participant