Class: ReactOnRails::Doctor
- Inherits:
-
Object
- Object
- ReactOnRails::Doctor
- 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- NODE_RENDERER_CONFIG_MAX_BYTES =
1_048_576- NODE_RENDERER_SYNTAX_CHECK_TIMEOUT_SECONDS =
5- NODE_RENDERER_SYNTAX_CHECK_TERMINATION_GRACE_SECONDS =
0.5- NODE_RENDERER_ROLLOUT_GENERATIONS =
2- NODE_RENDERER_CURRENT_GENERATION_MANIFEST_FILENAME =
/\Arorp-generation-v1-[0-9a-f]{64}\.json\z/- NODE_RENDERER_LAUNCHER_PATHS =
%w[ Procfile Procfile.dev Procfile.dev-static-assets Procfile.dev-prod-assets Procfile.production ].freeze
- NODE_RENDERER_SCRIPT_REFERENCE_PATTERN =
%r{\bnode\s+(?:\./)?((?:renderer|client)/node-renderer\.js)(?=\s|\z)}- NODE_RENDERER_DIRECT_LAUNCHER_PATTERN =
%r{ \A[ \t]*[A-Za-z0-9_-]+:[ \t]* (?:MAX_VM_POOL_SIZE=(?<assignment>[^\s]+)[ \t]+)? node[ \t]+(?:\./)?(?<path>(?:renderer|client)/node-renderer\.js) [ \t]*(?:\r?\n)?\z }x- NODE_RENDERER_JS_STRING_PATTERN =
/ "(?:\\.|[^"\\])*" | '(?:\\.|[^'\\])*' | `(?:\\.|[^`\\])*` /mx- NODE_RENDERER_NESTED_OBJECT_PATTERN =
/ \{ (?:#{NODE_RENDERER_JS_STRING_PATTERN}|[^{}"'`])* \} /mx- NODE_RENDERER_CONFIG_OBJECT_PATTERN =
/ \{ (?:#{NODE_RENDERER_JS_STRING_PATTERN}|#{NODE_RENDERER_NESTED_OBJECT_PATTERN}|[^{}"'`])* \} /mx- NODE_RENDERER_BARE_CALL_PATTERN =
/(?<![.\p{ID_Continue}$#])reactOnRailsProNodeRenderer\s*\(\s*/- MAX_NODE_RENDERER_BARE_CALL_CANDIDATES =
A canonical launcher needs one bare renderer call. This generous allowance bounds prefix-based reachability work while tolerating benign decoys.
32- NODE_RENDERER_PACKAGE_BINDING_PATTERN =
The canonical export must be a shorthand specifier and the binding must end explicitly. Aliases and trusted-looking expression prefixes are not evidence that the real renderer runs.
/ (?:\A|[;\n])\s* (?: const\s*\{\s* (?: [A-Za-z_$][A-Za-z0-9_$]*(?:\s*:\s*[A-Za-z_$][A-Za-z0-9_$]*)?\s*,\s* )* reactOnRailsProNodeRenderer (?: \s*,\s*[A-Za-z_$][A-Za-z0-9_$]*(?:\s*:\s*[A-Za-z_$][A-Za-z0-9_$]*)? )* \s*,?\s*\}\s*=\s* require\s*\(\s*["']react-on-rails-pro-node-renderer["']\s*\) | import\s*\{\s* (?: [A-Za-z_$][A-Za-z0-9_$]*(?:\s+as\s+[A-Za-z_$][A-Za-z0-9_$]*)?\s*,\s* )* reactOnRailsProNodeRenderer (?: \s*,\s*[A-Za-z_$][A-Za-z0-9_$]*(?:\s+as\s+[A-Za-z_$][A-Za-z0-9_$]*)? )* \s*,?\s*\}\s+from\s+ ["']react-on-rails-pro-node-renderer["'] )\s*(?:;|\z) /mx- NODE_RENDERER_REQUIRE_IDENTIFIER_PATTERN =
/(?<![.\p{ID_Continue}$#])require\b/- NODE_RENDERER_REQUIRE_FUNCTION_DECLARATION_PATTERN =
/\bfunction\s*\*?\s+require\b/- NODE_RENDERER_ARGUMENTS_IDENTIFIER_PATTERN =
/(?<![.\p{ID_Continue}$#])arguments\b/- NODE_RENDERER_DIRECT_EVAL_PATTERN =
/(?<![.\p{ID_Continue}$#])eval\s*\(/- NODE_RENDERER_LOCAL_BINDING_PATTERN =
%r{ \b(?:const|let|var)\s+ (?:reactOnRailsProNodeRenderer\b|\{[^{}]*\breactOnRailsProNodeRenderer\b[^{}]*\}) | \b(?:function|class)\s+reactOnRailsProNodeRenderer\b | \bimport\s*\{[^{}]*\breactOnRailsProNodeRenderer\b[^{}]*\}\s+from\b | (?<![.\p{ID_Continue}$#])reactOnRailsProNodeRenderer\b\s* (?:\+\+|--|\*\*=|&&=|\|\|=|\?\?=|[+\-*\/%&|^]=|=(?!=|>)) | (?:\+\+|--)\s*(?<![.\p{ID_Continue}$#])reactOnRailsProNodeRenderer\b }mx- NODE_RENDERER_IDENTIFIER_PATTERN =
/(?<![.\p{ID_Continue}$#])reactOnRailsProNodeRenderer\b/- NODE_RENDERER_GLOBAL_OBJECT_PATTERN =
/(?<![.\p{ID_Continue}$#])(?:globalThis|global)\b/- NODE_RENDERER_UNPROVEN_CALL_CONTROL_PATTERN =
/ (?:&&|\|\||=>|\?) | \b(?:if|else|for|while|do|switch|case|catch|finally|function|return|throw)\b /x- NODE_RENDERER_OPENING_DELIMITERS =
["(", "[", "{"].freeze
- NODE_RENDERER_QUOTE_CHARACTERS =
['"', "'"].freeze
- NODE_RENDERER_MATCHING_OPENING_DELIMITERS =
{ ")" => "(", "]" => "[", "}" => "{" }.freeze
- NODE_RENDERER_UNPROVEN_INITIALIZER_OPENINGS =
["(", "["].freeze
- 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 =
DoctorSchema is the canonical machine-readable report contract; bump its version only for breaking changes. 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.
DoctorSchema::VERSION
- 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: "node_renderer_rollout_capacity", title: "Node Renderer Rollout Capacity", method: :check_node_renderer_rollout_capacity }, { id: "react_server_components", title: "React Server Components", method: :check_rsc_setup } ].freeze
- CHECK_SECTIONS_BY_ID =
CHECK_SECTIONS.to_h { |section| [section[:id], section] }.freeze
Constants included from RscRspackSupport
RscRspackSupport::DECLARED_PACKAGE_DEPENDENCY_FIELDS, RscRspackSupport::GENERIC_DECLARED_PACKAGE_DEPENDENCY_FIELDS, RscRspackSupport::MINIMUM_RSC_RSPACK_MAJOR, RscRspackSupport::NODE_PACKAGE_RESOLUTION_SCRIPT, RscRspackSupport::PACKAGE_NAME_PATTERN, RscRspackSupport::PATH_PROTOCOL_PACKAGE_SPEC_PATTERN, RscRspackSupport::RSC_RSPACK_PACKAGE, RscRspackSupport::RSC_RSPACK_PACKAGE_DEPENDENCY_FIELDS, RscRspackSupport::RSC_RSPACK_UPGRADE_PACKAGE_DEPENDENCY_FIELDS, RscRspackSupport::RSC_RSPACK_V2_PACKAGES
Constants included from ShakapackerConfigHelpers
ShakapackerConfigHelpers::DEFAULT_SHAKAPACKER_CONFIG_PATH, ShakapackerConfigHelpers::SHAKAPACKER_ASSETS_BUNDLER_ENV, 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
-
#initialize(verbose: false, fix: false, format: :text, only: nil) ⇒ Doctor
constructor
A new instance of Doctor.
- #run_diagnosis ⇒ Object
Constructor Details
#initialize(verbose: false, fix: false, format: :text, only: nil) ⇒ Doctor
Returns a new instance of Doctor.
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 |
# File 'lib/react_on_rails/doctor.rb', line 258 def initialize(verbose: false, fix: false, format: :text, only: nil) @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 @only_explicitly_set = explicit_check_selection?(only) @check_sections = normalize_check_sections(only) @checker = SystemChecker.new @test_output_path_strategy = :unknown @rails_environment_loaded = false @rsc_artifacts_rebuild_guidance_added = false @rsc_registration_entry_warning_added = false end |
Instance Method Details
#run_diagnosis ⇒ Object
275 276 277 278 279 280 281 282 283 284 |
# File 'lib/react_on_rails/doctor.rb', line 275 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 |