react_on_rails 16.4.0.rc.6 → 16.4.0.rc.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7e2eb14c4503b061f7391a7a72c677ae86a960e895a8549055bcb657f8be9ddc
4
- data.tar.gz: 056d77c9579b70ec0a5afad57cf7b9d38709031852ce693a29a60f7b7dde1a27
3
+ metadata.gz: a834e70a65aa260461effa652dcea303bc28acb1888b8f81176b190a1234359b
4
+ data.tar.gz: 11c61171bcc86b52fa4b376550183f28916894f84e212395a6ad88777195bd0b
5
5
  SHA512:
6
- metadata.gz: 5a4855b3d19061720c333f4bea3bf25b8987906551758dde4b4a549032353280b111a636e79b2d00299cd6d9d0f511310880ed60d4da89c41d2133bf5c004433
7
- data.tar.gz: 2da99325513c8b7b85d9f9121c95168757f855a1adfe0eca7d4ab164a0481159968544355c79662453aac28aa64d3a18cb70dee6468cbe31453c29c4d91856a3
6
+ metadata.gz: 42b6d70052957bda4047e034a44aaffdafe78fae4190f141b9c624294be1e685f0e4a1833b41db17605771bbca917ca05e78d22bd02090ac8cd56dee77065e0a
7
+ data.tar.gz: 158a713f868d6563a1f89eb0c4e2ddf355bbda5c6341df447648681588e1164ae93ca1941e8aa8a4844a8a57661bae4cbb31845aa9855229fff5559e134db432
@@ -2,7 +2,7 @@
2
2
 
3
3
  eval_gemfile File.expand_path("../Gemfile.shared_dev_dependencies", __dir__)
4
4
 
5
- gem "shakapacker", "9.5.0"
5
+ gem "shakapacker", "9.6.1"
6
6
  gem "bootsnap", require: false
7
7
  gem "rails", "~> 7.1.0"
8
8
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- react_on_rails (16.4.0.rc.6)
4
+ react_on_rails (16.4.0.rc.7)
5
5
  addressable
6
6
  connection_pool
7
7
  execjs (~> 2.5)
@@ -138,7 +138,7 @@ GEM
138
138
  drb (2.2.3)
139
139
  equivalent-xml (0.6.0)
140
140
  nokogiri (>= 1.4.3)
141
- erb (6.0.1)
141
+ erb (6.0.2)
142
142
  erubi (1.13.1)
143
143
  execjs (2.9.1)
144
144
  ffi (1.16.3)
@@ -153,8 +153,9 @@ GEM
153
153
  concurrent-ruby (~> 1.0)
154
154
  interception (0.5)
155
155
  io-console (0.8.2)
156
- irb (1.16.0)
156
+ irb (1.17.0)
157
157
  pp (>= 0.6.0)
158
+ prism (>= 1.3.0)
158
159
  rdoc (>= 4.0.0)
159
160
  reline (>= 0.4.2)
160
161
  jbuilder (2.12.0)
@@ -188,7 +189,8 @@ GEM
188
189
  method_source (1.1.0)
189
190
  mini_mime (1.1.5)
190
191
  mini_portile2 (2.8.9)
191
- minitest (6.0.1)
192
+ minitest (6.0.2)
193
+ drb (~> 2.0)
192
194
  prism (~> 1.5)
193
195
  msgpack (1.7.2)
194
196
  mutex_m (0.3.0)
@@ -202,7 +204,7 @@ GEM
202
204
  net-smtp (0.5.1)
203
205
  net-protocol
204
206
  nio4r (2.7.5)
205
- nokogiri (1.19.0)
207
+ nokogiri (1.19.1)
206
208
  mini_portile2 (~> 2.8.2)
207
209
  racc (~> 1.4)
208
210
  ostruct (0.6.1)
@@ -214,7 +216,7 @@ GEM
214
216
  pp (0.6.3)
215
217
  prettyprint
216
218
  prettyprint (0.2.0)
217
- prism (1.8.0)
219
+ prism (1.9.0)
218
220
  pry (0.14.2)
219
221
  coderay (~> 1.1)
220
222
  method_source (~> 1.0)
@@ -236,7 +238,7 @@ GEM
236
238
  puma (6.6.1)
237
239
  nio4r (~> 2.0)
238
240
  racc (1.8.1)
239
- rack (3.2.4)
241
+ rack (3.2.5)
240
242
  rack-proxy (0.7.7)
241
243
  rack
242
244
  rack-session (2.1.1)
@@ -264,8 +266,8 @@ GEM
264
266
  activesupport (>= 5.0.0)
265
267
  minitest
266
268
  nokogiri (>= 1.6)
267
- rails-html-sanitizer (1.6.2)
268
- loofah (~> 2.21)
269
+ rails-html-sanitizer (1.7.0)
270
+ loofah (~> 2.25)
269
271
  nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
270
272
  railties (7.1.6)
271
273
  actionpack (= 7.1.6)
@@ -284,7 +286,7 @@ GEM
284
286
  ffi (~> 1.0)
285
287
  rbs (3.9.5)
286
288
  logger
287
- rdoc (7.1.0)
289
+ rdoc (7.2.0)
288
290
  erb
289
291
  psych (>= 4.0.0)
290
292
  tsort
@@ -359,8 +361,8 @@ GEM
359
361
  rexml (~> 3.2, >= 3.2.5)
360
362
  rubyzip (>= 1.2.2, < 3.0)
361
363
  websocket (~> 1.0)
362
- semantic_range (3.1.0)
363
- shakapacker (9.5.0)
364
+ semantic_range (3.1.1)
365
+ shakapacker (9.6.1)
364
366
  activesupport (>= 5.2)
365
367
  package_json
366
368
  rack-proxy (>= 0.6.1)
@@ -435,7 +437,7 @@ GEM
435
437
  xpath (3.2.0)
436
438
  nokogiri (~> 1.8)
437
439
  yard (0.9.36)
438
- zeitwerk (2.7.4)
440
+ zeitwerk (2.7.5)
439
441
 
440
442
  PLATFORMS
441
443
  ruby
@@ -478,7 +480,7 @@ DEPENDENCIES
478
480
  sass-rails (~> 6.0)
479
481
  sdoc
480
482
  selenium-webdriver (= 4.9.0)
481
- shakapacker (= 9.5.0)
483
+ shakapacker (= 9.6.1)
482
484
  spring (~> 4.0)
483
485
  sprockets (~> 4.0)
484
486
  sqlite3 (~> 1.6)
@@ -39,6 +39,14 @@ module ReactOnRails
39
39
  default: false,
40
40
  desc: "Setup React Server Components (requires Pro)"
41
41
 
42
+ # Hidden option: signals that Shakapacker was just installed by install_generator.
43
+ # When true, copy_packer_config uses force: true to overwrite Shakapacker's default config
44
+ # without prompting, since we know it's a fresh default (not user-customized).
45
+ class_option :shakapacker_just_installed,
46
+ type: :boolean,
47
+ default: false,
48
+ hide: true
49
+
42
50
  def add_hello_world_route
43
51
  # RSC uses HelloServer instead of HelloWorld, but Redux still needs hello_world route
44
52
  return if use_rsc? && !options.redux?
@@ -59,6 +67,7 @@ module ReactOnRails
59
67
  Procfile.dev-static-assets
60
68
  Procfile.dev-prod-assets
61
69
  .dev-services.yml.example
70
+ .env.example
62
71
  bin/shakapacker-precompile-hook]
63
72
 
64
73
  # HelloServer uses the hello_world layout so React on Rails can inject generated
@@ -75,7 +84,11 @@ module ReactOnRails
75
84
  end
76
85
 
77
86
  # Make the hook script executable (copy_file guarantees it exists)
78
- File.chmod(0o755, File.join(destination_root, "bin/shakapacker-precompile-hook"))
87
+ if options[:pretend]
88
+ say_status :pretend, "Skipping chmod on shakapacker-precompile-hook in --pretend mode", :yellow
89
+ else
90
+ File.chmod(0o755, File.join(destination_root, "bin/shakapacker-precompile-hook"))
91
+ end
79
92
  end
80
93
 
81
94
  def copy_js_bundle_files
@@ -91,7 +104,7 @@ module ReactOnRails
91
104
  end
92
105
 
93
106
  def copy_webpack_config
94
- puts "Adding Webpack config"
107
+ puts "Adding #{using_rspack? ? 'Rspack' : 'Webpack'} config"
95
108
  base_path = "base/base"
96
109
  base_files = %w[babel.config.js
97
110
  config/webpack/clientWebpackConfig.js
@@ -104,32 +117,38 @@ module ReactOnRails
104
117
  config = {
105
118
  message: "// The source code including full typescript support is available at:"
106
119
  }
107
- base_files.each { |file| template("#{base_path}/#{file}.tt", file, config) }
120
+ base_files.each do |file|
121
+ template("#{base_path}/#{file}.tt", destination_config_path(file), config)
122
+ end
108
123
 
109
124
  # Handle webpack.config.js separately with smart replacement
110
125
  copy_webpack_main_config(base_path, config)
111
126
  end
112
127
 
113
128
  def copy_packer_config
114
- # Skip copying if Shakapacker was just installed (to avoid conflicts)
115
- # Check for a temporary marker file that indicates fresh Shakapacker install
116
- if File.exist?(".shakapacker_just_installed")
117
- puts "Skipping Shakapacker config copy (already installed by Shakapacker installer)"
118
- File.delete(".shakapacker_just_installed") # Clean up marker
129
+ base_path = "base/base/"
130
+ config = "config/shakapacker.yml"
131
+
132
+ if options.shakapacker_just_installed?
133
+ puts "Replacing Shakapacker default config with React on Rails version"
134
+ # Shakapacker's installer just created this file from scratch (no pre-existing config).
135
+ # Safe to overwrite silently with RoR's version-aware template (e.g., private_output_path).
136
+ template("#{base_path}#{config}.tt", config, force: true)
119
137
  else
120
138
  puts "Adding Shakapacker #{ReactOnRails::PackerUtils.shakapacker_version} config"
121
- base_path = "base/base/"
122
- config = "config/shakapacker.yml"
123
- # Use template to enable version-aware configuration
139
+ # Thor handles the conflict: prompts user interactively, or respects --force/--skip flags.
124
140
  template("#{base_path}#{config}.tt", config)
125
141
  end
126
142
 
127
143
  # Configure bundler-specific settings
128
- configure_rspack_in_shakapacker if options.rspack?
144
+ configure_rspack_in_shakapacker if using_rspack?
129
145
 
130
146
  # Always ensure precompile_hook is configured (Shakapacker 9.0+ only)
131
- # This handles all scenarios: fresh install, pre-installed Shakapacker, or user declined overwrite
132
147
  configure_precompile_hook_in_shakapacker
148
+
149
+ # For SSR bundles, configure Shakapacker private_output_path (9.0+ only)
150
+ # This keeps Shakapacker and React on Rails server bundle paths in sync.
151
+ configure_private_output_path_in_shakapacker
133
152
  end
134
153
 
135
154
  def add_base_gems_to_gemfile
@@ -145,11 +164,12 @@ module ReactOnRails
145
164
  additions = []
146
165
  additions << "**/generated/**" unless gitignore_content.include?("**/generated/**")
147
166
  additions << "ssr-generated" unless gitignore_content.include?("ssr-generated")
167
+ additions << ".env" unless gitignore_content.match?(/^\.env$/)
148
168
 
149
169
  return if additions.empty?
150
170
 
151
171
  append_to_file ".gitignore" do
152
- lines = ["\n# Generated React on Rails packs"]
172
+ lines = ["\n# React on Rails (generated and local files)"]
153
173
  lines.concat(additions)
154
174
  "#{lines.join("\n")}\n"
155
175
  end
@@ -176,7 +196,7 @@ module ReactOnRails
176
196
  private
177
197
 
178
198
  def copy_webpack_main_config(base_path, config)
179
- webpack_config_path = "config/webpack/webpack.config.js"
199
+ webpack_config_path = bundler_main_config_path
180
200
 
181
201
  if File.exist?(webpack_config_path)
182
202
  existing_content = File.read(webpack_config_path)
@@ -188,7 +208,7 @@ module ReactOnRails
188
208
  # Show what we're doing
189
209
  puts " #{set_color('replace', :green)} #{webpack_config_path} " \
190
210
  "(auto-upgrading from standard Shakapacker to React on Rails config)"
191
- template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
211
+ template("#{base_path}/config/webpack/webpack.config.js.tt", webpack_config_path, config)
192
212
  elsif react_on_rails_config?(existing_content)
193
213
  puts " #{set_color('identical', :blue)} #{webpack_config_path} " \
194
214
  "(already React on Rails compatible)"
@@ -198,17 +218,19 @@ module ReactOnRails
198
218
  end
199
219
  else
200
220
  # File doesn't exist, create it
201
- template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
221
+ template("#{base_path}/config/webpack/webpack.config.js.tt", webpack_config_path, config)
202
222
  end
203
223
  end
204
224
 
205
225
  def handle_custom_webpack_config(base_path, config, webpack_config_path)
206
226
  # Custom config - ask user
207
- puts "\n#{set_color('NOTICE:', :yellow)} Your webpack.config.js appears to be customized."
227
+ config_file_name = File.basename(webpack_config_path)
228
+ bundler_name = using_rspack? ? "rspack" : "webpack"
229
+ puts "\n#{set_color('NOTICE:', :yellow)} Your #{config_file_name} appears to be customized."
208
230
  puts "React on Rails needs to replace it with an environment-specific loader."
209
- puts "Your current config will be backed up to webpack.config.js.backup"
231
+ puts "Your current config will be backed up to #{config_file_name}.backup"
210
232
 
211
- if yes?("Replace webpack.config.js with React on Rails version? (Y/n)")
233
+ if yes?("Replace #{config_file_name} with React on Rails version? (Y/n)")
212
234
  # Create backup
213
235
  backup_path = "#{webpack_config_path}.backup"
214
236
  if File.exist?(webpack_config_path)
@@ -216,11 +238,19 @@ module ReactOnRails
216
238
  puts " #{set_color('create', :green)} #{backup_path} (backup of your custom config)"
217
239
  end
218
240
 
219
- template("#{base_path}/#{webpack_config_path}.tt", webpack_config_path, config)
241
+ template("#{base_path}/config/webpack/webpack.config.js.tt", webpack_config_path, config)
220
242
  else
221
243
  puts " #{set_color('skip', :yellow)} #{webpack_config_path}"
222
244
  puts " #{set_color('WARNING:', :red)} React on Rails may not work correctly " \
223
- "without the environment-specific webpack config"
245
+ "without the environment-specific #{bundler_name} config"
246
+ end
247
+ end
248
+
249
+ def bundler_main_config_path
250
+ if using_rspack?
251
+ "config/rspack/rspack.config.js"
252
+ else
253
+ "config/webpack/webpack.config.js"
224
254
  end
225
255
  end
226
256
 
@@ -279,6 +309,22 @@ module ReactOnRails
279
309
  module.exports = webpackConfig
280
310
  CONFIG
281
311
 
312
+ # Shakapacker v9+ rspack configs (generateRspackConfig function)
313
+ configs << <<~CONFIG
314
+ // See the shakacode/shakapacker README and docs directory for advice on customizing your rspackConfig.
315
+ const { generateRspackConfig } = require('shakapacker/rspack')
316
+
317
+ const rspackConfig = generateRspackConfig()
318
+
319
+ module.exports = rspackConfig
320
+ CONFIG
321
+
322
+ configs << <<~CONFIG
323
+ const { generateRspackConfig } = require('shakapacker/rspack')
324
+ const rspackConfig = generateRspackConfig()
325
+ module.exports = rspackConfig
326
+ CONFIG
327
+
282
328
  configs
283
329
  end
284
330
 
@@ -362,6 +408,46 @@ module ReactOnRails
362
408
 
363
409
  puts Rainbow("✅ Configured precompile_hook in shakapacker.yml").green
364
410
  end
411
+
412
+ def configure_private_output_path_in_shakapacker
413
+ # private_output_path is only supported in Shakapacker 9.0+
414
+ return unless ReactOnRails::PackerUtils.shakapacker_version_requirement_met?("9.0.0")
415
+
416
+ shakapacker_config_path = "config/shakapacker.yml"
417
+ return unless File.exist?(shakapacker_config_path)
418
+
419
+ content = File.read(shakapacker_config_path)
420
+
421
+ # A non-empty active value means the app already made an explicit choice.
422
+ return if content.match?(/^\s+private_output_path:\s*\S+/)
423
+
424
+ # Normalize an empty active key before falling back to insertion.
425
+ gsub_file(
426
+ shakapacker_config_path,
427
+ /^(\s*)private_output_path:\s*$/,
428
+ "\\1private_output_path: ssr-generated"
429
+ )
430
+
431
+ # First try: uncomment an existing private_output_path placeholder line.
432
+ gsub_file(
433
+ shakapacker_config_path,
434
+ /^(\s*)#\s*private_output_path:\s*.*$/,
435
+ "\\1private_output_path: ssr-generated"
436
+ )
437
+
438
+ # Fallback: insert directly after public_output_path in the same section.
439
+ unless File.read(shakapacker_config_path).match?(/^\s+private_output_path:\s*ssr-generated/)
440
+ gsub_file(
441
+ shakapacker_config_path,
442
+ /^(\s*)(public_output_path:\s*.*\n)/,
443
+ "\\1\\2\\1private_output_path: ssr-generated\n"
444
+ )
445
+ end
446
+
447
+ return unless File.read(shakapacker_config_path).match?(/^\s+private_output_path:\s*ssr-generated/)
448
+
449
+ puts Rainbow("✅ Configured private_output_path in shakapacker.yml").green
450
+ end
365
451
  end
366
452
  end
367
453
  end
@@ -145,6 +145,36 @@ module GeneratorHelper
145
145
  options[:rsc]
146
146
  end
147
147
 
148
+ # Determine if the project is using rspack as the bundler.
149
+ #
150
+ # Detection priority:
151
+ # 1. Explicit --rspack option (most reliable during fresh installs)
152
+ # 2. config/shakapacker.yml assets_bundler setting (for standalone generators
153
+ # like `rails g react_on_rails:rsc` on an existing rspack project)
154
+ #
155
+ # @return [Boolean] true if rspack is the configured bundler
156
+ def using_rspack?
157
+ return @using_rspack if defined?(@using_rspack)
158
+
159
+ # options.key?(:rspack) is true when the generator declares --rspack (e.g. InstallGenerator),
160
+ # false when it does not (e.g. RscGenerator, ProGenerator). Using .key? rather than .nil?
161
+ # check on the value makes the intent explicit and avoids relying on Thor returning nil for
162
+ # undeclared options.
163
+ @using_rspack = options.key?(:rspack) ? options[:rspack] : rspack_configured_in_project?
164
+ end
165
+
166
+ # Remap a config path from config/webpack/ to config/rspack/ when using rspack.
167
+ # Source templates always live under config/webpack/ (template names are stable);
168
+ # this method handles the destination remapping.
169
+ #
170
+ # @param path [String] relative path, e.g. "config/webpack/serverWebpackConfig.js"
171
+ # @return [String] remapped path when rspack, unchanged otherwise
172
+ def destination_config_path(path)
173
+ return path unless using_rspack?
174
+
175
+ path.sub(%r{\Aconfig/webpack/}, "config/rspack/")
176
+ end
177
+
148
178
  # Detect the installed React version from package.json
149
179
  # Uses VERSION_PARTS_REGEX pattern from VersionChecker for consistency
150
180
  #
@@ -228,8 +258,8 @@ module GeneratorHelper
228
258
  #
229
259
  # @return [String, nil] relative config path, or nil if neither file exists
230
260
  def resolve_server_client_or_both_path
231
- new_path = "config/webpack/ServerClientOrBoth.js"
232
- old_path = "config/webpack/generateWebpackConfigs.js"
261
+ new_path = destination_config_path("config/webpack/ServerClientOrBoth.js")
262
+ old_path = destination_config_path("config/webpack/generateWebpackConfigs.js")
233
263
  full_new = File.join(destination_root, new_path)
234
264
  full_old = File.join(destination_root, old_path)
235
265
 
@@ -238,7 +268,7 @@ module GeneratorHelper
238
268
  elsif File.exist?(full_old)
239
269
  FileUtils.mv(full_old, full_new)
240
270
  %w[development.js production.js test.js].each do |env_file|
241
- env_path = "config/webpack/#{env_file}"
271
+ env_path = destination_config_path("config/webpack/#{env_file}")
242
272
  if File.exist?(File.join(destination_root, env_path))
243
273
  gsub_file(env_path, /generateWebpackConfigs/, "ServerClientOrBoth")
244
274
  end
@@ -249,6 +279,10 @@ module GeneratorHelper
249
279
 
250
280
  private
251
281
 
282
+ # NOTE: only the `default:` section is inspected — same assumption as
283
+ # rspack_configured_in_project?. Projects that set `javascript_transpiler`
284
+ # only in per-environment sections (without a `default:` block) will not be
285
+ # detected. In practice Shakapacker always places it in `default: &default`.
252
286
  def detect_swc_configuration
253
287
  shakapacker_yml_path = File.join(destination_root, "config/shakapacker.yml")
254
288
 
@@ -296,4 +330,20 @@ module GeneratorHelper
296
330
  # If we can't determine version, assume latest (which uses SWC)
297
331
  true
298
332
  end
333
+
334
+ # Detect rspack from config/shakapacker.yml when no explicit --rspack option is available.
335
+ # Used by standalone generators (RscGenerator, ProGenerator) on existing projects.
336
+ #
337
+ # Note: only the `default:` section is inspected. Projects that set `assets_bundler`
338
+ # only in per-environment sections (without a `default:` block) will not be detected.
339
+ # This is not a concern in practice: Shakapacker's install template always places
340
+ # `assets_bundler` inside the `default: &default` block, and our generator writes
341
+ # it there too via configure_rspack_in_shakapacker.
342
+ def rspack_configured_in_project?
343
+ shakapacker_yml_path = File.join(destination_root, "config/shakapacker.yml")
344
+ return false unless File.exist?(shakapacker_yml_path)
345
+
346
+ config = parse_shakapacker_yml(shakapacker_yml_path)
347
+ config.dig("default", "assets_bundler") == "rspack"
348
+ end
299
349
  end
@@ -38,11 +38,12 @@ module GeneratorMessages
38
38
  @output = []
39
39
  end
40
40
 
41
- def helpful_message_after_installation(component_name: "HelloWorld", route: "hello_world", rsc: false)
41
+ def helpful_message_after_installation(component_name: "HelloWorld", route: "hello_world", rsc: false,
42
+ shakapacker_just_installed: false)
42
43
  process_manager_section = build_process_manager_section
43
44
  testing_section = build_testing_section
44
45
  package_manager = detect_package_manager
45
- shakapacker_status = build_shakapacker_status_section
46
+ shakapacker_status = build_shakapacker_status_section(shakapacker_just_installed: shakapacker_just_installed)
46
47
  render_example = build_render_example(component_name: component_name, route: route, rsc: rsc)
47
48
  render_label = if rsc
48
49
  "• Streaming server rendering in app/views/#{route}/index.html.erb:"
@@ -142,11 +143,11 @@ module GeneratorMessages
142
143
  end
143
144
  end
144
145
 
145
- def build_shakapacker_status_section
146
+ def build_shakapacker_status_section(shakapacker_just_installed: false)
146
147
  version_warning = check_shakapacker_version_warning
147
148
 
148
- if File.exist?(".shakapacker_just_installed")
149
- base_message = <<~SHAKAPACKER
149
+ if shakapacker_just_installed
150
+ base = <<~SHAKAPACKER
150
151
 
151
152
  📦 SHAKAPACKER SETUP:
152
153
  ─────────────────────────────────────────────────────────────────────────
@@ -154,7 +155,7 @@ module GeneratorMessages
154
155
  #{Rainbow('✓ Installer ran successfully').green}
155
156
  #{Rainbow('✓ Webpack integration configured').green}
156
157
  SHAKAPACKER
157
- base_message + version_warning
158
+ base.chomp + version_warning
158
159
  elsif File.exist?("bin/shakapacker") && File.exist?("bin/shakapacker-dev-server")
159
160
  "\n📦 #{Rainbow('Shakapacker already configured ✓').green}#{version_warning}"
160
161
  else