Module: GeneratorHelper

Overview

rubocop:disable Metrics/ModuleLength

Instance Method Summary collapse

Instance Method Details

#add_documentation_reference(message, source) ⇒ Object



92
93
94
# File 'lib/generators/react_on_rails/generator_helper.rb', line 92

def add_documentation_reference(message, source)
  "#{message} \n#{source}"
end

#add_npm_dependencies(packages, dev: false) ⇒ Object

Safe wrapper for package_json operations



24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
# File 'lib/generators/react_on_rails/generator_helper.rb', line 24

def add_npm_dependencies(packages, dev: false)
  pj = package_json
  return false unless pj

  begin
    if dev
      pj.manager.add(packages, type: :dev, exact: true)
    else
      pj.manager.add(packages, exact: true)
    end
    true
  rescue StandardError => e
    say_status :warning, "Could not add packages via package_json gem: #{e.message}", :yellow
    say_status :warning, "Will fall back to direct npm commands.", :yellow
    false
  end
end

#component_extension(options) ⇒ Object



105
106
107
# File 'lib/generators/react_on_rails/generator_helper.rb', line 105

def component_extension(options)
  options.typescript? ? "tsx" : "jsx"
end

#copy_file_and_missing_parent_directories(source_file, destination_file = nil) ⇒ Object



84
85
86
87
88
89
90
# File 'lib/generators/react_on_rails/generator_helper.rb', line 84

def copy_file_and_missing_parent_directories(source_file, destination_file = nil)
  destination_file ||= source_file
  destination_path = Pathname.new(destination_file)
  parent_directories = destination_path.dirname
  empty_directory(parent_directories) unless dest_dir_exists?(parent_directories)
  copy_file source_file, destination_file
end

#dest_dir_exists?(dir) ⇒ Boolean

Returns:

  • (Boolean)


48
49
50
51
# File 'lib/generators/react_on_rails/generator_helper.rb', line 48

def dest_dir_exists?(dir)
  dest_dir = File.join(destination_root, dir)
  Dir.exist?(dest_dir) ? dest_dir : nil
end

#dest_file_exists?(file) ⇒ Boolean

Takes a relative path from the destination root, such as ‘.gitignore` or `app/assets/javascripts/application.js`

Returns:

  • (Boolean)


43
44
45
46
# File 'lib/generators/react_on_rails/generator_helper.rb', line 43

def dest_file_exists?(file)
  dest_file = File.join(destination_root, file)
  File.exist?(dest_file) ? dest_file : nil
end

#destination_config_path(path) ⇒ String

Remap a config path from config/webpack/ to config/rspack/ when using rspack. Source templates always live under config/webpack/ (template names are stable); this method handles the destination remapping.

Parameters:

  • path (String)

    relative path, e.g. “config/webpack/serverWebpackConfig.js”

Returns:

  • (String)

    remapped path when rspack, unchanged otherwise



179
180
181
182
183
# File 'lib/generators/react_on_rails/generator_helper.rb', line 179

def destination_config_path(path)
  return path unless using_rspack?

  path.sub(%r{\Aconfig/webpack/}, "config/rspack/")
end

#detect_react_versionString?

Detect the installed React version from package.json Uses VERSION_PARTS_REGEX pattern from VersionChecker for consistency

Returns:

  • (String, nil)

    React version string (e.g., “19.0.3”) or nil if not found/parseable



189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/generators/react_on_rails/generator_helper.rb', line 189

def detect_react_version
  pj = package_json
  return nil unless pj

  dependencies = pj.fetch("dependencies", {})
  react_version = dependencies["react"]
  return nil unless react_version

  # Skip non-version strings (workspace:*, file:, link:, http://, etc.)
  return nil if react_version.include?("/") || react_version.start_with?("workspace:")

  # Extract version using the same regex pattern as VersionChecker
  # Handles: "19.0.3", "^19.0.3", "~19.0.3", "19.0.3-beta.1", etc.
  match = react_version.match(/(\d+)\.(\d+)\.(\d+)(?:[-.]([0-9A-Za-z.-]+))?/)
  return nil unless match

  # Return the matched version (without pre-release suffix for comparison)
  "#{match[1]}.#{match[2]}.#{match[3]}"
rescue StandardError
  nil
end

#empty_directory_with_keep_file(destination, config = {}) ⇒ Object



61
62
63
64
# File 'lib/generators/react_on_rails/generator_helper.rb', line 61

def empty_directory_with_keep_file(destination, config = {})
  empty_directory(destination, config)
  keep_file(destination)
end

#gem_in_lockfile?(gem_name) ⇒ Boolean

Check if a gem is present in Gemfile.lock Always checks the target app’s Gemfile.lock, not inherited BUNDLE_GEMFILE See: github.com/shakacode/react_on_rails/issues/2287

Parameters:

  • gem_name (String)

    Name of the gem to check

Returns:

  • (Boolean)

    true if the gem is in Gemfile.lock



115
116
117
118
119
120
# File 'lib/generators/react_on_rails/generator_helper.rb', line 115

def gem_in_lockfile?(gem_name)
  File.file?("Gemfile.lock") &&
    File.foreach("Gemfile.lock").any? { |line| line.match?(/^\s{4}#{Regexp.escape(gem_name)}\s\(/) }
rescue StandardError
  false
end

#keep_file(destination) ⇒ Object



66
67
68
# File 'lib/generators/react_on_rails/generator_helper.rb', line 66

def keep_file(destination)
  create_file("#{destination}/.keep") unless options[:skip_keeps]
end

#mark_pro_gem_installed!Object



135
136
137
# File 'lib/generators/react_on_rails/generator_helper.rb', line 135

def mark_pro_gem_installed!
  @pro_gem_installed = true
end

#package_jsonObject



7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/generators/react_on_rails/generator_helper.rb', line 7

def package_json
  # Lazy load package_json gem only when actually needed for dependency management

  require "package_json" unless defined?(PackageJson)
  @package_json ||= PackageJson.read
rescue LoadError
  say_status :warning, "package_json gem not available. This is expected before Shakapacker installation.", :yellow
  say_status :warning, "Dependencies will be installed using the default package manager after Shakapacker setup.",
             :yellow
  nil
rescue StandardError => e
  say_status :warning, "Could not read package.json: #{e.message}", :yellow
  say_status :warning, "This is normal before Shakapacker creates the package.json file.", :yellow
  nil
end


96
97
98
99
100
101
102
103
# File 'lib/generators/react_on_rails/generator_helper.rb', line 96

def print_generator_messages
  # GeneratorMessages stores pre-colored strings, so we strip ANSI manually for --no-color output.
  no_color = !shell.is_a?(Thor::Shell::Color)
  GeneratorMessages.messages.each do |message|
    say(no_color ? message.to_s.gsub(/\e\[[0-9;]*m/, "") : message)
    say "" # Blank line after each message for readability
  end
end

#pro_gem_installed?Boolean

Check if React on Rails Pro gem is installed

Detection priority:

  1. Gem.loaded_specs - gem is loaded in current Ruby process (most reliable)

  2. Gemfile.lock - gem is resolved and installed

Returns:

  • (Boolean)

    true if react_on_rails_pro gem is installed



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

def pro_gem_installed?
  return @pro_gem_installed if defined?(@pro_gem_installed)

  @pro_gem_installed = Gem.loaded_specs.key?("react_on_rails_pro") || gem_in_lockfile?("react_on_rails_pro")
end

#resolve_server_client_or_both_pathString?

Resolve the path to ServerClientOrBoth.js, handling the legacy name. Old installs may still use generateWebpackConfigs.js; this renames it and updates references in environment configs so downstream transforms can rely on the canonical name.

Returns:

  • (String, nil)

    relative config path, or nil if neither file exists



267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
# File 'lib/generators/react_on_rails/generator_helper.rb', line 267

def resolve_server_client_or_both_path
  new_path = destination_config_path("config/webpack/ServerClientOrBoth.js")
  old_path = destination_config_path("config/webpack/generateWebpackConfigs.js")
  full_new = File.join(destination_root, new_path)
  full_old = File.join(destination_root, old_path)

  if File.exist?(full_new)
    new_path
  elsif File.exist?(full_old)
    FileUtils.mv(full_old, full_new)
    %w[development.js production.js test.js].each do |env_file|
      env_path = destination_config_path("config/webpack/#{env_file}")
      if File.exist?(File.join(destination_root, env_path))
        gsub_file(env_path, /generateWebpackConfigs/, "ServerClientOrBoth")
      end
    end
    new_path
  end
end

#setup_file_error(file, data) ⇒ Object



53
54
55
56
57
58
59
# File 'lib/generators/react_on_rails/generator_helper.rb', line 53

def setup_file_error(file, data)
  <<~MSG
    #{file} was not found.
    Please add the following content to your #{file} file:
    #{data}
  MSG
end

#shakapacker_version_9_or_higher?Boolean

Note:

Default behavior: Returns true when Shakapacker is not yet installed Rationale: During fresh installations, we optimistically assume users will install the latest Shakapacker version. This ensures new projects get best-practice configs. If users later install an older version, the generated webpack config includes fallback logic (e.g., ‘config.privateOutputPath || hardcodedPath`) that prevents breakage, and validation warnings guide them to fix any misconfigurations.

Check if Shakapacker 9.0 or higher is available Returns true if Shakapacker >= 9.0, false otherwise

This method is used during code generation to determine which configuration patterns to use in generated files (e.g., config.privateOutputPath vs hardcoded paths).

Returns:

  • (Boolean)

    true if Shakapacker 9.0+ is available or likely to be installed



225
226
227
228
229
230
231
232
233
234
235
236
237
238
# File 'lib/generators/react_on_rails/generator_helper.rb', line 225

def shakapacker_version_9_or_higher?
  return @shakapacker_version_9_or_higher if defined?(@shakapacker_version_9_or_higher)

  @shakapacker_version_9_or_higher = begin
    # If Shakapacker is not available yet (fresh install), default to true
    # since we're likely installing the latest version
    return true unless defined?(ReactOnRails::PackerUtils)

    ReactOnRails::PackerUtils.shakapacker_version_requirement_met?("9.0.0")
  rescue StandardError
    # If we can't determine version, assume latest
    true
  end
end

As opposed to Rails::Generators::Testing.create_link, which creates a link pointing to source_root, this symlinks a file in destination_root to a file also in destination_root.



73
74
75
76
77
78
79
80
81
82
# File 'lib/generators/react_on_rails/generator_helper.rb', line 73

def symlink_dest_file_to_dest_file(target, link)
  target_pathname = Pathname.new(File.join(destination_root, target))
  link_pathname = Pathname.new(File.join(destination_root, link))

  link_directory = link_pathname.dirname
  link_basename = link_pathname.basename
  target_relative_path = target_pathname.relative_path_from(link_directory)

  `cd #{link_directory} && ln -s #{target_relative_path} #{link_basename}`
end

#use_pro?Boolean

Check if Pro features should be enabled Returns true if –pro flag is set OR –rsc flag is set (RSC implies Pro)

Returns:

  • (Boolean)

    true if Pro setup should be included



143
144
145
# File 'lib/generators/react_on_rails/generator_helper.rb', line 143

def use_pro?
  options[:pro] || options[:rsc]
end

#use_rsc?Boolean

Check if RSC (React Server Components) should be enabled Returns true only if –rsc flag is explicitly set

Returns:

  • (Boolean)

    true if RSC setup should be included



151
152
153
# File 'lib/generators/react_on_rails/generator_helper.rb', line 151

def use_rsc?
  options[:rsc]
end

#using_rspack?Boolean

Determine if the project is using rspack as the bundler.

Detection priority:

  1. Explicit –rspack option (most reliable during fresh installs)

  2. config/shakapacker.yml assets_bundler setting (for standalone generators like ‘rails g react_on_rails:rsc` on an existing rspack project)

Returns:

  • (Boolean)

    true if rspack is the configured bundler



163
164
165
166
167
168
169
170
171
# File 'lib/generators/react_on_rails/generator_helper.rb', line 163

def using_rspack?
  return @using_rspack if defined?(@using_rspack)

  # options.key?(:rspack) is true when the generator declares --rspack (e.g. InstallGenerator),
  # false when it does not (e.g. RscGenerator, ProGenerator). Using .key? rather than .nil?
  # check on the value makes the intent explicit and avoids relying on Thor returning nil for
  # undeclared options.
  @using_rspack = options.key?(:rspack) ? options[:rspack] : rspack_configured_in_project?
end

#using_swc?Boolean

Note:

This method is used to determine whether to install SWC dependencies (@swc/core, swc-loader) instead of Babel dependencies during generation.

Note:

Caching: The result is memoized for the lifetime of the generator instance. If shakapacker.yml changes during generator execution (unlikely), the cached value will not update. This is acceptable since generators run quickly.

Check if SWC is configured as the JavaScript transpiler in shakapacker.yml

Detection logic:

  1. If shakapacker.yml exists and specifies javascript_transpiler: parse it

  2. For Shakapacker 9.3.0+, SWC is the default if not specified

  3. Returns true for fresh installations (SWC is recommended default)

Returns:

  • (Boolean)

    true if SWC is configured or should be used by default



255
256
257
258
259
# File 'lib/generators/react_on_rails/generator_helper.rb', line 255

def using_swc?
  return @using_swc if defined?(@using_swc)

  @using_swc = detect_swc_configuration
end