Module: ReactOnRails::Generators::ProSetup

Included in:
InstallGenerator, ProGenerator
Defined in:
lib/generators/react_on_rails/pro_setup.rb

Overview

Provides Pro setup functionality for React on Rails generators.

This module extracts Pro-specific setup methods that can be shared between:

  • InstallGenerator (when --pro or --rsc flags are used)
  • ProGenerator (standalone generator for upgrading existing apps)

Required Dependencies

Including classes must provide (typically via Rails::Generators::Base):

  • destination_root: Path to the target Rails application
  • template, copy_file, append_to_file: Thor file manipulation methods
  • options: Generator options hash

Including classes must also include GeneratorHelper which provides:

  • use_pro?, use_rsc?: Feature flag helpers
  • pro_gem_installed?: Pro gem detection (real lockfile / loaded-specs state)
  • pro_gem_install_deferred?, defer_pro_gem_install!: deferred-install tracking
  • invalidate_pro_gem_installed_cache!: invalidate memoized pro_gem_installed?

rubocop:disable Metrics/ModuleLength

Constant Summary collapse

PRO_GEM_NAME =
"react_on_rails_pro"
AUTO_INSTALL_TIMEOUT =

Version is appended dynamically via pro_gem_auto_install_command to ensure the installed version matches the current react_on_rails gem version.

120
TERMINATION_GRACE_PERIOD =
5
GET_LOADER_PATH_JS =

Loader helpers emitted by templates/base/base/config/webpack/serverWebpackConfig.js.tt.

Keep both blocks byte-identical to that template: a fresh --pro install renders the template while a standalone Pro upgrade patches an existing base config, and the two must produce the same source text so drift is detectable (issue #4786).

<<~JS
  // Normalizes an entry of a webpack/rspack `rule.use` array to its loader path.
  // Entries may be a bare string, a `{ loader, options }` object, or null.
  function getLoaderPath(item) {
    if (typeof item === 'string') return item;
    if (item && typeof item.loader === 'string') return item.loader;
    return '';
  }
JS
EXTRACT_LOADER_JS =
<<~JS
  function extractLoader(rule, loaderName) {
    if (!Array.isArray(rule.use)) return null;
    return rule.use.find((item) => getLoaderPath(item).includes(loaderName));
  }
JS
BUNDLER_REQUIRE_PATTERN =
%r{(const bundler = config\.assets_bundler.*\n.*require\('@rspack/core'\).*\n.*: require\('webpack'\);)}
GET_LOADER_PATH_DECLARATION =

Matches any declaration of the getLoaderPath symbol, however it is written. The emitted extractLoader calls getLoaderPath, so we must never add a second declaration: function next to an existing const is a SyntaxError, not a silent shadow, and the generated config would fail to parse in Node.

/(?:function\s+getLoaderPath\s*\(|(?:const|let|var)\s+getLoaderPath\s*=)/

Instance Method Summary collapse

Instance Method Details

#missing_pro_gem?(force: false) ⇒ Boolean

Check if the Pro gem is missing. When the base react_on_rails gem is in the Gemfile, installation is deferred to the later Gemfile swap (which preserves the user's version pin); otherwise auto-install via bundle add is attempted.

Parameters:

  • force (Boolean) (defaults to: false)

    When true, always checks (default: only if use_pro?).

Returns:

  • (Boolean)

    true only if the Pro gem is missing and could not be installed; false if it is present, was auto-installed, or the install is deferred to the Gemfile swap.



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# File 'lib/generators/react_on_rails/pro_setup.rb', line 142

def missing_pro_gem?(force: false)
  return false unless force || use_pro?
  return false if pro_gem_installed? || pro_gem_install_deferred?
  return false if defer_pro_gem_install_to_gemfile_swap
  return false if attempt_pro_gem_auto_install

  optional_prerelease_line = prerelease_note.empty? ? "" : "\n#{prerelease_note}"

  GeneratorMessages.add_error(<<~MSG.strip)
    🚫 Failed to auto-install #{PRO_GEM_NAME} gem.

    #{pro_gem_requirement_context_line}#{optional_prerelease_line}

    Please add manually to your Gemfile:
      gem '#{PRO_GEM_NAME}', '#{pro_gem_version_requirement}'

    Then run: bundle install

    No license needed for evaluation or non-production use.
    Free or low-cost production licenses available for startups and small companies.
    See the upgrade guide: https://reactonrails.com/docs/pro/upgrading-to-pro/
  MSG
  true
end

#node_renderer_will_be_created?Boolean

Returns:

  • (Boolean)


129
130
131
132
# File 'lib/generators/react_on_rails/pro_setup.rb', line 129

def node_renderer_will_be_created?
  !File.exist?(File.join(destination_root, "renderer/node-renderer.js")) &&
    !File.exist?(File.join(destination_root, "client/node-renderer.js"))
end

#pro_initializer_will_be_created?Boolean

Returns:

  • (Boolean)


125
126
127
# File 'lib/generators/react_on_rails/pro_setup.rb', line 125

def pro_initializer_will_be_created?
  !File.exist?(File.join(destination_root, "config/initializers/react_on_rails_pro.rb"))
end

#say_renderer_password_setup_summary(initializer_created) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
# File 'lib/generators/react_on_rails/pro_setup.rb', line 105

def say_renderer_password_setup_summary(initializer_created)
  if @generated_renderer_password
    say ""
    say set_color("🔐 A random renderer password was written into your config files.", :yellow, :bold)
    say "   For production, set RENDERER_PASSWORD as an env var instead and"
    say "   remove the literal value from version control."
    say "   See: https://www.shakacode.com/react-on-rails/docs/pro/node-renderer/"
    say ""
  elsif initializer_created
    # Initializer was newly created but the Node renderer file already exists;
    # the new initializer falls back to ENV["RENDERER_PASSWORD"] only so it doesn't
    # disagree with whatever literal the existing renderer file contains.
    say ""
    say set_color("⚠️  Existing Node renderer detected — Rails initializer uses " \
                  "ENV[\"RENDERER_PASSWORD\"] only.", :yellow, :bold)
    say "   Set RENDERER_PASSWORD in your environment to match the password in your existing renderer."
    say ""
  end
end

#setup_proObject

Note:

NPM dependencies are handled separately by JsDependencyManager

Main entry point for Pro setup. Orchestrates creation of all Pro-related files and configuration.

Creates:

  • config/initializers/react_on_rails_pro.rb
  • renderer/node-renderer.js
  • Procfile.dev entry for node-renderer


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/generators/react_on_rails/pro_setup.rb', line 77

def setup_pro
  say "\n#{set_color('=' * 80, :cyan)}"
  say set_color("🚀 REACT ON RAILS PRO SETUP", :cyan, :bold)
  say set_color("=" * 80, :cyan)

  # The Rails initializer and Node renderer bootstrap must share the same
  # password literal. Only mint a fresh random password when BOTH files will
  # be created — otherwise nil so each template falls back to the env-only
  # branch, avoiding a literal mismatch with any existing file.
  # Always reassign so a stale value from a prior invocation on the same
  # instance can't leak into a later partial-install run.
  @generated_renderer_password = nil
  if pro_initializer_will_be_created? && node_renderer_will_be_created?
    @generated_renderer_password = SecureRandom.hex(32)
  end

  initializer_created = create_pro_initializer
  legacy_renderer_detected = create_node_renderer
  add_pro_to_procfiles unless legacy_renderer_detected
  update_webpack_config_for_pro

  say_renderer_password_setup_summary(initializer_created)

  say set_color("=" * 80, :cyan)
  say "✅ React on Rails Pro setup complete!", :green
  say set_color("=" * 80, :cyan)
end