Class: ReactOnRails::Doctor

Inherits:
Object
  • Object
show all
Includes:
ConfigPathResolver, ShakapackerConfigHelpers
Defined in:
lib/react_on_rails/doctor.rb

Overview

rubocop:disable Metrics/ClassLength, Metrics/AbcSize

Constant Summary collapse

MESSAGE_COLORS =
{
  error: :red,
  warning: :yellow,
  success: :green,
  info: :blue
}.freeze
RSPEC_HELPER_FILES =
["spec/rails_helper.rb", "spec/spec_helper.rb"].freeze
MINITEST_HELPER_FILE =
"test/test_helper.rb"
DEFAULT_BUILD_TEST_COMMAND =
'config.build_test_command = "RAILS_ENV=test bin/shakapacker"'
SERVER_BUNDLE_SOURCE_EXTENSIONS =
%w[.js .jsx .ts .tsx .mjs .cjs].freeze
CUSTOM_LAUNCHER_INDICATOR_FILES =
%w[dev].freeze
RAILS_SERVER_COMMAND_REGEX =
%r{\b(?:(?:bin/)?rails\s+(?:server|s)|puma|unicorn|rackup|passenger\s+start)\b}
DEPRECATED_RENDERER_CACHE_TASK =

Deprecated-renderer-cache scan (used by check_deprecated_renderer_cache_task): look for references to the old pre_stage_bundle_for_node_renderer task in common deploy-script locations so users on older Procfile/Dockerfile entries get a migration nudge before the task is removed.

"pre_stage_bundle_for_node_renderer"
RENDERER_CACHE_DEPLOY_SCRIPT_PATHS =

Fixed allowlist of single-file deploy-script paths. Each entry is a literal path that may host a deploy hook referencing the deprecated task. Directory globs (e.g., per-stage Capistrano files or per-workflow GitHub Actions YAML) live in RENDERER_CACHE_DEPLOY_SCRIPT_GLOBS so they stay bounded.

[
  "Procfile",
  "Procfile.dev",
  "Procfile.dev-static-assets",
  "Procfile.production",
  "Dockerfile",
  "Dockerfile.production",
  "Dockerfile.staging",
  "Dockerfile.review",
  "docker-compose.yml",
  "docker-compose.yaml",
  "compose.yml",
  "compose.yaml",
  "bin/deploy",
  "bin/release",
  "bin/docker-entrypoint",
  "config/deploy.rb",
  "config/deploy/production.rb",
  "config/deploy/staging.rb",
  ".kamal/deploy.yml",
  "scripts/deploy.sh",
  ".circleci/config.yml",
  ".gitlab-ci.yml",
  "bitbucket-pipelines.yml",
  "Jenkinsfile"
].freeze
RENDERER_CACHE_DEPLOY_SCRIPT_GLOBS =

Bounded glob allowlist for deploy manifests that live in a known directory but use per-environment or per-workflow filenames. Each pattern matches only one directory level (no ‘**`) so the scan never recurses into the project tree, and the expansion is capped by RENDERER_CACHE_DEPLOY_SCRIPT_GLOB_MAX_MATCHES.

[
  ".github/workflows/*.yml",
  ".github/workflows/*.yaml",
  "config/deploy/*.rb"
].freeze
RENDERER_CACHE_DEPLOY_SCRIPT_MAX_BYTES =

Per-file safety gate to bound IO during the scan, not a meaningful size limit.

1_048_576
RENDERER_CACHE_DEPLOY_SCRIPT_GLOB_MAX_MATCHES =

Defense-in-depth cap on how many files a single glob may contribute. Realistic repos have a handful of workflow / deploy-stage files; far more than this is a sign of an unexpectedly broad pattern, not legitimate config.

100
OUTPUT_FORMATS =

Supported output formats. :text is the human-readable default; :json emits a machine-readable report (see JSON_SCHEMA_VERSION below).

%i[text json].freeze
JSON_SCHEMA_VERSION =

Version of the machine-readable doctor report schema (FORMAT=json). Bump ONLY on breaking changes to the shape below. Schema (v1):

{
  "schema_version": 1,
  "ror_version": "<ReactOnRails::VERSION>",
  "status": "pass" | "warn" | "fail",          // worst check status
  "checks": [
    {
      "id": "<stable snake_case id from CHECK_SECTIONS>",
      "title": "<human section title>",
      "status": "pass" | "warn" | "fail",
      "message": "<most severe message content, or null>",
      "details": [ { "level": "success|warning|error|info", "content": "..." } ]
    }
  ],
  "summary": { "pass": <count>, "warn": <count>, "fail": <count> }
}

No timestamp is included so output is deterministic for a given app state. Exit code semantics match text mode: 1 if any check fails, else 0.

1
CHECK_SECTIONS =

Doctor check sections. The :id values are part of the stable JSON schema contract (consumed by agents/tooling) — never rename or reuse them; add new sections with new ids instead.

[
  { id: "environment_prerequisites", title: "Environment Prerequisites", method: :check_environment },
  { id: "react_on_rails_versions", title: "React on Rails Versions", method: :check_react_on_rails_versions },
  { id: "react_on_rails_packages", title: "React on Rails Packages", method: :check_packages },
  { id: "javascript_package_dependencies", title: "JavaScript Package Dependencies",
    method: :check_dependencies },
  { id: "key_configuration_files", title: "Key Configuration Files", method: :check_key_files },
  { id: "configuration_analysis", title: "Configuration Analysis", method: :check_configuration_details },
  { id: "bin_dev_launcher_setup", title: "bin/dev Launcher Setup", method: :check_bin_dev_launcher },
  { id: "rails_integration", title: "Rails Integration", method: :check_rails },
  { id: "bundler_configuration", title: "Bundler Configuration", method: :check_bundler_configuration },
  { id: "testing_setup", title: "Testing Setup", method: :check_testing_setup },
  { id: "development_environment", title: "Development Environment", method: :check_development },
  { id: "react_on_rails_pro_setup", title: "React on Rails Pro Setup", method: :check_pro_setup },
  { id: "react_server_components", title: "React Server Components", method: :check_rsc_setup }
].freeze

Constants included from ShakapackerConfigHelpers

ShakapackerConfigHelpers::DEFAULT_SHAKAPACKER_CONFIG_PATH, ShakapackerConfigHelpers::SUPPORTED_ASSETS_BUNDLERS

Constants included from ConfigPathResolver

ConfigPathResolver::ALL_DEFAULT_CONFIG_CANDIDATES, ConfigPathResolver::RSPACK_DEFAULT_CONFIG_CANDIDATES, ConfigPathResolver::WEBPACK_DEFAULT_CONFIG_CANDIDATES

Instance Method Summary collapse

Constructor Details

#initialize(verbose: false, fix: false, format: :text) ⇒ Doctor

Returns a new instance of Doctor.



168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/react_on_rails/doctor.rb', line 168

def initialize(verbose: false, fix: false, format: :text)
  @verbose = verbose
  @fix = fix
  @format = format.respond_to?(:to_sym) ? format.to_sym : format
  unless OUTPUT_FORMATS.include?(@format)
    raise ArgumentError, "Invalid doctor format #{format.inspect}; expected one of #{OUTPUT_FORMATS.join(', ')}"
  end

  @checker = SystemChecker.new
  @test_output_path_strategy = :unknown
  @rails_environment_loaded = false
end

Instance Method Details

#run_diagnosisObject



181
182
183
184
185
186
187
188
189
190
# File 'lib/react_on_rails/doctor.rb', line 181

def run_diagnosis
  return run_json_diagnosis if format == :json

  print_header
  run_all_checks
  print_summary
  print_recommendations if should_show_recommendations?

  exit_with_status
end