Skip to content

Conversation

@ihabadham
Copy link
Collaborator

@ihabadham ihabadham commented Dec 26, 2025

Summary

This PR adds automatic license renewal support for both the Ruby gem and Node Renderer. When a user's license is approaching expiry, the client automatically fetches the renewed license from the licensing API, eliminating the need for manual token updates after subscription renewals.

Key Features

  • Ruby Gem: LicenseFetcher, LicenseCache, and LicenseRefreshChecker modules
  • Node Renderer: TypeScript equivalents (licenseFetcher.ts, licenseCache.ts, licenseRefreshChecker.ts)
  • Configurable: Via license_key, license_api_url, and auto_refresh_license settings
  • ENV support: All settings can be configured via environment variables
  • Graceful fallback: Network errors fall back to cached/existing license

Refresh Schedule

  • License expires in ≤7 days: daily refresh check
  • License expires in ≤30 days: weekly refresh check
  • License expires in >30 days: no refresh needed

Configuration

Ruby Gem (via initializer or ENV):

ReactOnRailsPro.configure do |config|
  config.license_key = "lic_..."  # or ENV['REACT_ON_RAILS_PRO_LICENSE_KEY']
  config.license_api_url = "https://licenses.shakacode.com"
  config.auto_refresh_license = true  # default
end

Node Renderer (via ENV):

REACT_ON_RAILS_PRO_LICENSE_KEY=lic_...
REACT_ON_RAILS_PRO_LICENSE_API_URL=https://licenses.shakacode.com
REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE=true  # default

Test Plan

  • Ruby gem unit tests (110 tests, 64 new)
  • Node Renderer unit tests (54 tests)
  • E2E test: API fetch with live licensing app
  • E2E test: Full renewal flow (old token → API → new token)
  • Network error fallback verified
  • All timing thresholds verified

Related

  • Requires licensing app PR with /api/license endpoint

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Automatic license renewal is now available for paid subscribers.
  • Configuration

    • Added license_key, auto_refresh_license, and license_api_url configuration options for license management.
  • Documentation

    • Expanded LICENSE_SETUP.md with setup guidance, configuration details, and troubleshooting for automatic renewal.
    • Updated installation guides with information for paid subscribers.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 26, 2025

Walkthrough

This PR introduces automatic license renewal for React on Rails Pro by adding persistent token caching, API-based license fetching with retry logic, intelligent refresh scheduling based on expiry dates, and async validation integration across both TypeScript and Ruby implementations with comprehensive test coverage.

Changes

Cohort / File(s) Summary
TypeScript License Auto-Refresh Modules
packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts, licenseFetcher.ts, licenseRefreshChecker.ts
New modules implement persistent disk-backed cache for tokens, API fetching with retry/timeout logic, and intelligent refresh scheduling based on days-until-expiry (daily ≤7d, weekly ≤30d, disabled >30d).
TypeScript License Validator & Initialization
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts, master.ts, ReactOnRailsProNodeRenderer.ts
Converts validation and master initialization to async; integrates cache-first token retrieval, auto-refresh pre-fetch, and cache seeding on first validation; awaits all async calls.
TypeScript Test Suites
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts, licenseFetcher.test.ts, licenseRefreshChecker.test.ts, licenseValidator.test.ts
Comprehensive Jest tests for cache validation, fetch retry behavior, refresh scheduling, and async validator integration; mocks fs, fetch, and environment.
Ruby Configuration & Initialization
react_on_rails_pro/lib/react_on_rails_pro.rb, lib/react_on_rails_pro/configuration.rb
Adds requires for license modules; extends Configuration with auto_refresh_license, license_key, license_api_url accessors, initialization params, and auto_refresh_enabled? method with ENV override support.
Ruby License Management
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb, license_fetcher.rb, license_refresh_checker.rb
Implements singleton-style license classes for disk cache (with key-hash validation), HTTP fetching (5s timeout, 2 retries), and refresh orchestration; mirrors TypeScript logic.
Ruby License Validator
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
Integrates auto-refresh: cache-first retrieval when enabled, pre-refresh before validation, cache seeding on first success.
Ruby Test Suites
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb, license_cache_spec.rb, license_fetcher_spec.rb, license_validator_spec.rb, spec_helper.rb
RSpec tests covering configuration defaults/overrides, cache read/write/validation, HTTP fetching, refresh scheduling, and token priority; adds WebMock for HTTP stubbing.
Documentation
react_on_rails_pro/LICENSE_SETUP.md, README.md, docs/configuration.md, docs/installation.md, spec/dummy/config/initializers/react_on_rails_pro.rb
Adds extensive auto-renewal guidance, configuration options (license_key, auto_refresh_license, license_api_url), FAQs, troubleshooting, privacy notes, and commented example initialization.
Misc
.lychee.toml
Adds URL exclusion for licenses.shakacode.com (planned deployment).

Sequence Diagram(s)

sequenceDiagram
    participant App as App/Renderer
    participant Validator as LicenseValidator
    participant Refresher as LicenseRefreshChecker
    participant Fetcher as LicenseFetcher
    participant Cache as LicenseCache
    participant API as License API
    participant Env as ENV/Config

    App->>Validator: getValidatedLicenseData()
    activate Validator
    
    Validator->>Refresher: maybeRefreshLicense()
    activate Refresher
    Refresher->>Refresher: shouldCheckForRefresh()?
    alt Refresh Needed
        Refresher->>Fetcher: fetchLicense()
        activate Fetcher
        Fetcher->>Env: Get license_key
        Fetcher->>API: GET /api/license<br/>(5s timeout, retries)
        alt Success (200)
            API-->>Fetcher: {token, expires_at}
            Fetcher-->>Refresher: LicenseResponse
        else Failure
            API-->>Fetcher: Error/Timeout
            Fetcher-->>Refresher: null
        end
        deactivate Fetcher
        
        alt Token Received
            Refresher->>Cache: writeCache({token, expires_at})
            activate Cache
            Cache->>Cache: Compute license_key_hash
            Cache->>Env: Write to disk (0600 perms)
            deactivate Cache
        end
    end
    Refresher-->>Validator: (void)
    deactivate Refresher
    
    rect rgb(200, 220, 255)
        Note over Validator: Load & Validate License
        Validator->>Cache: getCachedToken()<br/>(if auto-refresh enabled)
        alt Cache Hit
            Cache-->>Validator: token
        else Cache Miss
            Validator->>Env: Read ENV/Config file
            Env-->>Validator: token
        end
    end
    
    Validator->>Validator: decode & validate JWT
    Validator->>Cache: writeCache() for seeding
    Validator-->>App: LicenseData
    deactivate Validator
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • alexeyr-ci
  • alexeyr-ci2
  • AbanoubGhadban

Poem

🐰 Hops with glee
License tokens cache with care,
Auto-refresh fills the air,
No more waiting, no more fuss,
Renewal happens fast for us!
The rabbit's blessing flows so free, ✨
Safe and timely, wild and spree! 🌿

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 58.93% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Add automatic license renewal for gem and Node Renderer' accurately summarizes the main change—adding automatic license renewal functionality to both the Ruby gem and Node Renderer components of ReactOnRails Pro.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch ihabadham/pro/automatic-license-renewal

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7593128 and b68b676.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
🧰 Additional context used
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.

Applied to files:

  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running

Applied to files:

  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
🧬 Code graph analysis (1)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (3)
react_on_rails_pro/spec/dummy/app/graphql/mutations/update_user.rb (1)
  • resolve (12-16)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RouterApp.server.jsx (1)
  • controller (10-10)
🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts

[high] 61-61: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 180-180: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: examples (3.4, latest)
  • GitHub Check: examples (3.2, minimum)
  • GitHub Check: build-dummy-app-webpack-test-bundles (3.2, 20, minimum)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build
  • GitHub Check: pro-lint-js-and-ruby
  • GitHub Check: check-bundle-size
  • GitHub Check: markdown-link-check
  • GitHub Check: claude-review
🔇 Additional comments (4)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)

1-197: Excellent comprehensive test coverage!

The test suite thoroughly covers the license fetcher functionality including:

  • Environment configuration variations and edge cases (case-insensitive handling)
  • Success paths with proper header verification (Authorization, User-Agent)
  • Error scenarios (401, network failures, invalid JSON)
  • Retry behavior with correct attempt counts
  • Custom API URL configuration

The test structure is clean with proper setup/teardown and appropriate use of fake timers for retry testing.

packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (3)

8-10: Well-chosen timeout and retry parameters.

The constants provide sensible defaults:

  • 5-second request timeout prevents hanging requests
  • 2 retries (3 total attempts) balance reliability with responsiveness
  • 1-second minimum delay between retries avoids overwhelming the API

22-71: Clean custom retry implementation addresses Jest timer issues.

The simple for-loop-based withRetry utility with AbortRetryError provides:

  • Clean avoidance of Jest timer leaks (as noted in PR objectives)
  • Immediate abort path for non-retriable errors (auth failures)
  • Clear error propagation and callback support

The no-await-in-loop eslint-disable is appropriate here since sequential retry attempts with delays are the intended behavior.


93-177: Robust license fetching with proper error handling.

The implementation correctly handles:

  • Early returns when auto-refresh is disabled
  • Request timeout via AbortController with cleanup in finally block
  • Auth errors (401/403) abort retries immediately via AbortRetryError
  • Transient errors trigger retries (network failures, 5xx responses)
  • All errors gracefully return null with appropriate logging

The default-to-true behavior for isAutoRefreshEnabled when the setting is unspecified is a reasonable choice for an auto-refresh feature, requiring explicit opt-out rather than opt-in.


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.

@claude
Copy link

claude bot commented Dec 26, 2025

Code Review: Automatic License Renewal Feature

Thanks for this comprehensive implementation! This is a well-thought-out feature with solid test coverage. Here's my detailed review:


✅ Strengths

1. Excellent Architecture

  • Clean separation of concerns: LicenseFetcher, LicenseCache, LicenseRefreshChecker
  • Consistent implementation across Ruby and TypeScript
  • Good use of existing patterns (similar to existing license validation)

2. Strong Test Coverage

  • 110+ Ruby tests, 54 TypeScript tests
  • Good coverage of edge cases (network errors, auth failures, timing thresholds)
  • E2E tests for the full renewal flow

3. Security Considerations

  • License key hash validation prevents cache poisoning
  • File permissions (0600) on cache file
  • No retry on auth errors (401/403) - correct behavior
  • ENV variables take precedence over config (good for secrets management)

4. Good User Experience

  • Graceful fallback to cached/existing license on network errors
  • Clear distinction between license_key (permanent) and license token (temporary JWT)
  • Comprehensive documentation

🔍 Issues & Recommendations

CRITICAL: Security - License Key Storage

Ruby gem (license_fetcher.rb:23):

"Authorization" => "Bearer #{license_key}"

Potential Issue: The license_key is used directly in HTTP headers. If logging middleware captures request headers, the permanent license key could be logged.

Recommendation:

  • Audit all logging/monitoring to ensure license_key is never logged
  • Consider adding explicit warning in docs about not logging Authorization headers
  • Add test to verify key doesn't appear in debug logs

HIGH: Cache File Location - Ephemeral Storage

Ruby (license_cache.rb:76) and TypeScript (licenseCache.ts:53):

def cache_dir
  Rails.root.join("tmp")
end

Issue: On cloud platforms (Heroku, AWS Elastic Beanstalk, etc.), the tmp/ directory is often ephemeral and cleared on deploys/restarts.

Impact:

  • Cache will be lost on every deploy → unnecessary API calls at startup
  • On platforms with frequent restarts, this defeats the purpose of caching

Recommendation:

# Use a persistent location in production
def cache_dir
  if Rails.env.production? && ENV['REACT_ON_RAILS_PRO_LICENSE_CACHE_DIR']
    Pathname.new(ENV['REACT_ON_RAILS_PRO_LICENSE_CACHE_DIR'])
  else
    Rails.root.join("tmp")
  end
end

Or document clearly that users should configure persistent storage for the cache in production.


MEDIUM: Race Condition in Cache Seeding

Ruby (license_refresh_checker.rb:26-39):

def seed_cache_if_needed(license_data)
  return unless ReactOnRailsPro.configuration.auto_refresh_enabled?
  return if LicenseCache.token.present? # Cache already exists

  token = load_token_from_env_or_file
  # ... write cache
end

Issue: In multi-process deployments (Puma, Passenger), multiple workers could simultaneously check token.present?, find it false, and all write the cache file simultaneously.

Impact:

  • File corruption if writes overlap
  • Though unlikely due to fast I/O, it's possible

Recommendation:

# Add file locking
File.open(cache_path, File::RDWR|File::CREAT, 0600) do |file|
  file.flock(File::LOCK_EX)
  # Check again after acquiring lock
  return if LicenseCache.token.present?
  # ... write cache
end

MEDIUM: Error Handling - Missing Validation

Ruby (license_fetcher.rb:32):

Rails.logger.debug { "[ReactOnRailsPro] License fetched successfully" }
JSON.parse(response.body.to_s)

Issue: No validation that the response contains required fields (token, expires_at) before returning.

Impact: If API response format changes, invalid data could be cached.

Recommendation:

data = JSON.parse(response.body.to_s)
return nil unless data.is_a?(Hash) && data["token"].present? && data["expires_at"].present?
data

MEDIUM: TypeScript - Async Timing Issue

TypeScript (licenseValidator.ts integration):

The refresh check in maybeRefreshLicense() is async, but license validation is typically synchronous during startup.

Question: How does the async refresh integrate with startup validation? If the app starts while a refresh is in-progress, does it use stale cache?

Recommendation:

  • Document the startup behavior explicitly
  • Consider making the first refresh attempt synchronous/blocking during startup if cache is stale
  • Or document that the app may start with slightly stale token (which is acceptable)

LOW: Code Quality - Magic Numbers

Ruby (license_refresh_checker.rb:45):

days_until_expiry = ((expires_at - Time.now) / 1.day).to_i

Note: Uses 1.day from ActiveSupport. This is fine, but consider extracting threshold constants:

DAILY_CHECK_THRESHOLD_DAYS = 7
WEEKLY_CHECK_THRESHOLD_DAYS = 30

if days_until_expiry <= DAILY_CHECK_THRESHOLD_DAYS
  # ...

Improves readability and makes thresholds configurable in the future.


LOW: Documentation - Missing API Contract

The PR description mentions "Requires licensing app PR with /api/license endpoint" but:

Missing:

  • API contract specification (request/response format)
  • Error response formats
  • Rate limiting behavior
  • What happens if API is down for extended period?

Recommendation: Add API contract documentation so users understand the endpoint behavior.


🧪 Testing Observations

Good Coverage:

  • Network error scenarios ✅
  • Auth failures (401/403) ✅
  • Timing thresholds ✅
  • Cache validation ✅

Missing Tests:

  1. Multi-process race conditions - cache write contention
  2. Partial file writes - what happens if write is interrupted?
  3. Clock skew - behavior when system time changes
  4. File permission errors - when cache_dir is not writable

📝 Documentation Feedback

LICENSE_SETUP.md is excellent! Clear distinction between license_key and license_token.

Suggestions:

  1. Add troubleshooting section:

    • How to verify auto-refresh is working?
    • How to check when last refresh happened?
    • What logs to check for issues?
  2. Add section on production deployments:

    • Ephemeral storage considerations
    • Multi-server deployments
    • Cache persistence strategies

🎯 Summary

Category Count Severity
Critical Issues 1 License key logging risk
High Issues 1 Cache storage location
Medium Issues 3 Race condition, validation, async timing
Low Issues 2 Magic numbers, documentation

Overall: This is solid work with good architecture and test coverage. The critical and high issues are configuration/documentation problems rather than code bugs, so they're easy to address.

Recommendation: ✅ Approve with changes - address the cache storage location and add documentation about logging risks before merging.


🚀 Pre-Merge Checklist

Before merging, verify:

  • RuboCop passes: bundle exec rubocop
  • All tests pass: rake run_rspec:gem
  • TypeScript tests pass: pnpm test
  • Files end with newlines
  • Documentation updated (both changelogs if needed)
  • Manual test of yalc publish if package.json changed

Great work on this feature! 🎉

@github-actions
Copy link
Contributor

github-actions bot commented Dec 26, 2025

size-limit report 📦

Path Size
react-on-rails/client bundled (gzip) 62.5 KB (0%)
react-on-rails/client bundled (gzip) (time) 62.5 KB (0%)
react-on-rails/client bundled (brotli) 53.66 KB (0%)
react-on-rails/client bundled (brotli) (time) 53.66 KB (0%)
react-on-rails-pro/client bundled (gzip) 63.45 KB (0%)
react-on-rails-pro/client bundled (gzip) (time) 63.45 KB (0%)
react-on-rails-pro/client bundled (brotli) 54.53 KB (0%)
react-on-rails-pro/client bundled (brotli) (time) 54.53 KB (0%)
registerServerComponent/client bundled (gzip) 127.35 KB (0%)
registerServerComponent/client bundled (gzip) (time) 127.35 KB (-0.04% 🔽)
registerServerComponent/client bundled (brotli) 61.39 KB (0%)
registerServerComponent/client bundled (brotli) (time) 61.39 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) 121.96 KB (0%)
wrapServerComponentRenderer/client bundled (gzip) (time) 121.96 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) 56.66 KB (0%)
wrapServerComponentRenderer/client bundled (brotli) (time) 56.66 KB (0%)

Copy link
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 (7)
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts (1)

11-21: Consider consolidating mock setup.

The auto-refresh modules are mocked twice: once at the top level (lines 11-21) and again in beforeEach (lines 83-93). While this is necessary due to jest.resetModules(), consider extracting the mock definitions to a helper function to reduce duplication and ensure consistency.

🔎 Proposed refactor to reduce duplication
+function setupAutoRefreshMocks() {
+  jest.doMock('../src/shared/licenseFetcher', () => ({
+    isAutoRefreshEnabled: jest.fn().mockReturnValue(false),
+  }));
+  jest.doMock('../src/shared/licenseCache', () => ({
+    getCachedToken: jest.fn().mockReturnValue(null),
+  }));
+  jest.doMock('../src/shared/licenseRefreshChecker', () => ({
+    maybeRefreshLicense: jest.fn().mockResolvedValue(undefined),
+    seedCacheIfNeeded: jest.fn(),
+  }));
+}
+
 // Mock auto-refresh modules to prevent side effects
-jest.mock('../src/shared/licenseFetcher', () => ({
-  isAutoRefreshEnabled: jest.fn().mockReturnValue(false),
-}));
-jest.mock('../src/shared/licenseCache', () => ({
-  getCachedToken: jest.fn().mockReturnValue(null),
-}));
-jest.mock('../src/shared/licenseRefreshChecker', () => ({
-  maybeRefreshLicense: jest.fn().mockResolvedValue(undefined),
-  seedCacheIfNeeded: jest.fn(),
-}));
+setupAutoRefreshMocks();

   // ... in beforeEach ...
   
   // Re-mock auto-refresh modules after resetModules
-  jest.doMock('../src/shared/licenseFetcher', () => ({
-    isAutoRefreshEnabled: jest.fn().mockReturnValue(false),
-  }));
-  jest.doMock('../src/shared/licenseCache', () => ({
-    getCachedToken: jest.fn().mockReturnValue(null),
-  }));
-  jest.doMock('../src/shared/licenseRefreshChecker', () => ({
-    maybeRefreshLicense: jest.fn().mockResolvedValue(undefined),
-    seedCacheIfNeeded: jest.fn(),
-  }));
+  setupAutoRefreshMocks();

Also applies to: 83-93

react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)

28-29: Non-200 responses silently return nil.

The implementation returns nil for all non-200 status codes (401, 403, 404, 500, etc.) without distinguishing between client errors (4xx) and server errors (5xx). While this provides a graceful fallback, it may make debugging difficult.

Consider logging the status code for non-200 responses to aid troubleshooting.

🔎 Proposed enhancement for debugging
   response = HTTPX
              .plugin(:retries, max_retries: MAX_RETRIES, retry_after: RETRY_DELAY_SECONDS)
              .with(timeout: { request_timeout: REQUEST_TIMEOUT_SECONDS })
              .with(headers: {
                      "Authorization" => "Bearer #{license_key}",
                      "User-Agent" => "ReactOnRailsPro-Gem"
                    })
              .get("#{api_url}/api/license")

   return nil if response.is_a?(HTTPX::ErrorResponse)
-  return nil unless response.status == 200
+  unless response.status == 200
+    Rails.logger.warn { "[ReactOnRailsPro] License fetch failed: HTTP #{response.status}" }
+    return nil
+  end

   Rails.logger.debug { "[ReactOnRailsPro] License fetched successfully" }
   JSON.parse(response.body.to_s)
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)

89-120: Cache-first loading may mask ENV/file changes.

When auto-refresh is enabled, the cache takes precedence over ENV and config file (lines 91-96). This means if a user updates REACT_ON_RAILS_PRO_LICENSE or the config file, the cached token will still be used until it expires or the cache is cleared.

Consider documenting this behavior, or provide a way to force cache invalidation (e.g., deleting the cache file) for users who need to switch licenses immediately.

react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (1)

35-36: Consider atomic file write with secure permissions.

There's a small race window between File.write and File.chmod where the file could be readable by others (depending on umask). For a security-sensitive license file, consider using atomic write with initial permissions.

🔎 Proposed fix for atomic write with secure permissions
-        File.write(cache_path, JSON.pretty_generate(cache_data))
-        File.chmod(0o600, cache_path)
+        require "tempfile"
+        Tempfile.create(["license_cache", ".tmp"], cache_dir, mode: 0o600) do |temp|
+          temp.write(JSON.pretty_generate(cache_data))
+          temp.close
+          FileUtils.mv(temp.path, cache_path)
+        end
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)

65-68: Track the TODO for production URL update.

There's a TODO comment indicating the licensing app URL may need updating. Ensure this is addressed before the final release.

Do you want me to open an issue to track updating the license API URL once the licensing app is deployed to production?

packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (2)

150-161: Consider caching the result to avoid redundant file reads.

getFetchedAt() and getExpiresAt() each call readCache() independently. If called together (which seems likely), this results in multiple file reads. Consider either exposing readCache() for callers to use directly, or caching the result.

Additionally, the empty catch block on lines 158-159 silently swallows date parsing errors. While returning null is reasonable, logging might help debugging.


128-131: Consider atomic write with secure permissions.

Similar to the Ruby implementation, there's a small race window between writeFileSync and chmodSync. For improved security, consider writing to a temp file first and then renaming.

🔎 Proposed fix for atomic write
+import * as os from 'os';
...
-    fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2));
-
-    // Set file permissions to 0600 (owner read/write only)
-    fs.chmodSync(cachePath, 0o600);
+    // Write atomically with secure permissions
+    const tempPath = path.join(cacheDir, `.license_cache_${process.pid}.tmp`);
+    fs.writeFileSync(tempPath, JSON.stringify(cacheData, null, 2), { mode: 0o600 });
+    fs.renameSync(tempPath, cachePath);
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5618568 and 299e54a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • packages/react-on-rails-pro-node-renderer/package.json
  • packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts
  • packages/react-on-rails-pro-node-renderer/src/master.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/README.md
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
🧰 Additional context used
📓 Path-based instructions (2)
{package.json,webpack.config.js,packages/*/package.json,react_on_rails_pro/package.json}

📄 CodeRabbit inference engine (CLAUDE.md)

When resolving merge conflicts in build configuration files, verify file paths are correct by running grep -r 'old/path' . and test affected scripts like pnpm run prepack before continuing the merge

Files:

  • packages/react-on-rails-pro-node-renderer/package.json
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rb: Run bundle exec rubocop and fix ALL violations before every commit/push
Run bundle exec rubocop (MANDATORY) before every commit to ensure zero offenses

Files:

  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
🧠 Learnings (25)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • packages/react-on-rails-pro-node-renderer/package.json
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • packages/react-on-rails-pro-node-renderer/src/master.ts
  • packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • packages/react-on-rails-pro-node-renderer/package.json
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/README.md
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to lib/react_on_rails/engine.rb : In Rails Engine code, DO NOT use `rake_tasks` block to explicitly load rake files from `lib/tasks/` - Rails::Engine automatically loads them. Only use `rake_tasks` block if tasks are in non-standard location, need programmatic generation, or need to pass context

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails-pro-node-renderer/package.json
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running

Applied to files:

  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results

Applied to files:

  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/README.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/app_commands/**/*.rb : Create custom Rails app commands in `e2e/playwright/app_commands/` directory with .rb files for reusable helpers in Playwright tests

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use `app('clean')`, `appEval(code)`, `appFactories(options)`, and `appScenario(name)` helpers from cypress-on-rails in Playwright tests for Rails integration

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Install Playwright browsers with `cd react_on_rails/spec/dummy && pnpm playwright install --with-deps` before running E2E tests

Applied to files:

  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Shakapacker configuration should be tested by creating debug scripts in dummy app root (debug-webpack-rules.js, debug-webpack-with-config.js) to inspect webpack rules before committing configuration changes

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.

Applied to files:

  • react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2024-06-27T05:22:46.156Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1622
File: spec/dummy/spec/rake/assets_precompile_rake_spec.rb:12-12
Timestamp: 2024-06-27T05:22:46.156Z
Learning: When stubbing environment variables in RSpec tests, use `before` and `after` hooks to ensure that the original values are restored after the tests, preventing any side effects on other tests. Example provided by justin808:
```ruby
describe "My test" do
  before do
    original_value = ENV["VARIABLE_NAME"]
    allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_return("stubbed_value")
  end

  after do
    allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_call_original
    ENV["VARIABLE_NAME"] = original_value
  end

  it "tests something" do
    # Your test code here
  end
end
```
This practice ensures test isolation and reliability.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
🧬 Code graph analysis (7)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RouterApp.server.jsx (1)
  • controller (10-10)
packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (4)
  • getFetchedAt (150-161)
  • getExpiresAt (167-178)
  • writeCache (105-136)
  • getCachedToken (141-144)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
  • isAutoRefreshEnabled (51-60)
  • fetchLicense (66-131)
packages/react-on-rails-pro-node-renderer/src/master.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts (1)
  • Config (25-89)
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
  • getValidatedLicenseData (204-247)
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (6)
  • cache_dir (75-77)
  • write (27-39)
  • cache_path (79-81)
  • token (41-43)
  • fetched_at (45-50)
  • expires_at (52-57)
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (5)
  • configure (4-7)
  • configuration (9-38)
  • license_api_url (217-222)
  • license_key (202-207)
  • auto_refresh_enabled? (234-236)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (3)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (2)
  • seed_cache_if_needed (26-39)
  • maybe_refresh_license (13-21)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
  • configuration (9-38)
  • auto_refresh_enabled? (234-236)
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (1)
  • token (41-43)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (3)
  • configuration (9-38)
  • auto_refresh_enabled? (234-236)
  • license_api_url (217-222)
🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts

[high] 62-62: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 187-187: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

react_on_rails_pro/LICENSE_SETUP.md

[high] 102-102: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (11)
  • GitHub Check: dummy-app-integration-tests (3.4, 22, latest)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: build
  • GitHub Check: examples (3.4, latest)
  • GitHub Check: precompile-check
  • GitHub Check: pro-lint-js-and-ruby
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: check-bundle-size
  • GitHub Check: markdown-link-check
  • GitHub Check: claude-review
🔇 Additional comments (31)
react_on_rails_pro/lib/react_on_rails_pro.rb (1)

13-15: LGTM! Clean module loading sequence.

The three new license management modules are loaded in the correct order before the validator, ensuring all dependencies are available when needed.

packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts (1)

1-222: LGTM! Comprehensive test coverage for license refresh logic.

The test suite thoroughly exercises all three exported functions with appropriate mocking and edge case coverage. The beforeEach setup properly isolates tests, and console suppression keeps output clean.

packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts (1)

45-45: Verify awaiting behavior is documented.

The addition of await makes the master path consistent with the worker path and is necessary for async license validation. Since reactOnRailsProNodeRenderer was already async, callers should have been awaiting it.

Please confirm that this change is documented in CHANGELOG_PRO.md, as it could affect callers who weren't properly awaiting the function (though they should have been).

Based on learnings, user-visible changes to the react_on_rails_pro npm package should be documented in CHANGELOG_PRO.md.

react_on_rails_pro/docs/installation.md (1)

50-51: LGTM! Clear guidance for automatic license renewal.

The documentation appropriately directs paid subscribers to use the license key environment variable and links to detailed setup instructions.

react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb (1)

45-50: LGTM! Helpful configuration examples for development.

The commented examples clearly document the auto-refresh options available for local testing, providing a useful reference without affecting default behavior.

react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb (1)

24-27: LGTM! Standard WebMock setup for HTTP testing.

The WebMock integration with HTTPX adapter is properly configured to support testing of the license fetching functionality across all specs.

react_on_rails_pro/docs/configuration.md (1)

165-186: LGTM! Clear and comprehensive auto-refresh configuration documentation.

The new LICENSE AUTO-REFRESH CONFIGURATION section thoroughly documents all three options with appropriate defaults, environment variable names, and usage guidance. The link to LICENSE_SETUP.md provides users with detailed setup instructions.

packages/react-on-rails-pro-node-renderer/package.json (1)

23-23: Ensure async-retry addition is documented in react_on_rails_pro/CHANGELOG.md.

The async-retry dependency version ^1.3.3 is confirmed as the latest stable version and is appropriately added for the license fetch retry logic. However, this change is not documented in the changelog. Update react_on_rails_pro/CHANGELOG.md to include this new dependency addition under the appropriate release section.

Also applies to: 38-38

⛔ Skipped due to learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /{CHANGELOG.md,CHANGELOG_PRO.md} : Use format `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` in changelog entries (no hash in PR number)
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1687
File: spec/dummy/package.json:0-0
Timestamp: 2025-01-23T18:20:45.824Z
Learning: When adding or updating dependencies in spec/dummy/package.json, maintain version consistency with other package.json files in the codebase to avoid potential version conflicts.
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`
react_on_rails_pro/README.md (1)

297-298: LGTM! Clear guidance for paid subscribers.

The documentation clearly directs paid subscribers to use the LICENSE_KEY environment variable for automatic renewal, with an appropriate reference to detailed setup instructions.

react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)

29-31: LGTM! Cache seeding correctly positioned.

The cache seeding logic is appropriately placed after successful validation, ensuring only valid license data is cached. The comments clearly explain the purpose.


159-174: LGTM! License loading priority correctly updated.

The license loading flow correctly implements the auto-refresh feature:

  • Pre-load refresh attempt ensures fresh tokens
  • Cache-first retrieval when auto-refresh is enabled
  • Proper fallback chain: cache → ENV → config file
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)

1-206: LGTM! Comprehensive test coverage.

The test suite thoroughly covers the licenseFetcher functionality:

  • Auto-refresh enabling/disabling scenarios
  • API request handling with proper headers (Authorization, User-Agent)
  • Error handling (401, network errors, JSON parsing)
  • Retry logic with fake timers
  • Custom API URL support

The test data (JWT tokens, license keys) are clearly test values, not real secrets.

Note: The Gitleaks warnings for lines 62 and 187 are false positives—these are test JWT tokens and mock API keys used exclusively in test scenarios.

react_on_rails_pro/LICENSE_SETUP.md (2)

79-142: LGTM! Excellent documentation for automatic license renewal.

The automatic license renewal section is comprehensive and user-friendly:

  • Clear distinction between License Token and License Key
  • Well-organized setup instructions
  • Helpful frequency table for refresh checks
  • Appropriate guidance for air-gapped environments
  • Privacy considerations transparently documented

350-360: LGTM! Clear FAQ entries for dual-token model.

The FAQ entries effectively clarify:

  • Differences between LICENSE and LICENSE_KEY
  • When to use each method
  • Behavior when both are set
  • Data transmission transparency

Note: The Gitleaks warning for line 102 is a false positive—"lic_abc123xyz..." is clearly a placeholder example in documentation, not a real license key.

packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (1)

1-254: LGTM! Thorough test coverage for license cache.

The test suite comprehensively validates the cache functionality:

  • Read/write operations with license key validation
  • Security features (0600 permissions, license_key_hash verification)
  • Error handling (missing files, JSON parse errors, write failures)
  • All public API methods (getCachedToken, getFetchedAt, getExpiresAt)
  • Proper mocking and cleanup for filesystem operations

The tests ensure data integrity and security of the cached license data.

react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (1)

258-601: LGTM! Comprehensive auto-refresh test coverage.

The test suite thoroughly validates the license auto-refresh feature:

  • Token source priority (cache → ENV → config) with proper gating
  • Refresh timing logic with boundary tests (30, 8, 7, 3 days)
  • Seeding behavior for first-boot scenarios
  • Integration with LicenseCache, LicenseFetcher, and LicenseRefreshChecker
  • Proper handling of fetch success/failure scenarios

The comment at lines 346-352 explaining the 60-day choice to avoid timing races demonstrates thoughtful test design.

react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (1)

301-427: LGTM! Complete configuration test coverage.

The test suite thoroughly validates the license auto-refresh configuration:

  • Default values (auto_refresh_license defaults to true)
  • Configuration mutability
  • ENV variable precedence (LICENSE_KEY, LICENSE_API_URL)
  • Helper method logic (auto_refresh_enabled?)
  • Edge cases (empty ENV, nil values)
  • Proper ENV cleanup for test isolation
packages/react-on-rails-pro-node-renderer/src/master.ts (1)

14-18: The async conversion of masterRun is correctly implemented with proper await of getValidatedLicenseData(). The only call site in ReactOnRailsProNodeRenderer.ts line 45 is already properly awaiting the function with await master.default(config). No further changes needed.

react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb (1)

1-185: LGTM! Comprehensive test coverage.

The test suite thoroughly covers all scenarios for LicenseFetcher.fetch:

  • Auto-refresh disabled/enabled states
  • Various HTTP response codes (200, 401, 404, 500)
  • Network errors and timeouts
  • Invalid JSON handling
  • ENV variable precedence

Test isolation is properly maintained with appropriate setup/teardown.

react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (1)

1-75: LGTM! Well-structured refresh checker.

The refresh timing logic is clean and correctly implements the documented schedule:

  • ≤7 days: daily checks
  • ≤30 days: weekly checks
  • 30 days: no refresh

Cache seeding handles the first-boot scenario properly, and token loading follows the correct precedence (ENV → config file).

packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (1)

1-133: LGTM! TypeScript implementation matches Ruby behavior.

The TypeScript refresh checker correctly mirrors the Ruby implementation:

  • Identical refresh schedule logic
  • Proper async/await for fetch operations
  • Consistent cache seeding behavior
  • Correct time conversions (Unix timestamp to Date)
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (1)

1-283: LGTM! Comprehensive cache test coverage.

The test suite thoroughly covers all aspects of the license cache:

  • Read validation with hash verification
  • Write operations with proper permissions
  • Environment variable precedence
  • Timestamp parsing and error handling
  • Edge cases (missing files, invalid JSON, mismatched hashes)

FakeFS usage is correct with proper activation/deactivation.

packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts (1)

108-297: LGTM! Comprehensive async validator tests.

The test suite has been properly migrated to the async API:

  • All calls to getValidatedLicenseData(), isEvaluation() use await
  • Covers all critical scenarios (valid, expired, invalid, missing licenses)
  • Tests caching behavior and reset functionality
  • Proper test isolation with RSA key generation
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)

204-247: LGTM! Async validator implementation is solid.

The migration to async API is well-executed:

  • Cache-first loading with auto-refresh integration
  • Proper cache seeding on first validation
  • Consistent error handling
  • All exported functions correctly updated to async

The integration of maybeRefreshLicense() and seedCacheIfNeeded() completes the auto-renewal workflow.

react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (2)

15-25: LGTM!

The read method properly handles edge cases with appropriate error handling. The defensive rescue for Errno::ENOENT alongside the existence check is good practice against TOCTOU race conditions.


68-73: LGTM!

The hash truncation to 16 hex characters provides sufficient entropy for cache validation while keeping the stored data compact. The implementation correctly handles the nil case.

react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)

199-222: LGTM!

The environment variable precedence pattern is well-implemented. Using .present? correctly handles both nil and empty string cases, and the fallback to DEFAULT_LICENSE_API_URL provides sensible defaults.


229-236: LGTM!

The auto_refresh_enabled? method correctly gates auto-refresh on both the feature flag and the presence of a license key, preventing unnecessary API calls when credentials are missing.

packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (3)

48-54: Verify cache directory alignment with Ruby implementation.

This uses process.cwd() to determine the cache directory, while Ruby uses Rails.root. If the Node Renderer is started from a different working directory than the Rails root, they may use different cache files.

Is it intentional that Ruby and Node maintain separate caches, or should they share the same cache file? If sharing is intended, consider making the cache path configurable or using a more explicit path resolution.


80-99: LGTM!

The readCache function has appropriate error handling that fails gracefully. The validation flow correctly checks for file existence, parses JSON, and validates the license key hash before returning data.


15-20: LGTM!

The CacheData interface clearly defines the cache structure, matching the Ruby implementation. This ensures consistency between the two implementations.

Comment on lines 82 to 108
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);

try {
const response = await fetch(url, {
method: 'GET',
headers: {
Authorization: `Bearer ${licenseKey}`,
'User-Agent': 'ReactOnRailsPro-NodeRenderer',
},
signal: controller.signal,
});

if (response.status === 401 || response.status === 403) {
// Don't retry auth errors - bail immediately
bail(new UnauthorizedError());
throw new UnauthorizedError(); // TypeScript needs this
}

if (response.status !== 200) {
throw new Error(`HTTP ${response.status}`);
}

return (await response.json()) as LicenseResponse;
} finally {
clearTimeout(timeoutId);
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Find the test file for licenseFetcher
find . -name "*licenseFetcher*" -type f | head -20

Repository: shakacode/react_on_rails

Length of output: 213


🏁 Script executed:

# Check if error handling properly covers AbortError
rg -n "AbortError|catch|error" packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts -A 2 -B 2

Repository: shakacode/react_on_rails

Length of output: 1384


🏁 Script executed:

# Look for retry logic and complete fetch implementation
rg -n "retry\|catch\|finally" packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts -B 3 -A 5

Repository: shakacode/react_on_rails

Length of output: 50


🏁 Script executed:

cat -n packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts

Repository: shakacode/react_on_rails

Length of output: 4685


🏁 Script executed:

cat -n packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts

Repository: shakacode/react_on_rails

Length of output: 7843


AbortError is properly handled by existing error handling logic.

When controller.abort() is called on timeout, the fetch rejects with an AbortError. This error flows through the retry mechanism (which catches all errors and retries), and if retries are exhausted, it's caught by the outer catch block at line 122. Since AbortError extends Error, it's handled by the generic error handler at lines 126-127 and logged correctly. The finally block at line 107 safely clears the timeout regardless of whether the error occurred.

However, there is no explicit test case covering timeout/abort scenarios. Consider adding a test case that mocks AbortError to ensure this timeout behavior is explicitly validated.

🤖 Prompt for AI Agents
In packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts around
lines 82 to 108, the production code already handles AbortError via the retry
and outer catch, but there is no explicit test validating timeout/abort
behavior; add a unit test that mocks fetch to reject with an AbortError when the
AbortController signal is used (or simulate controller.abort()), use fake timers
or advance timers to trigger the REQUEST_TIMEOUT_MS path, assert that the retry
logic runs as expected and that the final error is handled/logged (and that
clearTimeout is still called or timers are cleaned up). Keep the test focused:
simulate a timeout-induced AbortError, verify retries/exhaustion behavior and
final error handling/logging rather than changing production code.

Comment on lines +210 to +211
// Try auto-refresh if enabled and near expiry
await maybeRefreshLicense();
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential race condition with concurrent validation calls.

If getValidatedLicenseData() is called multiple times concurrently before the first call completes, each call will invoke await maybeRefreshLicense() (line 211), potentially causing multiple concurrent API requests to the license server.

Consider adding a guard to ensure only one refresh attempt happens at a time, or document that callers should avoid concurrent calls during initialization.

Would you like me to propose a solution using a promise-based lock pattern to prevent concurrent refreshes?

🤖 Prompt for AI Agents
In packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
around lines 210-211, concurrent callers invoking await maybeRefreshLicense()
can trigger multiple simultaneous refresh requests; add a single-flight guard by
introducing a module-scoped variable (e.g., refreshInFlight: Promise<void> |
null) so maybeRefreshLicense checks if refreshInFlight exists and if so returns
await refreshInFlight, otherwise assigns refreshInFlight to the promise
representing the ongoing refresh, awaits it, and clears refreshInFlight in a
finally block; ensure errors from the refresh propagate to callers and that the
guard covers both success and failure paths.

Comment on lines +45 to +57
def fetched_at
timestamp = read&.dig("fetched_at")
Time.parse(timestamp) if timestamp
rescue ArgumentError
nil
end

def expires_at
timestamp = read&.dig("expires_at")
Time.parse(timestamp) if timestamp
rescue ArgumentError
nil
end
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add require "time" for Time.parse.

Time.parse is defined in Ruby's time library, which is not loaded by default. While it may work due to transitive requires, it's best to explicitly require it.

🔎 Proposed fix

Add to the requires at the top of the file:

 require "json"
 require "digest"
 require "fileutils"
+require "time"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb lines 45-57: the
methods use Time.parse but the file doesn't explicitly require Ruby's time
library; add require "time" near the top of the file alongside the other
requires (or at the top-level requires block) so Time.parse is available, then
run tests to confirm no further load issues.

Copy link
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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
react_on_rails_pro/LICENSE_SETUP.md (1)

1-1: Fix broken external link in documentation.

The https://licenses.shakacode.com link referenced on line 96 is currently inaccessible (HTTP 502 Bad Gateway). This link directs users to the "React on Rails Pro dashboard" to retrieve their license key. Update or replace this URL with a working alternative, or provide an alternative method for users to access their license management dashboard.

The other external links (https://shakacode.com/react-on-rails-pro and https://shakacode.com) are accessible and working correctly.

react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)

100-144: RBS signature file is out of sync and must be updated to match the initialize method.

The initialize method in react_on_rails_pro/lib/react_on_rails_pro/configuration.rb now includes four new parameters (concurrent_component_streaming_buffer_size, auto_refresh_license, license_api_url, license_key), but the corresponding RBS signature file at react_on_rails_pro/sig/react_on_rails_pro/configuration.rbs is missing:

  • The four new parameters in the initialize method signature (lines 52-78)
  • The corresponding attr_accessor declarations for these attributes (lines 26-50)
  • The four new DEFAULT_* constants (lines 3-24)

Update the RBS file to include all four parameters and their attribute declarations, then validate with bundle exec rake rbs:validate and bundle exec rake rbs:steep.

🧹 Nitpick comments (7)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)

28-35: Consider handling JSON parse errors explicitly.

If the API returns a 200 status but with malformed or empty JSON, JSON.parse(response.body.to_s) will raise JSON::ParserError. While this is caught by the StandardError rescue, it might be worth logging a more specific message for this case to aid debugging.

🔎 Proposed enhancement for clearer error handling
         return nil if response.is_a?(HTTPX::ErrorResponse)
         return nil unless response.status == 200

         Rails.logger.debug { "[ReactOnRailsPro] License fetched successfully" }
-        JSON.parse(response.body.to_s)
+        begin
+          JSON.parse(response.body.to_s)
+        rescue JSON::ParserError => e
+          Rails.logger.warn { "[ReactOnRailsPro] License fetch received invalid JSON: #{e.message}" }
+          nil
+        end
       rescue StandardError => e
         Rails.logger.warn { "[ReactOnRailsPro] License fetch failed: #{e.message}" }
         nil
packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (1)

85-103: Consider adding logging when config file is found but has issues.

The function silently ignores errors when reading the config file. While this is graceful fallback behavior, a debug-level log might help users troubleshoot configuration issues.

🔎 Optional: Add debug logging
   // Try config file
   try {
     const configPath = path.join(process.cwd(), 'config', 'react_on_rails_pro_license.key');
     if (fs.existsSync(configPath)) {
       return fs.readFileSync(configPath, 'utf8').trim();
     }
-  } catch {
+  } catch (error) {
     // Ignore errors reading config file
+    console.debug?.(`[React on Rails Pro] Could not read license file: ${error}`);
   }
packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (1)

105-136: Solid cache write implementation with security considerations.

The function:

  • Creates directory recursively if needed
  • Validates license key is configured before writing
  • Sets restrictive file permissions (0600)
  • Handles errors gracefully with warning logs

One minor note: fs.writeFileSync followed by fs.chmodSync has a brief window where the file exists with default permissions. Consider using fs.writeFileSync with mode option, though this is a very minor concern for a cache file.

🔎 Optional: Atomic permission setting
-    fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2));
-
-    // Set file permissions to 0600 (owner read/write only)
-    fs.chmodSync(cachePath, 0o600);
+    fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2), { mode: 0o600 });
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (1)

95-99: Clarify the bail+throw pattern for future maintainers.

The throw new UnauthorizedError() after bail() is needed because bail() doesn't return never in TypeScript. Consider adding a comment or using a type assertion.

🔎 Optional: Clarify the pattern
           if (response.status === 401 || response.status === 403) {
             // Don't retry auth errors - bail immediately
             bail(new UnauthorizedError());
-            throw new UnauthorizedError(); // TypeScript needs this
+            // TypeScript requires a throw here because bail() doesn't return `never`.
+            // The throw is unreachable at runtime since bail() aborts the retry loop.
+            throw new UnauthorizedError();
           }
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (2)

35-36: Non-atomic file write and permission setting could leave file temporarily exposed.

There's a brief window between File.write and File.chmod where the file has default permissions (typically 0644). For sensitive license data, consider using a more secure approach.

🔎 Suggested atomic write approach
-        File.write(cache_path, JSON.pretty_generate(cache_data))
-        File.chmod(0o600, cache_path)
+        # Write to temp file with correct permissions, then atomic rename
+        temp_path = cache_path.sub_ext(".tmp")
+        File.open(temp_path, File::WRONLY | File::CREAT | File::TRUNC, 0o600) do |f|
+          f.write(JSON.pretty_generate(cache_data))
+        end
+        File.rename(temp_path, cache_path)

45-56: Consider using Time.iso8601 for stricter parsing.

Since timestamps are stored in ISO8601 format (line 32), using Time.iso8601 instead of Time.parse would provide stricter validation and clearer intent. Time.parse is more permissive and could accept malformed dates.

🔎 Suggested change
       def fetched_at
         timestamp = read&.dig("fetched_at")
-        Time.parse(timestamp) if timestamp
-      rescue ArgumentError
+        Time.iso8601(timestamp) if timestamp
+      rescue ArgumentError, TypeError
         nil
       end

       def expires_at
         timestamp = read&.dig("expires_at")
-        Time.parse(timestamp) if timestamp
-      rescue ArgumentError
+        Time.iso8601(timestamp) if timestamp
+      rescue ArgumentError, TypeError
         nil
       end
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (1)

68-69: Consider rescuing file read errors.

While config_path.exist? reduces risk, there's still a race condition where the file could be deleted or become unreadable between the check and read. Also, encoding issues or permission problems could cause exceptions.

🔎 Suggested defensive approach
         config_path = Rails.root.join("config", "react_on_rails_pro_license.key")
-        return File.read(config_path).strip if config_path.exist?
+        return File.read(config_path).strip if config_path.exist? && config_path.readable?
+      rescue Errno::ENOENT, Errno::EACCES
+        nil
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5618568 and 299e54a.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • packages/react-on-rails-pro-node-renderer/package.json
  • packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts
  • packages/react-on-rails-pro-node-renderer/src/master.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/README.md
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
🧰 Additional context used
📓 Path-based instructions (2)
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rb: Run bundle exec rubocop and fix ALL violations before every commit/push
Run bundle exec rubocop (MANDATORY) before every commit to ensure zero offenses

Files:

  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
{package.json,webpack.config.js,packages/*/package.json,react_on_rails_pro/package.json}

📄 CodeRabbit inference engine (CLAUDE.md)

When resolving merge conflicts in build configuration files, verify file paths are correct by running grep -r 'old/path' . and test affected scripts like pnpm run prepack before continuing the merge

Files:

  • packages/react-on-rails-pro-node-renderer/package.json
🧠 Learnings (25)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages

Applied to files:

  • packages/react-on-rails-pro-node-renderer/src/master.ts
  • react_on_rails_pro/README.md
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • packages/react-on-rails-pro-node-renderer/package.json
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/docs/configuration.md
  • packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)

Applied to files:

  • react_on_rails_pro/README.md
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • packages/react-on-rails-pro-node-renderer/package.json
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.

Applied to files:

  • react_on_rails_pro/README.md
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /{CHANGELOG.md,CHANGELOG_PRO.md} : Use format `[PR 1818](https://github.com/shakacode/react_on_rails/pull/1818) by [username](https://github.com/username)` in changelog entries (no hash in PR number)

Applied to files:

  • react_on_rails_pro/README.md
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • react_on_rails_pro/README.md
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`

Applied to files:

  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • react_on_rails_pro/README.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • react_on_rails_pro/README.md
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • packages/react-on-rails-pro-node-renderer/package.json
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • react_on_rails_pro/README.md
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2024-06-27T05:22:46.156Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1622
File: spec/dummy/spec/rake/assets_precompile_rake_spec.rb:12-12
Timestamp: 2024-06-27T05:22:46.156Z
Learning: When stubbing environment variables in RSpec tests, use `before` and `after` hooks to ensure that the original values are restored after the tests, preventing any side effects on other tests. Example provided by justin808:
```ruby
describe "My test" do
  before do
    original_value = ENV["VARIABLE_NAME"]
    allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_return("stubbed_value")
  end

  after do
    allow(ENV).to receive(:[]).with("VARIABLE_NAME").and_call_original
    ENV["VARIABLE_NAME"] = original_value
  end

  it "tests something" do
    # Your test code here
  end
end
```
This practice ensures test isolation and reliability.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.

Applied to files:

  • react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to lib/react_on_rails/engine.rb : In Rails Engine code, DO NOT use `rake_tasks` block to explicitly load rake files from `lib/tasks/` - Rails::Engine automatically loads them. Only use `rake_tasks` block if tasks are in non-standard location, need programmatic generation, or need to pass context

Applied to files:

  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results

Applied to files:

  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Install Playwright browsers with `cd react_on_rails/spec/dummy && pnpm playwright install --with-deps` before running E2E tests

Applied to files:

  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use `app('clean')`, `appEval(code)`, `appFactories(options)`, and `appScenario(name)` helpers from cypress-on-rails in Playwright tests for Rails integration

Applied to files:

  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/app_commands/**/*.rb : Create custom Rails app commands in `e2e/playwright/app_commands/` directory with .rb files for reusable helpers in Playwright tests

Applied to files:

  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Shakapacker configuration should be tested by creating debug scripts in dummy app root (debug-webpack-rules.js, debug-webpack-with-config.js) to inspect webpack rules before committing configuration changes

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/docs/configuration.md
🧬 Code graph analysis (10)
packages/react-on-rails-pro-node-renderer/src/master.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts (1)
  • Config (25-89)
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
  • getValidatedLicenseData (204-247)
react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (5)
  • configure (4-7)
  • configuration (9-38)
  • license_api_url (217-222)
  • license_key (202-207)
  • auto_refresh_enabled? (234-236)
packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (4)
  • getFetchedAt (150-161)
  • getExpiresAt (167-178)
  • writeCache (105-136)
  • getCachedToken (141-144)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
  • isAutoRefreshEnabled (51-60)
  • fetchLicense (66-131)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
  • isAutoRefreshEnabled (51-60)
  • fetchLicense (66-131)
react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)
  • validated_license_data! (6-200)
  • validated_license_data! (15-44)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (3)
  • should_check_for_refresh? (41-54)
  • maybe_refresh_license (13-21)
  • seed_cache_if_needed (26-39)
react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (6)
  • cache_dir (75-77)
  • write (27-39)
  • cache_path (79-81)
  • token (41-43)
  • fetched_at (45-50)
  • expires_at (52-57)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (1)
  • configure (4-7)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
  • configuration (9-38)
  • auto_refresh_enabled? (234-236)
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (4)
  • write (27-39)
  • token (41-43)
  • expires_at (52-57)
  • fetched_at (45-50)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (3)
  • configuration (9-38)
  • auto_refresh_enabled? (234-236)
  • license_api_url (217-222)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RouterApp.server.jsx (1)
  • controller (10-10)
react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
  • api_url (44-46)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
  • configure (4-7)
  • license_api_url (217-222)
🪛 GitHub Actions: Check Markdown Links
react_on_rails_pro/LICENSE_SETUP.md

[error] 1-1: Network error: Connection failed. Check network connectivity and firewall settings

🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts

[high] 50-50: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 70-70: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: rspec-dummy-app-node-renderer
  • GitHub Check: dummy-app-node-renderer-e2e-tests
🔇 Additional comments (51)
packages/react-on-rails-pro-node-renderer/package.json (1)

23-23: Verify package version validity and security.

The async-retry version (^1.3.3) is current and has no known security vulnerabilities. Version 1.3.3 is the latest available release (published 2021-08-17), is not deprecated, and is licensed under MIT. The corresponding type definitions (@types/async-retry@1.4.9) are compatible and properly included.

react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb (1)

24-26: LGTM! WebMock configuration is appropriate.

The addition of WebMock with its HTTPX adapter enables proper HTTP request stubbing for the new license-fetching tests, which is essential for testing the automatic license renewal feature without making real HTTP calls.

react_on_rails_pro/lib/react_on_rails_pro.rb (1)

13-15: LGTM! Module requires are appropriately ordered.

The new license-related modules are correctly placed between license_public_key and license_validator, following a logical dependency order.

react_on_rails_pro/docs/installation.md (1)

50-51: LGTM! Clear guidance for paid subscribers.

The note appropriately directs paid subscribers to use the dedicated license key environment variable for automatic renewal, with a reference to detailed setup documentation.

packages/react-on-rails-pro-node-renderer/src/master.ts (1)

14-17: LGTM! Async transformation is appropriate.

Converting masterRun to async and awaiting getValidatedLicenseData() is necessary to support the automatic license refresh flow, which includes HTTP requests and cache operations. The validation correctly blocks startup to ensure a valid license before forking workers.

react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb (1)

45-50: LGTM! Helpful configuration example.

The commented configuration block provides clear guidance for developers testing the license auto-refresh feature locally with a licensing app, without affecting the existing test suite.

packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts (1)

45-45: LGTM! Consistent async handling.

Awaiting master.default(config) is consistent with the async transformation of the master initialization process and ensures license validation completes before the renderer is considered ready.

react_on_rails_pro/README.md (1)

297-298: LGTM! Consistent documentation guidance.

The note is consistent with the similar guidance in installation.md and appropriately directs paid subscribers to use the dedicated license key environment variable for automatic renewal.

react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb (4)

301-320: LGTM! Proper test setup with ENV cleanup.

The test suite correctly includes an after hook to clean up environment variables, following best practices for test isolation. The auto_refresh_license tests appropriately cover defaults and mutability.


322-338: LGTM! Complete coverage for license_api_url configuration.

The tests appropriately verify both the default value and customization of the license_api_url configuration option.


340-379: LGTM! Comprehensive license_key precedence testing.

The tests thoroughly cover the license key sourcing logic, including environment variable precedence and edge cases like empty ENV values, matching the implementation in configuration.rb.


381-427: LGTM! Thorough auto_refresh_enabled? predicate testing.

The tests comprehensively verify the auto_refresh_enabled? logic, covering all combinations of auto_refresh_license and license_key states, including edge cases like empty strings and environment-based configuration.

react_on_rails_pro/docs/configuration.md (1)

165-186: LGTM! Clear documentation for auto-refresh configuration.

The new LICENSE AUTO-REFRESH CONFIGURATION section is well-structured and provides clear guidance on the three new configuration options. The cross-reference to LICENSE_SETUP.md for detailed documentation is appropriate.

react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb (1)

1-185: LGTM! Comprehensive test coverage for license fetcher.

The test suite provides thorough coverage of the LicenseFetcher module:

  • Configuration-driven behavior (auto_refresh on/off, license_key presence)
  • Successful API fetch with proper Authorization header
  • All major error scenarios (401, 404, 500, timeout, network errors, invalid JSON)
  • ENV variable override support
  • Proper test isolation with WebMock
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)

1-206: LGTM! Thorough test coverage for Node license fetcher.

The test suite provides comprehensive coverage:

  • isAutoRefreshEnabled logic with all configuration combinations and case-insensitive values
  • fetchLicense success and error paths
  • Retry logic verification using fake timers
  • User-Agent header inclusion
  • Custom API URL support
  • Proper mocking and cleanup
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)

29-31: LGTM! Cache seeding on first boot.

The call to LicenseRefreshChecker.seed_cache_if_needed after successful validation ensures the cache is populated on first boot, enabling subsequent auto-refresh checks.


158-180: LGTM! Cache-first license loading with auto-refresh.

The updated load_license_string method correctly implements:

  1. Pre-refresh check via maybe_refresh_license
  2. Cache-first retrieval when auto-refresh is enabled
  3. Fallback to ENV and config file sources
  4. Clear priority documentation in comments
react_on_rails_pro/LICENSE_SETUP.md (2)

79-142: LGTM! Comprehensive auto-refresh documentation.

The new "Automatic License Renewal" section provides clear guidance:

  • Helpful comparison table (LICENSE_KEY vs LICENSE_TOKEN)
  • Straightforward setup instructions
  • Clear explanation of refresh schedule thresholds
  • Privacy assurances about data transmission
  • Instructions for disabling auto-refresh in air-gapped environments

350-360: LGTM! Helpful FAQ additions.

The new FAQ entries clearly distinguish LICENSE from LICENSE_KEY and guide users on when to use each option.

react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb (4)

258-323: LGTM! Token source priority tests verify cache-first behavior.

The tests correctly validate the priority order:

  1. Cached token (when auto-refresh enabled)
  2. ENV variable (fallback)
  3. Cache seeding when cache is empty

325-471: LGTM! Comprehensive boundary testing for refresh thresholds.

The should_check_for_refresh? tests thoroughly cover all critical boundaries:

  • 30 days (no checks)

  • 30 days boundary (weekly checks)
  • 8 days boundary (still weekly)
  • 7 days boundary (daily checks)
  • 3 days (daily checks)
  • nil fetched_at (triggers fetch)

The comment on lines 346-349 explaining the timing race with 31 days demonstrates thoughtful test design.


473-538: LGTM! maybe_refresh_license behavior properly tested.

Tests verify:

  • No fetch when auto_refresh disabled or should_check_for_refresh? returns false
  • Cache write on successful fetch
  • No cache write on fetch failure

540-601: LGTM! seed_cache_if_needed covers all scenarios.

Tests validate seeding logic across:

  • auto_refresh disabled (no write)
  • Cache already has token (no write)
  • Cache empty with ENV token (write with token and expiry)
  • No token available (no write)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (1)

1-254: LGTM! Comprehensive cache test coverage.

The test suite thoroughly validates:

  • readCache: non-existent files, license_key_hash validation, valid cache, JSON errors
  • writeCache: directory creation, correct data structure, file permissions (0600), error handling
  • Accessor functions: getCachedToken, getFetchedAt, getExpiresAt

The static analysis warnings on lines 50 and 70 are false positives—these are test JWT token examples, not real secrets.

react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (1)

1-283: LGTM! Thorough cache implementation tests.

The test suite provides comprehensive coverage:

  • read: all edge cases including hash validation, ENV override, invalid data
  • write: data merging, hash/timestamp addition, permissions, directory creation, error handling
  • Accessors (token, fetched_at, expires_at): valid and invalid scenarios

The use of FakeFS for filesystem isolation and time freezing for deterministic timestamps demonstrates solid testing practices.

react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)

16-36: Implementation looks solid overall.

The fetch method correctly:

  • Guards with auto_refresh_enabled? check
  • Uses HTTPX retry plugin appropriately
  • Includes timeout and proper headers
  • Handles error responses gracefully
  • Returns nil on failures for graceful fallback
packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts (3)

1-45: Comprehensive test setup with proper mock isolation.

The test file correctly:

  • Mocks dependencies before importing the module under test
  • Resets environment variables and mocks in beforeEach
  • Restores all mocks in afterEach
  • Suppresses console output during tests

47-109: Thorough coverage of refresh timing logic.

The shouldCheckForRefresh tests appropriately cover:

  • No expiry data case
  • Far expiry (>30 days) - no refresh needed
  • 30-day window with weekly check logic
  • 7-day window with daily check logic
  • Never-fetched edge case

131-150: Verify the expected cache write payload matches the implementation.

The test expects writeCache to be called with only token and expires_at, but looking at licenseCache.ts line 104-135, writeCache also adds fetched_at and license_key_hash internally. Ensure the test's expectation aligns with the actual function signature.

packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts (3)

36-55: Consider edge case when exactly on 7-day or 30-day boundary.

When daysUntilExpiry equals exactly 7 or 30, the <= conditions work correctly. However, Math.floor truncates, so a license expiring in 7.9 days would show as 7 days. This seems intentional but worth noting for clarity.

The logic is correct for the documented behavior.


61-79: Clean async orchestration with proper guards.

The function:

  • Checks isAutoRefreshEnabled() first
  • Then checks shouldCheckForRefresh() to avoid unnecessary API calls
  • Only writes to cache on successful fetch
  • Returns void rather than success/failure, relying on cache state

112-133: Seed function correctly handles first-boot scenario.

The function properly:

  • Guards on auto-refresh being enabled
  • Avoids overwriting existing cache
  • Seeds from ENV or file
  • Converts Unix timestamp to ISO string for cache storage
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (4)

5-7: Clean integration of auto-refresh modules.

The new imports properly bring in the required functionality for cache-first loading and refresh orchestration.


88-96: Cache-first loading when auto-refresh is enabled.

Good pattern: when auto-refresh is enabled, prefer the cached (potentially refreshed) token over the static ENV/file token. This ensures users benefit from renewed licenses.


204-227: Async flow integrates refresh before validation.

The sequence is correct:

  1. Return cached data if already validated
  2. Try auto-refresh if enabled and near expiry
  3. Load and decode license
  4. Validate license data
  5. Cache results
  6. Seed cache for future boots

One observation: seedCacheIfNeeded is called even if the token came from cache (since validation passed). This is harmless but could be optimized to skip if isAutoRefreshEnabled() && getCachedToken() was already true in loadLicenseString.


255-273: Async wrappers for public API.

The isEvaluation and getGraceDaysRemaining functions correctly await getValidatedLicenseData() before accessing cached data.

packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (2)

33-35: Truncated hash provides reasonable security.

Using the first 16 characters of SHA-256 (64 bits) provides sufficient collision resistance for cache invalidation purposes while keeping storage compact.


150-178: Accessors handle errors defensively.

Both getFetchedAt and getExpiresAt properly handle:

  • Missing cache data
  • Invalid date strings (via try-catch)

The pattern of returning null on any error is appropriate for optional cache data.

packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts (3)

11-21: Proper mock isolation for auto-refresh modules.

The mocks correctly disable auto-refresh behavior during tests:

  • isAutoRefreshEnabled returns false
  • getCachedToken returns null
  • maybeRefreshLicense resolves immediately
  • seedCacheIfNeeded is a no-op

This ensures tests focus on license validation logic without refresh side effects.


83-94: Re-mocking after resetModules is necessary.

Since jest.resetModules() clears the module cache, the mocks need to be re-established using jest.doMock. This is the correct pattern for dynamic module mocking.


109-124: Test correctly updated for async API.

The test properly:

  • Uses async function declaration
  • Awaits getValidatedLicenseData()
  • Validates the returned data structure
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)

51-60: Auto-refresh enabled logic is correct.

The function properly:

  • Requires a license key to be set
  • Defaults to enabled unless explicitly set to 'false'
  • Uses case-insensitive comparison

80-118: Robust retry implementation with proper timeout handling.

The implementation correctly:

  • Uses AbortController with setTimeout for request timeout
  • Clears timeout in finally block to prevent leaks
  • Bails immediately on auth errors (no point retrying)
  • Retries on transient failures (non-200 status)
  • Logs retry attempts with error context
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)

65-68: Default values align with PR requirements.

  • DEFAULT_AUTO_REFRESH_LICENSE = true enables auto-refresh by default
  • DEFAULT_LICENSE_API_URL points to the licensing service
  • TODO comment appropriately notes pending deployment

199-236: Configuration accessors with proper ENV override pattern.

The methods correctly:

  • Check environment variables first (consistent with 12-factor app principles)
  • Fall back to configured values
  • Provide default for API URL
  • Combine both conditions in auto_refresh_enabled?

Environment variable names (REACT_ON_RAILS_PRO_LICENSE_KEY, REACT_ON_RAILS_PRO_LICENSE_API_URL) match the TypeScript implementation in licenseFetcher.ts and licenseCache.ts.

react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (3)

15-25: LGTM on the read method structure.

The method correctly handles cache file absence, JSON parsing errors, and validates the cached data against the current license key before returning. Good defensive programming with the rescue clause.


68-73: LGTM on hash truncation for cache validation.

Using the first 16 hex characters (64 bits) of SHA256 is sufficient for detecting license key changes. This isn't used for cryptographic security, just cache invalidation.


75-81: LGTM on cache path configuration.

Using Rails.root.join("tmp") is appropriate for cache files. The Pathname-based approach is clean and handles path construction correctly.

react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (3)

13-21: LGTM on refresh orchestration.

The method has clean guard clauses and properly delegates to LicenseFetcher and LicenseCache. The graceful fallback behavior (returning early on nil response) aligns with the PR objectives.


41-54: LGTM on refresh timing logic.

The implementation correctly follows the documented refresh schedule. The approach of returning false when expires_at is nil is a safe default.


56-61: LGTM on cooldown enforcement.

Returning true when fetched_at is nil correctly allows the first fetch attempt. The time comparison is straightforward.

Comment on lines +33 to +38
expires_at = Time.at(license_data["exp"])

LicenseCache.write(
"token" => token,
"expires_at" => expires_at.iso8601
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add guard for missing or invalid exp claim.

If license_data["exp"] is nil or not a valid numeric timestamp, Time.at(nil) will raise a TypeError. Consider adding a guard clause.

🔎 Suggested fix
       def seed_cache_if_needed(license_data)
         return unless ReactOnRailsPro.configuration.auto_refresh_enabled?
         return if LicenseCache.token.present? # Cache already exists

         token = load_token_from_env_or_file
         return unless token

+        exp = license_data["exp"]
+        return unless exp.is_a?(Numeric)
+
-        expires_at = Time.at(license_data["exp"])
+        expires_at = Time.at(exp)

         LicenseCache.write(
           "token" => token,
           "expires_at" => expires_at.iso8601
         )
       end
🤖 Prompt for AI Agents
In react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb around
lines 33 to 38, add a guard that verifies license_data["exp"] is present and a
valid integer timestamp before calling Time.at; attempt to convert the claim
with Integer(license_data["exp"]) (rescuing ArgumentError/TypeError) and, on
failure, log or handle the invalid/missing claim and avoid calling Time.at (do
not write an invalid expires_at into LicenseCache), otherwise compute expires_at
= Time.at(valid_exp) and write the token and expires_at.iso8601 to the cache.

@claude
Copy link

claude bot commented Dec 26, 2025

Code Review: Automatic License Renewal Feature

Great work on this comprehensive feature! The implementation is well-designed and thoroughly tested (118 new tests!). However, I've identified 2 critical race conditions that need to be addressed before merge, plus several medium-priority improvements.


Critical Issues (Must Fix)

1. Race Condition: Non-Atomic Cache File Writes

Severity: HIGH
Files: license_cache.rb:27-37, licenseCache.ts:104-134

Problem: Multiple processes can simultaneously write to cache file, causing corrupted JSON.

Current Code:

File.write(cache_path, JSON.pretty_generate(cache_data))
File.chmod(0o600, cache_path)  # NOT atomic

Fix: Use atomic rename pattern:

  • Write to temp file with process ID
  • Set permissions on temp file
  • Atomic rename to final location

Add test for concurrent writes to verify no corruption.


2. Race Condition: Duplicate API Fetch on Startup

Severity: MEDIUM
Files: license_refresh_checker.rb, licenseRefreshChecker.ts

Problem: During deployment, multiple processes trigger simultaneous API fetches (10+ workers × 2 retries = 20-30 requests).

Fix: Add file-based lock using File.flock(LOCK_NB) to deduplicate API calls.


Important Issues (Should Fix)

3. Security Documentation

Add best practices to LICENSE_SETUP.md:

  • Never commit license key to git
  • Use encrypted credentials in production
  • File permissions 0600
  • Key rotation procedure

4. Blocking Network Call on Boot

await getValidatedLicenseData() can block startup for 10s if API down. Move refresh to background using setImmediate().

5. Observability

Add structured logging for refresh events, expiry dates, and success/failure status.


Nice-to-Have Improvements

  1. Increase hash from 16 to 32 chars (better collision resistance)
  2. Set cache directory permissions to 0700
  3. Make 7-day/30-day thresholds configurable

Strengths

  1. Excellent test coverage (118 new tests)
  2. Comprehensive error handling
  3. Smart refresh schedule
  4. Graceful degradation
  5. Security-conscious design
  6. Good documentation
  7. Backward compatible

Summary

Must fix before merge:

  • Issue 1: Atomic cache writes
  • Issue 2: API fetch deduplication

Should fix: Security docs, performance, observability

Once the critical race conditions are addressed, this will be a robust feature. Great work! 🎉

@claude
Copy link

claude bot commented Dec 26, 2025

Comprehensive Code Review

I've thoroughly reviewed all code changes in this PR. Overall, this is high-quality code with excellent test coverage and thoughtful architecture. Here are my findings:


🔴 Critical Issues - Must Fix Before Merge

1. Negative Days Until Expiry Bug

Location: license_refresh_checker.rb and licenseRefreshChecker.ts

Issue: If the license is already expired, days_until_expiry will be negative, but the logic doesn't handle this:

days_until_expiry = ((expires_at - Time.now) / 1.day).to_i
# If license expired 1 day ago: days_until_expiry = -1
# This matches: days_until_expiry <= 7 ✅ (triggers daily refresh)

This could trigger daily refresh attempts for expired licenses indefinitely.

Fix: Add explicit check in both implementations:

return false if days_until_expiry < 0  # Don't refresh if already expired

2. File Permission Race Condition

Location: license_cache.rb:write and licenseCache.ts:write

Issue: Between File.write() and File.chmod(), the file exists with default permissions (typically 0644), briefly exposing the license token:

File.write(cache_path, JSON.pretty_generate(cache_data))
File.chmod(0o600, cache_path)  # File is publicly readable until this line

Fix: Use atomic write with proper permissions:

# Ruby
File.write(cache_path, JSON.pretty_generate(cache_data), perm: 0o600)

# TypeScript
fs.writeFileSync(cachePath, JSON.stringify(cacheData, null, 2), { mode: 0o600 });

🟡 High Priority - Should Fix Soon

3. Thread Safety: File Cache Race Condition

Location: All cache operations

Issue: In multi-threaded Rails (Puma) or multi-process Node Renderer, multiple processes might:

  1. Both see should_check_for_refresh? == true
  2. Both fetch from API (wasted API calls)
  3. Both write to cache (last write wins)

Fix: Add file locking:

def write(data)
  File.open(cache_path, File::RDWR | File::CREAT, 0o600) do |f|
    f.flock(File::LOCK_EX)
    f.write(JSON.pretty_generate(cache_data))
    f.flush
  end
end

For TypeScript, consider using the proper-lockfile package.

4. Blocking Startup on Network Delay

Location: master.ts:masterRun

Issue: License validation blocks Node Renderer startup:

await getValidatedLicenseData();  // Blocks for up to 5-15 seconds if network is slow

Recommendation: Make refresh non-blocking:

// Validate with cached/ENV license first (fast)
const licenseData = await getValidatedLicenseData();

// Trigger async refresh in background (don't block startup)
maybeRefreshLicense().catch(err => log.warn('Background refresh failed:', err));

🟢 Minor Improvements

5. TypeScript: Unreachable Code

Location: licenseFetcher.ts:133

bail(new UnauthorizedError());
throw new UnauthorizedError(); // TypeScript needs this - but bail() already throws

If bail() already throws, the second throw is unreachable. Consider using return or clarifying the comment.

6. Ruby: Explicit HTTP Status Handling

Location: license_fetcher.rb

Consider matching TypeScript's explicit 401/403 handling:

case response.status
when 401, 403
  Rails.logger.error { "[ReactOnRailsPro] License fetch unauthorized" }
  return nil
when 200
  # success
else
  Rails.logger.warn { "[ReactOnRailsPro] License fetch failed: HTTP #{response.status}" }
  return nil
end

7. Security: Add HTTPS Validation

Location: Configuration

Add validation to prevent accidental HTTP usage in production:

if (!url.startsWith('https://') && process.env.NODE_ENV === 'production') {
  throw new Error('License API URL must use HTTPS in production');
}

✅ Strengths

Excellent work on:

  • 🎯 Clean separation of concerns (fetch, cache, refresh checker)
  • 🧪 Comprehensive test coverage (110 Ruby tests, 54 TypeScript tests)
  • 🔒 Proper security (0600 file permissions, cache hash validation)
  • 🛡️ Graceful error handling and fallback behavior
  • 📚 Well-documented code
  • ⚖️ Consistent implementation between Ruby and TypeScript

Test coverage is particularly impressive:

  • ✅ ENV variable precedence
  • ✅ Network failure scenarios (401, 403, 500, timeout)
  • ✅ All timing thresholds (7 days, 30 days, daily/weekly)
  • ✅ Cache validation with different license keys
  • ✅ JSON parse errors and file I/O errors

📋 Missing Test Cases (Optional)

Consider adding:

  1. Test for already-expired licenses (negative days until expiry)
  2. Test for concurrent cache writes (if easily testable)
  3. Test for invalid ISO8601 dates in cache
  4. Test for cache directory creation failure

Overall Assessment

This is production-ready code with the critical fixes applied. The architecture is solid, error handling is thorough, and test coverage is excellent. Main concerns are edge cases around thread safety and expired licenses.

Recommended merge strategy:

  1. Fix critical issues (TODO for first version #1, Make work with turbolinks and better helper #2) - Required
  2. Address high priority items (Add linting and CI scripts #3, Add integration tests #4) - Strongly recommended
  3. Consider minor improvements (Add Unit tests #5-7) - Nice to have

Great work on this feature! 🎉

@claude
Copy link

claude bot commented Dec 26, 2025

Code Review: Automatic License Renewal

Great work on this feature! The implementation is well-structured with comprehensive test coverage.

Critical Issues

1. HTTPX Thread Safety ⚠️
The Ruby implementation uses HTTPX, which has known thread-safety issues (see PR #2251). In multi-threaded Rails environments (Puma), this could cause race conditions. Consider switching to Net::HTTP (thread-safe, stdlib) or coordinating with PR #2251.

2. Missing clearTimeout in TypeScript
The abort controller timeout is not cleared after successful requests, which could cause issues. Add clearTimeout in try/catch/finally blocks.

3. TODO Comment
The DEFAULT_LICENSE_API_URL has a TODO comment. If https://licenses.shakacode.com is not the final URL, this should not be merged.

Security Concerns

1. Timing Attack - Cache validation uses direct string comparison. Use OpenSSL.fixed_length_secure_compare instead.

2. Weak Hash - Only 16 chars of SHA256 used (64 bits). Use at least 32 characters (128 bits).

Bugs

1. Race Condition - maybe_refresh_license called before seed_cache_if_needed on first boot. Cache is empty so refresh timing cannot be determined.

2. File I/O - Each cache accessor reads entire JSON file. Add memoization.

Recommendations

Must fix: HTTPX thread-safety, clearTimeout, TODO comment
Should fix: Timing attack, hash length, race condition
Nice to have: Memoization, edge case tests

Overall: Excellent design and test coverage. Address critical issues before merge!

@claude
Copy link

claude bot commented Dec 26, 2025

Code Review: Automatic License Renewal Feature

I've reviewed PR #2254 and overall this is a well-implemented feature with excellent test coverage. Here's my detailed feedback:


Strengths

1. Excellent Test Coverage

  • Ruby: 110 tests total (64 new tests for the license system)
  • TypeScript: 54 tests for Node Renderer
  • E2E tests for actual API integration
  • All critical edge cases covered (network failures, auth errors, cache invalidation)

2. Strong Security Practices

  • File permissions set to 0600 on cache files
  • License key hashing for cache validation
  • No sensitive data logged (only error messages)
  • Cache invalidation when license key changes

3. Good Architecture

  • Clean separation of concerns (fetcher, cache, refresh checker)
  • Consistent implementation between Ruby and TypeScript
  • Graceful degradation on errors
  • Proper retry logic with exponential backoff

4. Documentation

  • Comprehensive LICENSE_SETUP.md updates
  • Clear configuration examples
  • Privacy disclosure (no telemetry)
  • Good comparison table (License Key vs License Token)

Issues and Recommendations

HIGH PRIORITY

1. Race Condition in Cache File Operations

Location: licenseCache.ts:126 and license_cache.rb:82

Issue: The cache read/write operations are not atomic. Multiple Node processes could read cache simultaneously, both trigger refresh, race to write new token, and potentially corrupt the cache file.

Recommendation: Use atomic write-rename pattern. Write to a temp file first, then atomically rename it. This is the standard pattern for safe file updates.

2. Missing Cache Directory Permissions

Location: licenseCache.ts:130

Issue: If mkdirSync fails silently, chmodSync might fail on the cache file.

Recommendation: Set directory mode during creation: fs.mkdirSync(cacheDir, { recursive: true, mode: 0o700 })

3. Async/Await Consistency

Location: master.ts:14-17

Issue: While you correctly made masterRun async and await getValidatedLicenseData(), the function signature change could break existing code that doesn't await the result.

Recommendation: Check if any code calls masterRun() without awaiting. Document as breaking change if needed.


MEDIUM PRIORITY

4. HTTP Retry Logic Differences

Ruby version uses HTTPX with fixed 1s delay between retries. TypeScript version uses async-retry with exponential backoff.

Issue: Inconsistent retry behavior between implementations.

Recommendation: Align retry strategies. Exponential backoff is better for production systems.

5. Cache Timestamp Parsing Edge Case

Location: licenseCache.ts:153-158 and license_cache.rb:53-58

Issue: If cache contains invalid ISO8601 date, parsing fails silently and returns null. This could mask data corruption.

Recommendation: Log warnings for invalid timestamps to help debug cache corruption issues.

6. Environment Variable Type Safety

Location: licenseFetcher.ts:57

Issue: autoRefreshSetting?.toLowerCase() !== 'false' means any typo (fasle, False, FALSE) will enable auto-refresh.

Recommendation: Also check for '0' as falsy value for better robustness.


LOW PRIORITY

7. HTTPX Dependency Documentation

Location: license_fetcher.rb:3

Issue: The PR adds httpx as a dependency but doesn't mention it in the PR description.

Recommendation: Verify httpx is in react_on_rails_pro.gemspec and document the new dependency.

8. Test Mock Cleanup

Location: licenseCache.test.ts:32, licenseFetcher.test.ts:14

Issue: console.warn is mocked but output isn't verified in all tests where warnings are expected.

Recommendation: Add assertions for warning messages where applicable.


Security Review

  • Cache file permissions (0600) - Good!
  • No credentials in logs - Good!
  • License key hashing - Good!
  • Race conditions - Needs file locking (see issue 1)
  • HTTPS only - Good!
  • No telemetry - Good!

Performance Considerations

Positive:

  • Minimal API calls (only near expiration)
  • Effective caching strategy
  • Short timeout (5s) prevents hanging

Potential concern:

  • Cache file I/O on every boot to check fetched_at
  • Consider in-memory memoization after first read

Testing Recommendations

Great coverage overall! A few additional test cases to consider:

  1. Concurrent access test: Simulate multiple processes writing cache simultaneously
  2. Clock skew test: What happens if server time != client time?
  3. Partial file write: What if process crashes mid-write?
  4. Permission errors: What if tmp/ directory is not writable?

Documentation Review

LICENSE_SETUP.md is excellent! Clear, comprehensive, addresses common questions.

Minor suggestions:

  • Add troubleshooting section for cache corruption scenarios
  • Mention tmp/ directory requirement
  • Document how to manually clear cache if needed

Pre-Merge Checklist

Before merging, please verify:

  • httpx gem added to react_on_rails_pro.gemspec dependencies
  • async-retry package in package.json (already done)
  • CHANGELOG_PRO.md updated with this feature
  • CI passes (Playwright, RSpec, integration tests)
  • Test the feature locally with a real license key
  • Test cache invalidation when switching license keys
  • Verify no file corruption under concurrent load

Overall Assessment

Quality: 5/5
Test Coverage: 5/5
Security: 4/5 (needs file locking)
Documentation: 5/5

Recommendation: Approve with minor fixes

The implementation is solid and well-tested. The race condition issue should be addressed before production deployment, but it's not a blocker for merge if you're comfortable with the risk (unlikely to manifest in typical deployments).

Excellent work on the parallel implementation (Ruby + TypeScript), comprehensive testing, and thorough documentation!

Copy link
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: 0

🧹 Nitpick comments (1)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (1)

56-202: Strong test coverage with minor gaps.

The test suite is well-structured and covers most critical paths including retry logic, error handling, and header verification. The use of fake timers for retry tests is appropriate.

However, consider adding tests for:

  1. 403 Forbidden status: The implementation treats 403 the same as 401 (no retry), but only 401 is tested.
  2. Request timeout: The implementation uses AbortController with a timeout, but there's no explicit test for timeout scenarios.
📝 Suggested additional test cases

Add a test for 403 status:

it('returns null on 403 forbidden', async () => {
  process.env.REACT_ON_RAILS_PRO_LICENSE_KEY = 'lic_invalid';

  fetchSpy.mockResolvedValueOnce({
    status: 403,
  });

  const result = await fetchLicense();
  expect(result).toBeNull();
  expect(fetchSpy).toHaveBeenCalledTimes(1); // No retry
});

Add a test for timeout:

it('returns null on request timeout after retries', async () => {
  jest.useFakeTimers();
  process.env.REACT_ON_RAILS_PRO_LICENSE_KEY = 'lic_test123';

  fetchSpy.mockImplementation(() => 
    new Promise((_, reject) => 
      setTimeout(() => reject(new Error('Aborted')), 15000)
    )
  );

  const resultPromise = fetchLicense();
  await jest.runAllTimersAsync();
  const result = await resultPromise;

  expect(result).toBeNull();
  expect(fetchSpy).toHaveBeenCalledTimes(3);
});
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 299e54a and 4079e0e.

📒 Files selected for processing (2)
  • .lychee.toml
  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results

Applied to files:

  • .lychee.toml
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running

Applied to files:

  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
🧬 Code graph analysis (1)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (2)
  • isAutoRefreshEnabled (51-60)
  • fetchLicense (66-131)
🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts

[high] 67-67: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 186-186: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (7)
.lychee.toml (4)

1-13: LGTM! Well-configured retry and timeout settings.

The retry logic (4 retries with 15s wait) and 20s timeout are appropriate for handling transient network failures in CI while avoiding excessive delays. The accepted status codes (200 and 308) are standard.


64-65: Track the TODO for post-deployment cleanup.

The exclusion for licenses.shakacode.com is appropriate while deployment is pending. Ensure this TODO is tracked (e.g., in a follow-up issue) so the exclusion is removed once the licensing app is live.

Can you confirm:

  1. Is there a tracking issue for removing this exclusion after deployment?
  2. What is the expected deployment timeline for licenses.shakacode.com?

107-113: LGTM! Sensible path exclusions.

The excluded paths are well-justified: placeholder links in planning docs, dependencies, test fixtures, and legacy TOC structure. The comment explaining why SUMMARY.md is retained is particularly helpful.


115-120: LGTM! Appropriate caching and fragment settings.

Disabling fragment checking avoids flaky anchor validation, and 1-day cache expiry provides a good balance between freshness and CI performance.

packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (3)

1-25: LGTM: Solid test setup with proper timer cleanup.

The test setup properly addresses async-retry timer leaks mentioned in the PR objectives. The combination of jest.clearAllTimers() and jest.useRealTimers() in afterEach prevents "Cannot log after tests are done" errors, and the explanatory comment with the Jest issue reference is helpful for future maintainers.


27-54: LGTM: Comprehensive coverage of auto-refresh logic.

The test cases thoroughly cover all branches of isAutoRefreshEnabled(), including edge cases like case-insensitive handling and default behavior.


67-67: Static analysis false positive: mock test data, not real secrets.

Gitleaks flagged these JWT tokens as potential secrets, but they're clearly mock test data (note the ... truncation). This is a false positive and safe to ignore.

Also applies to: 186-186

ihabadham and others added 17 commits December 26, 2025 16:45
Add configuration options for automatic license token refresh:
- auto_refresh_license: enable/disable auto-refresh (default: true)
- license_api_url: API endpoint URL (placeholder, TODO for production)
- license_key: permanent key for API auth (ENV takes precedence)
- auto_refresh_enabled?: helper to check if properly configured

Follows existing pattern from load_license_string for ENV precedence.

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

Co-Authored-By: Claude <noreply@anthropic.com>
HTTP client to fetch license tokens from the licensing API:
- 5 second request timeout
- 2 retries with 1 second delay on transient failures
- Returns parsed JSON on success, nil on failure
- Falls back silently to cached token if fetch fails

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

Co-Authored-By: Claude <noreply@anthropic.com>
Caches license tokens to tmp/react_on_rails_pro_license.cache:
- Persists across app restarts to reduce API calls
- Validates cached token belongs to current license_key via hash
- Sets 0600 file permissions for security
- Stores token, expires_at, plan, fetched_at for refresh logic

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

Co-Authored-By: Claude <noreply@anthropic.com>
Modified load_license_string to support automatic license renewal:
- Calls maybe_refresh_license before loading token
- Cache takes priority when auto-refresh is enabled
- Falls back to ENV → config file if no cache

Refresh timing (based on cached expires_at):
- >30 days until expiry: no checks
- 8-30 days: weekly checks
- ≤7 days: daily checks

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

Co-Authored-By: Claude <noreply@anthropic.com>
Prevents fetching on every boot during the final week.
Now consistent: daily checks (1 day threshold) when ≤7 days,
weekly checks (7 day threshold) when 8-30 days.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Adds 64 new tests for the auto-refresh license feature:
- LicenseFetcher (13 tests): HTTP responses, timeouts, network errors, ENV priority
- LicenseCache (22 tests): file operations, hash validation, time parsing
- Configuration (14 tests): license_key, auto_refresh_enabled? conditions
- LicenseValidator (15 tests): token priority, timing thresholds, refresh orchestration

Uses WebMock with HTTPX adapter for HTTP stubbing and FakeFS for file system tests.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Remove separate webmock_helper.rb file and add the requires directly
to spec_helper.rb, matching the pattern used in spec/dummy/spec/.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
The original implementation had a gap: should_check_for_refresh? checked
LicenseCache.expires_at, but the cache was only populated via API refresh.
Since refresh required the cache to exist (to know the expiry), refresh
never triggered - a chicken-and-egg problem.

Fix: After successful JWT validation, if auto-refresh is enabled and
cache is empty, seed the cache with the current token's data (token +
expiry). This allows should_check_for_refresh? to work on subsequent
boots.

Flow:
1. First boot: JWT from ENV → validate → seed cache
2. Subsequent boots: cache exists → check expiry → refresh if needed

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Extract auto-refresh timing logic from LicenseValidator into a dedicated
LicenseRefreshChecker module. This reduces LicenseValidator from 264 to
201 lines and removes the need for rubocop:disable directive.

- Create LicenseRefreshChecker with: maybe_refresh_license,
  seed_cache_if_needed, should_check_for_refresh?, last_fetch_older_than?,
  load_token_from_env_or_file
- LicenseValidator now calls the module directly at call sites
- Update tests to call LicenseRefreshChecker directly instead of
  testing private methods via described_class.send()

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

Co-Authored-By: Claude <noreply@anthropic.com>
- Add LICENSE_SETUP.md section explaining auto-refresh feature:
  - License Key vs License Token comparison table
  - Setup instructions
  - How refresh timing works (weekly at 30d, daily at 7d)
  - How to disable for air-gapped environments
  - Privacy section
  - New FAQ entries
- Add configuration.md section with config options:
  - license_key
  - auto_refresh_license
  - license_api_url
- Add notes in README.md and installation.md pointing paid
  subscribers to auto-refresh documentation

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Implements Phase 4 of automatic license renewal for the Node Renderer.
When configured with REACT_ON_RAILS_PRO_LICENSE_KEY, the Node Renderer
will automatically fetch fresh license tokens when approaching expiry.

New modules:
- licenseFetcher.ts: HTTP client for license API (5s timeout, 2 retries)
- licenseCache.ts: File-based token caching with license_key validation
- licenseRefreshChecker.ts: Timing logic (weekly at 30 days, daily at 7 days)

Changes:
- licenseValidator.ts: Now async, integrates refresh before loading token
- master.ts: Awaits async license validation
- ReactOnRailsProNodeRenderer.ts: Awaits async master.default()

ENV vars (shared with Rails via .env):
- REACT_ON_RAILS_PRO_LICENSE_KEY: Permanent key for API auth
- REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE: Enable/disable (default: true)
- REACT_ON_RAILS_PRO_LICENSE_API_URL: API endpoint (default: production)

Tests: 51 tests covering all new functionality

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

Co-Authored-By: Claude <noreply@anthropic.com>
Both Ruby gem and Node Renderer now send identifying User-Agent headers:
- Ruby gem: ReactOnRailsPro-Gem
- Node Renderer: ReactOnRailsPro-NodeRenderer

Helps with server-side logging and debugging license issues.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Use jest.useFakeTimers() to control async-retry's internal setTimeout
calls, preventing timer leakage between tests.

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

Co-Authored-By: Claude <noreply@anthropic.com>
- Add User-Agent header verification test
- Add invalid JSON response handling test
- Add write failure handling test for cache

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- configuration.rb: Add ENV support for license_api_url
  (REACT_ON_RAILS_PRO_LICENSE_API_URL) to match license_key behavior.
  This enables consistent configuration via environment variables for
  both Ruby gem and Node Renderer.

- licenseFetcher.ts: Fix TypeScript errors in async-retry callback:
  - Add generic type parameter to retry<LicenseResponse>()
  - Properly handle unknown error type in onRetry callback

- dummy app: Add commented license config example for local testing

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Copy .lychee.toml from master and add exclusion for licenses.shakacode.com
which doesn't exist yet (licensing app deployment pending).

TODO: Remove the exclusion once the licensing app is deployed.

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

Co-Authored-By: Claude <noreply@anthropic.com>
Add jest.clearAllTimers() and jest.useRealTimers() to afterEach to
prevent "Cannot log after tests are done" errors caused by async-retry's
internal setTimeout callbacks escaping the fake timer boundary.

The issue occurred because async-retry schedules retries via setTimeout,
and when jest.useRealTimers() was called, pending microtasks could fire
after the test completed.

See: jestjs/jest#10487

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

Co-Authored-By: Claude <noreply@anthropic.com>
@ihabadham ihabadham force-pushed the ihabadham/pro/automatic-license-renewal branch from 4079e0e to 7593128 Compare December 26, 2025 14:47
Copy link
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: 0

♻️ Duplicate comments (2)
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)

210-211: Consider adding single-flight guard for concurrent validation calls.

A previous review comment identified a potential race condition: if getValidatedLicenseData() is called multiple times concurrently before the first call completes, each invocation will trigger maybeRefreshLicense(), potentially causing multiple simultaneous API requests.

While this scenario is unlikely during typical startup (single master process validates once), adding a promise-based lock pattern would prevent unnecessary concurrent refreshes. This is particularly relevant if the validator is called from multiple initialization paths.

🔎 Example single-flight guard pattern
+let refreshInFlight: Promise<void> | null = null;
+
 export async function getValidatedLicenseData(): Promise<LicenseData> {
   if (cachedLicenseData !== undefined) {
     return cachedLicenseData;
   }

   try {
     // Try auto-refresh if enabled and near expiry
-    await maybeRefreshLicense();
+    if (refreshInFlight) {
+      await refreshInFlight;
+    } else {
+      refreshInFlight = maybeRefreshLicense().finally(() => {
+        refreshInFlight = null;
+      });
+      await refreshInFlight;
+    }

     // Load and decode license (but don't cache yet)
     const licenseData = loadAndDecodeLicense();
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (1)

33-38: Add guard for missing or invalid exp claim.

If license_data["exp"] is nil or not a valid numeric timestamp, Time.at(nil) will raise a TypeError. This issue was flagged in a previous review but has not been addressed.

🔎 Proposed fix
       def seed_cache_if_needed(license_data)
         return unless ReactOnRailsPro.configuration.auto_refresh_enabled?
         return if LicenseCache.token.present? # Cache already exists

         token = load_token_from_env_or_file
         return unless token

+        exp = license_data["exp"]
+        return unless exp.is_a?(Numeric)
+
-        expires_at = Time.at(license_data["exp"])
+        expires_at = Time.at(exp)

         LicenseCache.write(
           "token" => token,
           "expires_at" => expires_at.iso8601
         )
       end
🧹 Nitpick comments (1)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (1)

66-67: Consider extracting repeated hash calculation to a test helper.

The crypto hash calculation logic is duplicated across four tests. Extracting this to a helper function would improve maintainability and DRY principles.

Additionally, consider using ES6 import instead of require('crypto') for consistency with the rest of the file.

🔎 Proposed refactor

Add a helper function at the top of the test suite:

+import * as crypto from 'crypto';
+
 jest.mock('fs');
 
+function computeExpectedHash(licenseKey: string): string {
+  return crypto.createHash('sha256').update(licenseKey).digest('hex').substring(0, 16);
+}
+
 describe('LicenseCache', () => {

Then replace the duplicated code. For example, at lines 66-67:

-    // Compute expected hash (first 16 chars of SHA256)
-    const crypto = require('crypto');
-    const expectedHash = crypto.createHash('sha256').update('lic_test123').digest('hex').substring(0, 16);
+    const expectedHash = computeExpectedHash('lic_test123');

Apply similar changes at lines 174-175, 201-202, and 230-231.

Also applies to: 174-175, 201-202, 230-231

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4079e0e and 7593128.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (28)
  • .lychee.toml
  • packages/react-on-rails-pro-node-renderer/package.json
  • packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts
  • packages/react-on-rails-pro-node-renderer/src/master.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/README.md
  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
✅ Files skipped from review due to trivial changes (1)
  • react_on_rails_pro/spec/react_on_rails_pro/license_validator_spec.rb
🚧 Files skipped from review as they are similar to previous changes (11)
  • react_on_rails_pro/spec/react_on_rails_pro/license_fetcher_spec.rb
  • packages/react-on-rails-pro-node-renderer/package.json
  • packages/react-on-rails-pro-node-renderer/tests/licenseRefreshChecker.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/configuration_spec.rb
  • .lychee.toml
  • react_on_rails_pro/docs/installation.md
  • react_on_rails_pro/spec/dummy/config/initializers/react_on_rails_pro.rb
  • react_on_rails_pro/lib/react_on_rails_pro.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts
  • react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseRefreshChecker.ts
🧰 Additional context used
📓 Path-based instructions (1)
**/*.rb

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.rb: Run bundle exec rubocop and fix ALL violations before every commit/push
Run bundle exec rubocop (MANDATORY) before every commit to ensure zero offenses

Files:

  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
🧠 Learnings (23)
📓 Common learnings
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG_PRO.md : Update `/CHANGELOG_PRO.md` for user-visible changes in the Pro-only react_on_rails_pro gem and npm packages

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • packages/react-on-rails-pro-node-renderer/src/master.ts
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to /CHANGELOG.md : Update `/CHANGELOG.md` for user-visible changes in the open-source react_on_rails gem and npm package (features, bug fixes, breaking changes, deprecations, performance improvements)

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • packages/react-on-rails-pro-node-renderer/src/master.ts
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2025-10-23T17:22:01.074Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1875
File: lib/react_on_rails/utils.rb:112-124
Timestamp: 2025-10-23T17:22:01.074Z
Learning: In React on Rails, when Pro is installed but not licensed, the intended behavior is to raise an error on boot. The `react_on_rails_pro?` method validates licenses and should raise errors early (including during path resolution in methods like `server_bundle?`) to enforce licensing requirements rather than failing later with obscure errors.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2025-09-15T21:24:48.207Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1781
File: node_package/src/ClientSideRenderer.ts:82-95
Timestamp: 2025-09-15T21:24:48.207Z
Learning: In React on Rails, the force_load feature includes both explicit `data-force-load="true"` usage and the ability to hydrate components during the page loading state (`document.readyState === 'loading'`). Both capabilities require a Pro license, so the condition `!railsContext.rorPro && (isComponentForceLoaded || document.readyState === 'loading')` correctly gates both scenarios.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
📚 Learning: 2025-09-16T08:01:11.146Z
Learnt from: justin808
Repo: shakacode/react_on_rails PR: 1770
File: lib/generators/react_on_rails/templates/base/base/app/javascript/src/HelloWorld/ror_components/HelloWorld.client.jsx:2-2
Timestamp: 2025-09-16T08:01:11.146Z
Learning: React on Rails uses webpack CSS Modules configuration with namedExports: true, which requires the import syntax `import * as style from './file.module.css'` rather than the default export pattern. This configuration enables better tree shaking and bundle size optimization for CSS modules.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/README.md
📚 Learning: 2025-02-12T16:38:06.537Z
Learnt from: Romex91
Repo: shakacode/react_on_rails PR: 1697
File: package-scripts.yml:28-28
Timestamp: 2025-02-12T16:38:06.537Z
Learning: The file `node_package/lib/ReactOnRails.full.js` is autogenerated during the build process and should not be present in the repository.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/README.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation in React on Rails Pro is handled through a chain of validations:
1. Pro version check in `run_stream_inside_fiber`
2. RSC support check during pack generation via `ReactOnRailsPro.configuration.enable_rsc_support`
3. RSC support validation during component registration
This makes additional validation in the helper methods unnecessary.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • react_on_rails_pro/LICENSE_SETUP.md
  • react_on_rails_pro/lib/react_on_rails_pro/configuration.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
📚 Learning: 2025-07-08T05:57:29.630Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1745
File: node_package/src/RSCRequestTracker.ts:8-14
Timestamp: 2025-07-08T05:57:29.630Z
Learning: The global `generateRSCPayload` function in React on Rails Pro (RORP) is provided by the framework during rendering requests, not implemented in application code. The `declare global` statements are used to document the expected interface that RORP will inject at runtime.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: In IDE configuration, exclude these directories to prevent slowdowns: /coverage, /tmp, /gen-examples, /packages/react-on-rails/lib, /node_modules, /react_on_rails/spec/dummy/node_modules, /react_on_rails/spec/dummy/tmp, /react_on_rails/spec/dummy/app/assets/webpack, /react_on_rails/spec/dummy/log, /react_on_rails/spec/dummy/e2e/playwright-report, /react_on_rails/spec/dummy/test-results

Applied to files:

  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use Playwright E2E tests in `react_on_rails/spec/dummy/e2e/playwright/` for React component integration testing. Tests automatically start Rails server on port 5017 before running

Applied to files:

  • react_on_rails_pro/docs/configuration.md
  • packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts
  • react_on_rails_pro/LICENSE_SETUP.md
  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
  • packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts
📚 Learning: 2025-02-13T16:50:47.848Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/clientStartup.ts:18-21
Timestamp: 2025-02-13T16:50:47.848Z
Learning: In the react_on_rails module, the `reactOnRailsPageUnloaded` function in clientStartup.ts is intentionally kept private as it's only used internally as a callback for `onPageUnloaded`.

Applied to files:

  • react_on_rails_pro/docs/configuration.md
📚 Learning: 2025-02-13T16:50:26.861Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/turbolinksUtils.ts:34-36
Timestamp: 2025-02-13T16:50:26.861Z
Learning: In React on Rails, when checking for Turbolinks version 5 using `turbolinksVersion5()`, always ensure `Turbolinks` exists first by checking `turbolinksInstalled()` to prevent TypeError when accessing properties.

Applied to files:

  • react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-02-18T13:08:01.477Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: lib/react_on_rails/helper.rb:190-197
Timestamp: 2025-02-18T13:08:01.477Z
Learning: RSC support validation is handled in deeper level calls of the React on Rails Pro codebase, so it doesn't need to be validated again in the `rsc_payload_react_component` helper method.

Applied to files:

  • react_on_rails_pro/LICENSE_SETUP.md
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Install Playwright browsers with `cd react_on_rails/spec/dummy && pnpm playwright install --with-deps` before running E2E tests

Applied to files:

  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/e2e/**/*.spec.js : Use `app('clean')`, `appEval(code)`, `appFactories(options)`, and `appScenario(name)` helpers from cypress-on-rails in Playwright tests for Rails integration

Applied to files:

  • packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts
  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to sig/react_on_rails/**/*.rbs : RBS signatures in `sig/react_on_rails/` should be added for new Ruby files in `lib/react_on_rails/`, included in Steepfile, validated with `bundle exec rake rbs:validate`, and type-checked with `bundle exec rake rbs:steep`

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/README.md
  • react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2024-10-08T20:53:47.076Z
Learnt from: theforestvn88
Repo: shakacode/react_on_rails PR: 1620
File: spec/dummy/client/app/startup/HelloTurboStream.jsx:3-3
Timestamp: 2024-10-08T20:53:47.076Z
Learning: The `RailsContext` import in `spec/dummy/client/app/startup/HelloTurboStream.jsx` is used later in the project, as clarified by the user theforestvn88.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-04-26T21:55:55.874Z
Learnt from: alexeyr-ci2
Repo: shakacode/react_on_rails PR: 1732
File: spec/dummy/client/app-react16/startup/ReduxSharedStoreApp.client.jsx:40-44
Timestamp: 2025-04-26T21:55:55.874Z
Learning: In the react_on_rails project, files under `app-react16` directories are copied/moved to corresponding `/app` directories during the conversion process (removing the `-react16` suffix), which affects their relative import paths at runtime.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Shakapacker configuration should be tested by creating debug scripts in dummy app root (debug-webpack-rules.js, debug-webpack-with-config.js) to inspect webpack rules before committing configuration changes

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
📚 Learning: 2025-12-19T18:57:59.314Z
Learnt from: CR
Repo: shakacode/react_on_rails PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-12-19T18:57:59.314Z
Learning: Applies to react_on_rails/spec/dummy/e2e/playwright/app_commands/**/*.rb : Create custom Rails app commands in `e2e/playwright/app_commands/` directory with .rb files for reusable helpers in Playwright tests

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2025-02-13T14:29:49.267Z
Learnt from: AbanoubGhadban
Repo: shakacode/react_on_rails PR: 1644
File: spec/react_on_rails/utils_spec.rb:218-218
Timestamp: 2025-02-13T14:29:49.267Z
Learning: In RSpec tests, prefer using local variables over constants within test blocks to avoid constant redefinition warnings and maintain better test isolation.

Applied to files:

  • react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb
  • react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb
📚 Learning: 2024-12-12T13:07:09.929Z
Learnt from: alexeyr-ci
Repo: shakacode/react_on_rails PR: 1644
File: node_package/src/ReactOnRailsRSC.ts:87-87
Timestamp: 2024-12-12T13:07:09.929Z
Learning: When handling errors in 'node_package/src/ReactOnRailsRSC.ts', include the error stack in error messages in development and test environments to aid debugging.

Applied to files:

  • packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts
🧬 Code graph analysis (6)
react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (1)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (3)
  • configuration (9-38)
  • auto_refresh_enabled? (234-236)
  • license_api_url (217-222)
packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (1)
react_on_rails_pro/spec/dummy/client/app/ror-auto-load-components/RouterApp.server.jsx (1)
  • controller (10-10)
react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (3)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (2)
  • seed_cache_if_needed (26-39)
  • maybe_refresh_license (13-21)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
  • configuration (9-38)
  • auto_refresh_enabled? (234-236)
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (1)
  • token (41-43)
react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (2)
react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (2)
  • configuration (9-38)
  • auto_refresh_enabled? (234-236)
react_on_rails_pro/lib/react_on_rails_pro/license_cache.rb (4)
  • write (27-39)
  • token (41-43)
  • expires_at (52-57)
  • fetched_at (45-50)
packages/react-on-rails-pro-node-renderer/src/master.ts (2)
packages/react-on-rails-pro-node-renderer/src/shared/configBuilder.ts (1)
  • Config (25-89)
packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (1)
  • getValidatedLicenseData (204-247)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (2)
react_on_rails_pro/spec/dummy/client/node-renderer.js (1)
  • process (5-5)
packages/react-on-rails-pro-node-renderer/src/shared/licenseCache.ts (5)
  • readCache (80-99)
  • writeCache (105-136)
  • getCachedToken (141-144)
  • getFetchedAt (150-161)
  • getExpiresAt (167-178)
🪛 Gitleaks (8.30.0)
packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts

[high] 67-67: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 186-186: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts

[high] 50-50: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)


[high] 70-70: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: examples (3.4, latest)
  • GitHub Check: examples (3.2, minimum)
  • GitHub Check: build-dummy-app-webpack-test-bundles (3.2, 20, minimum)
  • GitHub Check: rspec-package-tests (3.4, latest)
  • GitHub Check: rspec-package-tests (3.2, minimum)
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: build-dummy-app-webpack-test-bundles
  • GitHub Check: pro-lint-js-and-ruby
  • GitHub Check: build
  • GitHub Check: check-bundle-size
  • GitHub Check: claude-review
  • GitHub Check: markdown-link-check
🔇 Additional comments (33)
packages/react-on-rails-pro-node-renderer/tests/licenseCache.test.ts (3)

1-37: LGTM! Well-structured test setup.

The test setup follows best practices with proper mocking, environment cleanup, and mock restoration. The beforeEach/afterEach pattern ensures test isolation.


50-50: Static analysis false positives: test mock data.

The Gitleaks warnings about API keys on these lines are false positives. These are clearly test mock tokens (truncated JWT format with ...) used for testing the cache functionality, not actual secrets.

Also applies to: 70-70


39-253: Excellent comprehensive test coverage!

The test suite thoroughly covers all public API functions with appropriate edge cases:

  • Cache validation with license key hash matching
  • Filesystem error handling (missing files, parse errors, write failures)
  • Permission settings (0600)
  • Date parsing and extraction
  • Fallback behavior when cache is invalid or missing

This provides strong confidence in the licenseCache module's correctness and robustness.

packages/react-on-rails-pro-node-renderer/src/shared/licenseFetcher.ts (1)

1-131: LGTM! Clean implementation of license fetching with retry logic.

The implementation correctly handles:

  • Auto-refresh enablement checks
  • Retry logic with exponential backoff
  • Timeout via AbortController
  • Auth error bail pattern (401/403)
  • Graceful error handling with appropriate logging

Note: A previous review comment suggested adding an explicit test case for timeout/abort scenarios to ensure the AbortError path is covered. Consider this as an enhancement to test coverage.

packages/react-on-rails-pro-node-renderer/src/ReactOnRailsProNodeRenderer.ts (1)

45-45: LGTM! Correctly awaits async master initialization.

The addition of await aligns with the asynchronous license validation flow introduced in master.ts.

packages/react-on-rails-pro-node-renderer/src/master.ts (1)

14-18: LGTM! Proper async integration for license validation.

The function correctly transitions to async and awaits license validation before proceeding with worker setup.

packages/react-on-rails-pro-node-renderer/tests/licenseFetcher.test.ts (2)

19-23: Good practice: Timer cleanup prevents test leaks.

The jest.clearAllTimers() and jest.useRealTimers() calls properly address the async-retry timer leak issue mentioned in the PR objectives.


67-67: Static analysis false positives: Mock tokens are safe.

Gitleaks flags these as potential secrets, but they are test fixtures with mock JWT tokens. These are safe to ignore.

Also applies to: 186-186

packages/react-on-rails-pro-node-renderer/src/shared/licenseValidator.ts (2)

84-96: LGTM! Cache-first loading with proper fallback.

The priority order (cache → ENV → config file) correctly implements the auto-refresh flow while maintaining backward compatibility.


204-226: LGTM! Async validation flow with auto-refresh integration.

The validation sequence correctly:

  1. Checks cache
  2. Attempts auto-refresh if needed
  3. Loads and validates license
  4. Seeds cache for future refresh checks
packages/react-on-rails-pro-node-renderer/tests/licenseValidator.test.ts (2)

11-21: LGTM! Proper mocking prevents auto-refresh side effects.

The auto-refresh module mocks correctly isolate the validator tests from side effects, ensuring tests remain fast and predictable. The re-mocking after resetModules maintains isolation across test cases.

Also applies to: 83-93


109-295: LGTM! Test suite properly updated for async API.

All test cases correctly await the async validation methods, and the test coverage remains comprehensive.

react_on_rails_pro/spec/react_on_rails_pro/spec_helper.rb (1)

24-27: LGTM!

The webmock and HTTPX adapter setup is correctly ordered and provides proper HTTP stubbing support for the new license fetcher tests.

react_on_rails_pro/README.md (1)

297-298: LGTM!

Clear guidance for paid subscribers about using LICENSE_KEY for automatic renewal, with appropriate link to detailed documentation.

react_on_rails_pro/LICENSE_SETUP.md (2)

79-142: LGTM!

Comprehensive documentation for the automatic license renewal feature covering:

  • Clear comparison table between License Token and License Key
  • Well-documented refresh schedule thresholds
  • Air-gapped environment configuration
  • Privacy guarantees

The documentation aligns well with the implementation details in the codebase.


345-360: LGTM!

FAQ additions address the key questions users will have about the new auto-refresh feature, including the LICENSE vs LICENSE_KEY distinction and data privacy.

react_on_rails_pro/docs/configuration.md (1)

165-186: LGTM!

The license auto-refresh configuration section is well-documented with clear defaults and references to the detailed LICENSE_SETUP.md guide.

Minor note: Line 175 shows config.license_key = ENV["REACT_ON_RAILS_PRO_LICENSE_KEY"], but since the getter already checks ENV first (as per configuration.rb lines 203-207), this explicit assignment is optional. The example is still valid for documentation purposes to show the ENV variable name.

react_on_rails_pro/lib/react_on_rails_pro/license_validator.rb (2)

29-32: LGTM!

Good practice to seed the cache on first successful validation. This ensures the cache has expiry information for refresh scheduling on subsequent boots.


158-180: LGTM!

The license loading flow is well-structured:

  1. Attempt refresh if due (guarded by should_check_for_refresh? in LicenseRefreshChecker)
  2. Use cached token when auto-refresh is enabled
  3. Fall back to ENV variable
  4. Fall back to config file

The memoization at line 16 (return @license_data if defined?(@license_data)) prevents repeated API calls within the same process lifecycle.

react_on_rails_pro/spec/react_on_rails_pro/license_cache_spec.rb (4)

1-42: LGTM!

Well-structured test setup with:

  • FakeFS for filesystem isolation
  • Proper configuration reset in before/after hooks
  • ENV cleanup to prevent test pollution

43-123: LGTM!

Comprehensive .read tests covering all edge cases: missing file, invalid JSON, hash mismatch, nil key, valid cache, and ENV override.


125-187: LGTM!

Good coverage of .write behavior including:

  • Data merging with required fields
  • Directory creation when missing
  • Error handling that logs warnings without raising (graceful degradation)

189-283: LGTM!

Accessor method tests (token, fetched_at, expires_at) properly cover the happy path and edge cases including invalid time formats.

react_on_rails_pro/lib/react_on_rails_pro/license_fetcher.rb (2)

16-36: Solid implementation with graceful fallback.

The fetch method correctly:

  • Guards on auto_refresh_enabled? before making network calls
  • Uses HTTPX's retry plugin for transient failures
  • Handles both network errors (ErrorResponse) and non-200 responses
  • Returns nil on any failure, allowing fallback to cached/existing token

Regarding the fixed vs exponential retry strategy noted in past reviews: for this use case (license renewal during app boot, with graceful fallback), the simpler fixed-delay approach is acceptable. The critical path is protected by falling back to the cached token.


40-46: LGTM!

Clean delegation to configuration for license_key and api_url.

react_on_rails_pro/lib/react_on_rails_pro/configuration.rb (3)

65-69: LGTM!

Good defaults for the auto-refresh feature. The TODO comment for the license API URL is appropriate while the licensing app deployment is pending.


199-236: LGTM!

Well-designed configuration methods:

  • license_key and license_api_url correctly prioritize ENV variables over configured values
  • auto_refresh_enabled? properly gates on both the feature flag and having a key configured
  • Clear YARD documentation for each method

The || DEFAULT_LICENSE_API_URL fallback on line 221 ensures a URL is always returned even when both ENV and config are unset.


111-114: LGTM!

New initialization parameters with appropriate defaults. The pattern follows existing configuration style in this file.

Also applies to: 141-143

react_on_rails_pro/lib/react_on_rails_pro/license_refresh_checker.rb (5)

13-21: LGTM!

The refresh logic correctly guards against disabled auto-refresh and unnecessary checks, gracefully handles nil responses from the fetcher, and persists successful responses to the cache.


41-54: LGTM!

The refresh schedule logic correctly implements the cadence from the PR objectives: daily checks when ≤7 days until expiry, weekly when ≤30 days, and no checks when >30 days. The method gracefully handles missing expiry timestamps and expired licenses.


56-61: LGTM!

The timing logic correctly handles the case where no fetch has occurred yet (returning true to trigger a check) and accurately compares the time elapsed since the last fetch.


64-72: LGTM!

The token loading logic correctly prioritizes the environment variable, falls back to the file, and returns nil when neither is available. The .strip call ensures whitespace doesn't cause issues with tokens read from files.


1-75: No action required. RuboCop violations are automatically detected and fixed by lefthook pre-commit hooks when you commit changes to this file.

Likely an incorrect or invalid review comment.

@claude
Copy link

claude bot commented Dec 26, 2025

Comprehensive Review of PR #2254: Automatic License Renewal

Executive Summary

This PR adds automatic license renewal functionality for both the Ruby gem and Node Renderer. The implementation is well-designed and secure with excellent test coverage (110+ tests). However, there are 3 required fixes before merging.


🔐 Security Analysis - EXCELLENT ✅

JWT Security - Perfect Implementation

  • ✅ Correctly enforces verify_signature: true with security comments
  • ✅ Hardcodes algorithm: "RS256" to prevent algorithm substitution attacks
  • ✅ Uses RSA public key verification with proper key management
  • ✅ Manual expiration handling for grace period support

File Permissions - Secure

  • ✅ Cache files set to 0600 (owner read/write only)
  • ✅ Prevents other users from reading cached license tokens

API Authentication - Good

  • ✅ Bearer token over HTTPS
  • ✅ 5-second timeout prevents hanging
  • ✅ 2 retries with 1-second delay
  • ✅ TypeScript bails on 401/403 (Ruby should too - see recommendations)

Secrets Management - Excellent

  • ✅ ENV variables take precedence over config
  • ✅ Consistent behavior between Ruby and TypeScript
  • ✅ Prevents accidental commit of secrets

🐛 Required Fixes (MUST ADDRESS)

1. ENV Inconsistency (CRITICAL)

Issue: TypeScript supports REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE ENV variable, but Ruby does NOT.

Current Ruby (configuration.rb:219):

def auto_refresh_enabled?
  auto_refresh_license && license_key.present?
end

Current TypeScript (licenseFetcher.ts:51-59):

const autoRefreshSetting = process.env.REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE;
return autoRefreshSetting?.toLowerCase() \!== 'false';

Fix Required: Add ENV support to Ruby to match TypeScript:

def auto_refresh_enabled?
  env_setting = ENV.fetch("REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE", nil)
  enabled = if env_setting.present?
              env_setting.downcase \!= "false"
            else
              auto_refresh_license
            end
  
  enabled && license_key.present?
end

2. Missing Changelog Entry (REQUIRED)

Per CLAUDE.md guidelines, need to update /CHANGELOG_PRO.md:

#### [Unreleased]

##### Added
- Automatic license renewal for Ruby gem and Node Renderer. When configured with a `license_key`, licenses are automatically renewed before expiration. [PR 2254](https://github.com/shakacode/react_on_rails/pull/2254) by [ihabadham](https://github.com/ihabadham)

3. Inefficient Retry on Auth Failures (MEDIUM)

Issue: Ruby retries on 401/403 errors, wasting retries on auth failures. TypeScript correctly bails immediately.

Current Ruby (license_fetcher.rb:24-25):

return nil if response.is_a?(HTTPX::ErrorResponse)
return nil unless response.status == 200

Fix Required: Add explicit check to NOT retry on auth errors:

# Bail immediately on auth errors (don't waste retries)
return nil if [401, 403].include?(response.status)

return nil if response.is_a?(HTTPX::ErrorResponse)
return nil unless response.status == 200

📊 Test Coverage - EXCELLENT ✅

  • 110 total tests (64 new Ruby tests)
  • 54 TypeScript tests
  • ✅ Uses WebMock for HTTP stubbing
  • ✅ Uses FakeFS for file system isolation
  • ✅ Excellent use of fake timers for async-retry testing
  • ✅ Covers network errors, timeouts, cache corruption, ENV priority

Minor Gap: No integration test for full renewal flow (ENV → fetch → cache → validate). Optional but recommended for future PR.


⚡ Performance Analysis - EXCELLENT ✅

Refresh Schedule - Optimal

Time Until Expiration Check Frequency
> 30 days No checks
7-30 days Weekly
≤ 7 days Daily
  • ✅ Minimal API calls (at most daily)
  • ✅ No unnecessary checks when far from expiry
  • ✅ Aggressive checks when near expiry

Cache Persistence

  • ✅ Persists across app restarts in tmp/react_on_rails_pro_license.cache
  • ✅ Reduces API calls after restart
  • ✅ Located in tmp/ (gitignored by default)

Minor Note: First boot with near-expiry license blocks ~5 seconds on API call. Consider async refresh in future PR if this becomes an issue.


📚 Documentation - EXCELLENT ✅

  • ✅ LICENSE_SETUP.md: Clear comparison table, setup instructions, FAQ
  • ✅ configuration.md: All new options documented
  • ✅ Code comments: Excellent inline documentation explaining "why"
  • ✅ Privacy section: States no telemetry collected

TODO Comments: Remember to remove these once licensing app is deployed:

  • .lychee.toml:64
  • configuration.rb:67

🎯 Code Quality - EXCELLENT ✅

Ruby Implementation

  • ✅ Clean separation of concerns (Fetcher, Cache, RefreshChecker, Validator)
  • ✅ Single responsibility principle followed
  • ✅ Proper error handling with graceful fallback
  • ✅ Good use of Rails conventions (1.day, 7.days)

TypeScript Implementation

  • ✅ Excellent async/retry implementation with AbortController
  • ✅ Strong type safety with proper interfaces
  • ✅ Explicit bail on auth errors (better than Ruby)
  • ✅ Good separation of concerns matching Ruby

Consistency Between Implementations

  • ✅ Same constants (timeout, retries, delay)
  • ✅ Same cache file structure and hash validation
  • ✅ Same refresh timing logic
  • ⚠️ Except ENV support for auto_refresh_license (see Fix TODO for first version #1)

🔍 Minor Improvements (Optional - Future PRs)

  1. Atomic Cache Writes: Use temp file + rename pattern to prevent corruption on crash

    temp_path = cache_path.sub(/\.cache$/, '.cache.tmp')
    File.write(temp_path, JSON.pretty_generate(cache_data))
    File.chmod(0o600, temp_path)
    File.rename(temp_path, cache_path)  # Atomic on POSIX
  2. Full SHA256 Hash: Currently truncates to 16 chars - use full hash for better security practice

  3. Integration Test: End-to-end test of full renewal flow

  4. Async Startup Refresh: Prevent blocking on first boot near expiry


✅ Final Verdict

APPROVE with 3 required fixes

This is exceptionally well-implemented code with:

  • ✅ Excellent security practices (JWT, file permissions, API auth)
  • ✅ Comprehensive test coverage (110+ tests)
  • ✅ Clear documentation and comments
  • ✅ Graceful error handling and fallback
  • ✅ Optimal performance characteristics

Action Items:

  1. ✅ Fix ENV inconsistency for auto_refresh_license
  2. ✅ Update /CHANGELOG_PRO.md
  3. ✅ Add bail on 401/403 in Ruby LicenseFetcher

Once these 3 items are addressed, this PR is ready to merge. Great work! 🎉

async-retry's internal design mixes setTimeout with Promise chains,
causing "Cannot log after tests are done" warnings in Jest CI.

Replace with a simple custom retry implementation that:
- Uses a simple for-loop with await for cleaner cleanup
- Supports AbortRetryError for immediate abort (like auth errors)
- Has onFailedAttempt callback for logging

This fixes the package-js-tests CI failure on PR #2254.

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@claude
Copy link

claude bot commented Dec 26, 2025

Code Review: Automatic License Renewal

Great work on this feature! The implementation is well-structured and comprehensive.


✅ Strengths

1. Excellent Architecture & Code Quality

  • Clean separation of concerns: LicenseFetcher, LicenseCache, LicenseRefreshChecker are well-defined
  • Symmetric Ruby/TypeScript implementation follows the same patterns
  • Comprehensive test coverage: 110 Ruby tests (64 new) + 54 TypeScript tests
  • Good use of design patterns: cache invalidation with key hashing, retry logic, graceful degradation

2. Security Best Practices

  • Proper file permissions: Cache files set to 0o600 (owner read/write only)
  • Key hashing for validation: Uses SHA256 hash to validate cache belongs to current license key
  • No secrets in cache: Stores hash, not the actual license key
  • Timeout protection: 5-second request timeout prevents hanging
  • Retry limits: MAX_RETRIES=2 prevents infinite loops

3. Configuration & UX

  • Sensible defaults: Auto-refresh enabled by default with graceful fallback
  • Environment variable support: All settings configurable via ENV
  • Clear documentation in PR description

🔍 Potential Issues & Recommendations

1. Security: License Key Exposure Risk (Medium Priority)

Issue: The license key is sent as a Bearer token in HTTP headers. If license_api_url is accidentally configured as HTTP (not HTTPS), the key would be transmitted in plaintext.

Recommendation: Add validation in configuration.rb to warn if HTTP is used.

Files: react_on_rails_pro/lib/react_on_rails_pro/configuration.rb:217-227


2. Race Condition: Cache Write in Multi-Process Environments (Low-Medium)

Files: license_cache.rb:28-36, licenseCache.ts:105-136

Issue: In multi-process environments (Puma/Unicorn with multiple workers), simultaneous writes to the cache file could cause corruption.

Scenario:

  1. Process A reads cache, finds it expired
  2. Process B reads cache, finds it expired
  3. Both fetch new license simultaneously
  4. Both write to cache file → last write wins (or corruption if interleaved)

Recommendation: Add file locking using File.flock for atomic writes.


3. Performance: Cache Read on Every Request (Low)

File: license_validator.rb:159-166

Issue: load_license_string calls LicenseRefreshChecker.maybe_refresh_license on every validation, which reads from disk cache on every server-rendered request.

Recommendation: Consider memoizing the cache read or using a background job for periodic refresh checks.

Note: validated_license_data! already memoizes the result, but maybe_refresh_license runs before caching kicks in on first call.


4. Documentation: Missing Troubleshooting Guide (Low)

Missing info:

  • What happens if licensing API is down?
  • How to manually trigger refresh?
  • How to debug cache issues?
  • Air-gapped environment setup

Recommendation: Add troubleshooting section to docs.


🧪 Test Coverage Analysis

Excellent coverage overall! The tests cover:

  • Auto-refresh enabled/disabled states
  • ENV variable precedence
  • Network errors and retries
  • Cache validation and invalidation
  • Timing thresholds (7 days, 30 days)
  • File permissions

📋 Before Merging Checklist

Per CLAUDE.md requirements:

  • Run RuboCop: bundle exec rubocop (MANDATORY)
  • Run tests: rake run_rspec:gem && rake run_rspec:dummy
  • Update changelog: Add entry to /CHANGELOG_PRO.md (user-facing feature)
  • Check file endings: All files must end with newline

🎯 Summary

Verdict: ✅ Approve with minor recommendations

This is a well-crafted feature with solid architecture, comprehensive tests, and good security practices. The issues identified are mostly nice-to-haves or defensive improvements rather than blockers.

Priority fixes (if any):

  1. Medium: Add HTTPS validation for license_api_url (1-line change)
  2. Low: Consider file locking for cache writes (if multi-process deploy is common)
  3. Low: Document troubleshooting steps

Great work overall! 🎉

@ihabadham ihabadham marked this pull request as draft December 28, 2025 16:01
@claude
Copy link

claude bot commented Dec 30, 2025

PR #2254 Code Review: Automatic License Renewal

Overall Assessment: This is a well-designed, production-ready feature with excellent test coverage and consistent implementation across Ruby and TypeScript.

Test Coverage: Excellent - 64 new Ruby tests, 54 new TypeScript tests.

Strengths

  1. Architecture: Clean module separation (Fetcher, Cache, RefreshChecker)
  2. Security: File permissions 0600, SHA256 hash validation, ENV priority, no secrets in logs
  3. Test Quality: Comprehensive coverage with FakeFS and WebMock

High Priority Issues

1. Ruby LicenseFetcher: Add specific HTTPX exception handling for timeout/network errors
2. TypeScript withRetry: Add validation to prevent negative retry values

Medium Priority

  1. Add debug logging for timestamp parsing failures
  2. Document why 16-char hash is used (collision probability)
  3. Add mutex to prevent parallel refresh on server restart
  4. Add REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE ENV to Ruby for consistency

Low Priority

  1. Add User-Agent header to Ruby fetcher
  2. Consolidate duplicate load_token_from_env_or_file logic
  3. Include API URL in error messages for debugging
  4. Add test for expired token in cache

Security

  • PASS: File permissions, secrets handling, ENV precedence, cache validation
  • WARNING: Consider HTTPS scheme validation for license_api_url in production

Performance

  • Cache I/O on every validation: consider in-memory TTL caching
  • Daily checks on server boot: acceptable for horizontally scaled apps
  • 5-second timeout: good choice

Documentation

Consider adding: migration guide, troubleshooting section, refresh window explanation

Conclusion

Excellent work! Production-ready with only minor improvements needed.

Recommendation: Approve with optional fixes for High Priority items.

@claude
Copy link

claude bot commented Dec 30, 2025

Code Review: Automatic License Renewal (PR #2254)

Thank you for this comprehensive implementation! The overall architecture is solid with good separation of concerns.

Critical Issues

1. Async/Await Mismatch (BLOCKING) - licenseValidator.ts loadAndDecodeLicense() is synchronous but calls await maybeRefreshLicense(). This will cause a runtime error. Fix: Make the entire chain async.

2. License Key Hash Too Short (SECURITY) - licenseCache.ts:33 and license_cache.rb:71 truncate SHA256 to only 16 characters (64 bits), reducing collision resistance from 2^128 to 2^32. Fix: Use at least 32 characters (128 bits).

3. Missing ENV Variable Support in Ruby - TypeScript reads REACT_ON_RAILS_PRO_AUTO_REFRESH_LICENSE from ENV, but Ruby configuration doesn't, creating inconsistent behavior between platforms.

High Priority Issues

4. Race Condition on Cache Write - TOCTOU race when multiple processes start simultaneously. Both might write to cache file concurrently. Fix: Use atomic writes or file locking.

5. Blocking I/O on Boot Path - License validation can trigger 5-second HTTP timeout during startup. Consider fire-and-forget pattern after initial validation.

6. Invalid Cache Cleanup - When license_key_hash doesn't match (user changed key), readCache() returns null but old cache file remains. Should delete cache file on hash mismatch.

Medium Priority

7. Missing Test Coverage - Need tests for: concurrent refresh attempts, clock skew scenarios, partial file writes, permission errors

8. Input Validation - Missing validation that expires_at is valid ISO8601 and license_api_url is valid URL

Done Well

File permissions (0600), Bearer token auth, smart refresh schedule (7/30 day thresholds), AbortRetryError pattern, test isolation with FakeFS/WebMock, caching strategy

Summary

Must Fix Before Merge: async/await bug (runtime error), hash length to 32+ chars (security), ENV variable support in Ruby (consistency)

Strongly Recommended: File locking/atomic writes, cache cleanup on key change, concurrency tests

Overall well-structured implementation with solid test coverage for happy paths. Once critical issues are addressed, this will be production-ready!

Review performed following project guidelines in CLAUDE.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants