react_on_rails 16.4.0.rc.7 → 16.4.0.rc.8
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 +4 -4
- data/Gemfile.lock +1 -1
- data/lib/generators/react_on_rails/js_dependency_manager.rb +32 -6
- data/lib/react_on_rails/helper.rb +7 -0
- data/lib/react_on_rails/packer_utils.rb +19 -106
- data/lib/react_on_rails/system_checker.rb +71 -21
- data/lib/react_on_rails/version.rb +1 -1
- data/rakelib/update_changelog.rake +315 -17
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8836b6e37dab21e6ee52f045e1f1dbb0ddce044107ce4ff6b48724ac57003bb9
|
|
4
|
+
data.tar.gz: 3deef582db0eccf9d692813848f8dc0e1f7c610f819497bb563fb6de0b5b2091
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 38982dfff467e79045d3c6017a991f8dedac9c17abee867cb55197e2514cbe238317ec99ef89e5ad0c258a7d306bf8325ea1266926ae85a98111af1f759023fb
|
|
7
|
+
data.tar.gz: 124b251b8a0ca66b4f38a8d2990012690be96408c768a4f1081ad43f87c6d73b525a5bfd2de56d023a138288a28f5dbf6a6f988bdcbab8dd8bf7f1e2ea585be7
|
data/Gemfile.lock
CHANGED
|
@@ -53,17 +53,19 @@ module ReactOnRails
|
|
|
53
53
|
# to handle all JS dependency installation via package_json gem.
|
|
54
54
|
module JsDependencyManager
|
|
55
55
|
# Core React dependencies required for React on Rails
|
|
56
|
-
# Note: @babel/preset-react
|
|
57
|
-
#
|
|
58
|
-
# - Users configure their preferred transpiler via shakapacker.yml javascript_transpiler setting
|
|
59
|
-
# - SWC is now the default and doesn't need Babel presets
|
|
60
|
-
# - For Babel users, shakapacker will install babel-loader and its dependencies
|
|
56
|
+
# Note: @babel/preset-react is handled separately in BABEL_REACT_DEPENDENCIES
|
|
57
|
+
# and is added only when SWC is not the active transpiler.
|
|
61
58
|
REACT_DEPENDENCIES = %w[
|
|
62
59
|
react
|
|
63
60
|
react-dom
|
|
64
61
|
prop-types
|
|
65
62
|
].freeze
|
|
66
63
|
|
|
64
|
+
# Babel preset needed by the generated babel.config.js for non-SWC setups.
|
|
65
|
+
BABEL_REACT_DEPENDENCIES = %w[
|
|
66
|
+
@babel/preset-react
|
|
67
|
+
].freeze
|
|
68
|
+
|
|
67
69
|
# CSS processing dependencies for webpack
|
|
68
70
|
CSS_DEPENDENCIES = %w[
|
|
69
71
|
css-loader
|
|
@@ -144,12 +146,17 @@ module ReactOnRails
|
|
|
144
146
|
add_react_dependencies
|
|
145
147
|
add_css_dependencies
|
|
146
148
|
add_rspack_dependencies if using_rspack?
|
|
147
|
-
|
|
149
|
+
add_transpiler_dependencies
|
|
148
150
|
add_pro_dependencies if using_pro
|
|
149
151
|
add_rsc_dependencies if using_rsc
|
|
150
152
|
add_dev_dependencies
|
|
151
153
|
end
|
|
152
154
|
|
|
155
|
+
def add_transpiler_dependencies
|
|
156
|
+
add_swc_dependencies if using_swc?
|
|
157
|
+
add_babel_react_dependencies if !using_swc? && !using_rspack?
|
|
158
|
+
end
|
|
159
|
+
|
|
153
160
|
def add_react_on_rails_package
|
|
154
161
|
# Use exact version match between gem and npm package for all versions including pre-releases
|
|
155
162
|
# Ruby gem versions use dots (16.2.0.beta.10) but npm requires hyphens (16.2.0-beta.10)
|
|
@@ -289,6 +296,25 @@ module ReactOnRails
|
|
|
289
296
|
MSG
|
|
290
297
|
end
|
|
291
298
|
|
|
299
|
+
def add_babel_react_dependencies
|
|
300
|
+
puts "Installing Babel React preset dependency..."
|
|
301
|
+
return if add_packages(BABEL_REACT_DEPENDENCIES, dev: true)
|
|
302
|
+
|
|
303
|
+
GeneratorMessages.add_warning(<<~MSG.strip)
|
|
304
|
+
⚠️ Failed to add Babel React preset dependency.
|
|
305
|
+
|
|
306
|
+
You can install it manually by running:
|
|
307
|
+
npm install --save-dev #{BABEL_REACT_DEPENDENCIES.join(' ')}
|
|
308
|
+
MSG
|
|
309
|
+
rescue StandardError => e
|
|
310
|
+
GeneratorMessages.add_warning(<<~MSG.strip)
|
|
311
|
+
⚠️ Error adding Babel React preset dependency: #{e.message}
|
|
312
|
+
|
|
313
|
+
You can install it manually by running:
|
|
314
|
+
npm install --save-dev #{BABEL_REACT_DEPENDENCIES.join(' ')}
|
|
315
|
+
MSG
|
|
316
|
+
end
|
|
317
|
+
|
|
292
318
|
def add_typescript_dependencies
|
|
293
319
|
puts "Installing TypeScript dependencies..."
|
|
294
320
|
return if add_packages(TYPESCRIPT_DEPENDENCIES, dev: true)
|
|
@@ -304,6 +304,8 @@ module ReactOnRails
|
|
|
304
304
|
end
|
|
305
305
|
end
|
|
306
306
|
|
|
307
|
+
add_csp_nonce_to_context(result)
|
|
308
|
+
|
|
307
309
|
if defined?(request) && request.present?
|
|
308
310
|
# Check for encoding of the request's original_url and try to force-encoding the
|
|
309
311
|
# URLs as UTF-8. This situation can occur in browsers that do not encode the
|
|
@@ -341,6 +343,11 @@ module ReactOnRails
|
|
|
341
343
|
@rails_context.merge(serverSide: server_side)
|
|
342
344
|
end
|
|
343
345
|
|
|
346
|
+
def add_csp_nonce_to_context(result)
|
|
347
|
+
nonce = csp_nonce
|
|
348
|
+
result[:cspNonce] = nonce if nonce.present?
|
|
349
|
+
end
|
|
350
|
+
|
|
344
351
|
def load_pack_for_generated_component(react_component_name, render_options)
|
|
345
352
|
return unless render_options.auto_load_bundle
|
|
346
353
|
|
|
@@ -1,15 +1,10 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "erb"
|
|
4
3
|
require "pathname"
|
|
5
4
|
require "shakapacker"
|
|
6
|
-
require "yaml"
|
|
7
5
|
|
|
8
6
|
module ReactOnRails
|
|
9
|
-
# rubocop:disable Metrics/ModuleLength
|
|
10
7
|
module PackerUtils
|
|
11
|
-
SHAKAPACKER_CONFIG_PATH = File.join("config", "shakapacker.yml")
|
|
12
|
-
|
|
13
8
|
def self.dev_server_running?
|
|
14
9
|
Shakapacker.dev_server.running?
|
|
15
10
|
end
|
|
@@ -185,20 +180,21 @@ module ReactOnRails
|
|
|
185
180
|
end
|
|
186
181
|
|
|
187
182
|
def self.extract_precompile_hook
|
|
188
|
-
# Prefer
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
183
|
+
# Prefer the public API (available in Shakapacker 9.0+)
|
|
184
|
+
return ::Shakapacker.config.precompile_hook if ::Shakapacker.config.respond_to?(:precompile_hook)
|
|
185
|
+
|
|
186
|
+
# Fallback: access config data using private :data method
|
|
187
|
+
config_data = ::Shakapacker.config.send(:data)
|
|
193
188
|
|
|
194
|
-
|
|
189
|
+
# Try symbol keys first (Shakapacker's internal format), then fall back to string keys
|
|
190
|
+
config_data&.[](:precompile_hook) || config_data&.[]("precompile_hook")
|
|
195
191
|
end
|
|
196
192
|
|
|
197
193
|
# Regex pattern to detect pack generation in hook scripts
|
|
198
194
|
# Matches both:
|
|
199
195
|
# - The rake task: react_on_rails:generate_packs
|
|
200
|
-
# - The Ruby
|
|
201
|
-
GENERATE_PACKS_PATTERN = /\b(react_on_rails:generate_packs|generate_packs_if_stale
|
|
196
|
+
# - The Ruby method: generate_packs_if_stale (used by generator template)
|
|
197
|
+
GENERATE_PACKS_PATTERN = /\b(react_on_rails:generate_packs|generate_packs_if_stale)\b/
|
|
202
198
|
|
|
203
199
|
# Pattern to detect a real self-guard statement that exits early when
|
|
204
200
|
# SHAKAPACKER_SKIP_PRECOMPILE_HOOK is true. This avoids false positives
|
|
@@ -221,7 +217,7 @@ module ReactOnRails
|
|
|
221
217
|
|
|
222
218
|
# Check if it's a script file path
|
|
223
219
|
script_path = resolve_hook_script_path(hook_value)
|
|
224
|
-
return false unless script_path
|
|
220
|
+
return false unless script_path && File.exist?(script_path)
|
|
225
221
|
|
|
226
222
|
# Read and check script contents
|
|
227
223
|
script_contents = File.read(script_path)
|
|
@@ -234,29 +230,20 @@ module ReactOnRails
|
|
|
234
230
|
def self.resolve_hook_script_path(hook_value)
|
|
235
231
|
return nil if hook_value.blank?
|
|
236
232
|
|
|
237
|
-
|
|
238
|
-
return nil if hook_path.empty?
|
|
239
|
-
|
|
240
|
-
# Strip interpreter prefix (e.g., "ruby bin/hook" -> "bin/hook")
|
|
241
|
-
hook_path = extract_script_from_command(hook_path) || hook_path
|
|
242
|
-
|
|
243
|
-
# Hook value might be a script path relative to project root.
|
|
244
|
-
# project_root prefers Rails.root and otherwise derives from BUNDLE_GEMFILE or cwd.
|
|
245
|
-
potential_path = project_root.join(hook_path)
|
|
233
|
+
potential_path = project_root.join(hook_value.to_s.strip)
|
|
246
234
|
potential_path if potential_path.file?
|
|
247
235
|
end
|
|
248
236
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
# Returns nil if the value doesn't look like an interpreter-prefixed command.
|
|
252
|
-
def self.extract_script_from_command(command)
|
|
253
|
-
parts = command.strip.split(/\s+/, 2)
|
|
254
|
-
return nil unless parts.length == 2
|
|
237
|
+
def self.project_root
|
|
238
|
+
return Rails.root if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
255
239
|
|
|
256
|
-
|
|
257
|
-
|
|
240
|
+
bundle_gemfile = ENV.fetch("BUNDLE_GEMFILE", nil)
|
|
241
|
+
if bundle_gemfile && !bundle_gemfile.strip.empty?
|
|
242
|
+
gemfile_path = Pathname.new(bundle_gemfile).expand_path
|
|
243
|
+
return gemfile_path.dirname if gemfile_path.file?
|
|
244
|
+
end
|
|
258
245
|
|
|
259
|
-
|
|
246
|
+
Pathname.new(Dir.pwd)
|
|
260
247
|
end
|
|
261
248
|
|
|
262
249
|
# Check if a hook script file contains the self-guard pattern that prevents
|
|
@@ -283,79 +270,5 @@ module ReactOnRails
|
|
|
283
270
|
rescue StandardError
|
|
284
271
|
nil
|
|
285
272
|
end
|
|
286
|
-
|
|
287
|
-
def self.extract_precompile_hook_from_shakapacker_config
|
|
288
|
-
# Prefer the public API (available in Shakapacker 9.0+)
|
|
289
|
-
return ::Shakapacker.config.precompile_hook if ::Shakapacker.config.respond_to?(:precompile_hook)
|
|
290
|
-
|
|
291
|
-
# Fallback: access config data using private :data method
|
|
292
|
-
config_data = ::Shakapacker.config.send(:data)
|
|
293
|
-
|
|
294
|
-
# Try symbol keys first (Shakapacker's internal format), then fall back to string keys
|
|
295
|
-
if config_data&.key?(:precompile_hook)
|
|
296
|
-
config_data[:precompile_hook]
|
|
297
|
-
elsif config_data&.key?("precompile_hook")
|
|
298
|
-
config_data["precompile_hook"]
|
|
299
|
-
end
|
|
300
|
-
rescue StandardError
|
|
301
|
-
nil
|
|
302
|
-
end
|
|
303
|
-
|
|
304
|
-
def self.extract_precompile_hook_from_yaml
|
|
305
|
-
config_path = project_root.join(SHAKAPACKER_CONFIG_PATH)
|
|
306
|
-
return nil unless config_path.file?
|
|
307
|
-
|
|
308
|
-
yaml_content = ERB.new(File.read(config_path)).result
|
|
309
|
-
config_data = YAML.safe_load(yaml_content, permitted_classes: [Symbol], aliases: true) || {}
|
|
310
|
-
|
|
311
|
-
env_config = extract_hash_for_environment(config_data, current_shakapacker_environment)
|
|
312
|
-
return env_config["precompile_hook"] if env_config.key?("precompile_hook")
|
|
313
|
-
return env_config[:precompile_hook] if env_config.key?(:precompile_hook)
|
|
314
|
-
|
|
315
|
-
default_config = extract_hash_for_environment(config_data, "default")
|
|
316
|
-
return default_config["precompile_hook"] if default_config.key?("precompile_hook")
|
|
317
|
-
return default_config[:precompile_hook] if default_config.key?(:precompile_hook)
|
|
318
|
-
|
|
319
|
-
nil
|
|
320
|
-
rescue StandardError
|
|
321
|
-
nil
|
|
322
|
-
end
|
|
323
|
-
|
|
324
|
-
def self.extract_hash_for_environment(config_data, env_name)
|
|
325
|
-
value = config_data[env_name] || config_data[env_name.to_sym]
|
|
326
|
-
value.is_a?(Hash) ? value : {}
|
|
327
|
-
end
|
|
328
|
-
|
|
329
|
-
def self.current_shakapacker_environment
|
|
330
|
-
if defined?(Rails) && Rails.respond_to?(:env)
|
|
331
|
-
env = begin
|
|
332
|
-
Rails.env.to_s
|
|
333
|
-
rescue StandardError
|
|
334
|
-
nil
|
|
335
|
-
end
|
|
336
|
-
return env unless env.to_s.strip.empty?
|
|
337
|
-
end
|
|
338
|
-
|
|
339
|
-
rails_env = ENV.fetch("RAILS_ENV", nil)
|
|
340
|
-
return rails_env unless rails_env.to_s.strip.empty?
|
|
341
|
-
|
|
342
|
-
rack_env = ENV.fetch("RACK_ENV", nil)
|
|
343
|
-
return rack_env unless rack_env.to_s.strip.empty?
|
|
344
|
-
|
|
345
|
-
"development"
|
|
346
|
-
end
|
|
347
|
-
|
|
348
|
-
def self.project_root
|
|
349
|
-
return Rails.root if defined?(Rails) && Rails.respond_to?(:root) && Rails.root
|
|
350
|
-
|
|
351
|
-
bundle_gemfile = ENV.fetch("BUNDLE_GEMFILE", nil)
|
|
352
|
-
if bundle_gemfile && !bundle_gemfile.strip.empty?
|
|
353
|
-
gemfile_path = Pathname.new(bundle_gemfile).expand_path
|
|
354
|
-
return gemfile_path.dirname if gemfile_path.file?
|
|
355
|
-
end
|
|
356
|
-
|
|
357
|
-
Pathname.new(Dir.pwd)
|
|
358
|
-
end
|
|
359
273
|
end
|
|
360
|
-
# rubocop:enable Metrics/ModuleLength
|
|
361
274
|
end
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "erb"
|
|
3
4
|
require "open3"
|
|
5
|
+
require "yaml"
|
|
4
6
|
|
|
5
7
|
module ReactOnRails
|
|
6
8
|
# SystemChecker provides validation methods for React on Rails setup
|
|
@@ -186,14 +188,13 @@ module ReactOnRails
|
|
|
186
188
|
return unless File.exist?(package_json_path)
|
|
187
189
|
|
|
188
190
|
package_json = JSON.parse(File.read(package_json_path))
|
|
189
|
-
npm_version = package_json
|
|
190
|
-
package_json.dig("devDependencies", "react-on-rails")
|
|
191
|
+
package_name, npm_version = react_on_rails_npm_package_details(package_json)
|
|
191
192
|
|
|
192
|
-
if
|
|
193
|
-
add_success("✅
|
|
193
|
+
if package_name
|
|
194
|
+
add_success("✅ #{package_name} NPM package #{npm_version} is declared")
|
|
194
195
|
else
|
|
195
196
|
add_warning(<<~MSG.strip)
|
|
196
|
-
⚠️ react-on-rails NPM package
|
|
197
|
+
⚠️ Neither react-on-rails nor react-on-rails-pro NPM package found in package.json.
|
|
197
198
|
|
|
198
199
|
Install it with:
|
|
199
200
|
npm install react-on-rails
|
|
@@ -208,8 +209,7 @@ module ReactOnRails
|
|
|
208
209
|
|
|
209
210
|
begin
|
|
210
211
|
package_json = JSON.parse(File.read("package.json"))
|
|
211
|
-
npm_version = package_json
|
|
212
|
-
package_json.dig("devDependencies", "react-on-rails")
|
|
212
|
+
package_name, npm_version = react_on_rails_npm_package_details(package_json)
|
|
213
213
|
|
|
214
214
|
return unless npm_version && defined?(ReactOnRails::VERSION)
|
|
215
215
|
|
|
@@ -221,7 +221,7 @@ module ReactOnRails
|
|
|
221
221
|
gem_version = ReactOnRails::VERSION
|
|
222
222
|
|
|
223
223
|
if normalized_npm_version == gem_version
|
|
224
|
-
add_success("✅ React on Rails gem and NPM package versions match (#{gem_version})")
|
|
224
|
+
add_success("✅ React on Rails gem and #{package_name} NPM package versions match (#{gem_version})")
|
|
225
225
|
check_version_patterns(npm_version, gem_version)
|
|
226
226
|
else
|
|
227
227
|
# Check for major version differences
|
|
@@ -232,7 +232,7 @@ module ReactOnRails
|
|
|
232
232
|
add_error(<<~MSG.strip)
|
|
233
233
|
🚫 Major version mismatch detected:
|
|
234
234
|
• Gem version: #{gem_version} (major: #{gem_major})
|
|
235
|
-
•
|
|
235
|
+
• #{package_name} version: #{npm_version} (major: #{npm_major})
|
|
236
236
|
|
|
237
237
|
Major version differences can cause serious compatibility issues.
|
|
238
238
|
Update both packages to use the same major version immediately.
|
|
@@ -241,7 +241,7 @@ module ReactOnRails
|
|
|
241
241
|
add_warning(<<~MSG.strip)
|
|
242
242
|
⚠️ Version mismatch detected:
|
|
243
243
|
• Gem version: #{gem_version}
|
|
244
|
-
•
|
|
244
|
+
• #{package_name} version: #{npm_version}
|
|
245
245
|
|
|
246
246
|
Consider updating to exact, fixed matching versions of gem and npm package for best compatibility.
|
|
247
247
|
MSG
|
|
@@ -341,7 +341,7 @@ module ReactOnRails
|
|
|
341
341
|
|
|
342
342
|
begin
|
|
343
343
|
package_json = JSON.parse(File.read("package.json"))
|
|
344
|
-
all_deps = package_json["dependencies"]
|
|
344
|
+
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
345
345
|
all_deps["webpack-bundle-analyzer"]
|
|
346
346
|
rescue StandardError
|
|
347
347
|
false
|
|
@@ -373,6 +373,14 @@ module ReactOnRails
|
|
|
373
373
|
|
|
374
374
|
private
|
|
375
375
|
|
|
376
|
+
def react_on_rails_npm_package_details(package_json)
|
|
377
|
+
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
378
|
+
return ["react-on-rails-pro", all_deps["react-on-rails-pro"]] if all_deps["react-on-rails-pro"]
|
|
379
|
+
return ["react-on-rails", all_deps["react-on-rails"]] if all_deps["react-on-rails"]
|
|
380
|
+
|
|
381
|
+
[nil, nil]
|
|
382
|
+
end
|
|
383
|
+
|
|
376
384
|
def node_missing?
|
|
377
385
|
command = ReactOnRails::Utils.running_on_windows? ? "where" : "which"
|
|
378
386
|
_stdout, _stderr, status = Open3.capture3(command, "node")
|
|
@@ -449,11 +457,53 @@ module ReactOnRails
|
|
|
449
457
|
end
|
|
450
458
|
|
|
451
459
|
def required_react_dependencies
|
|
452
|
-
{
|
|
460
|
+
deps = {
|
|
453
461
|
"react" => "React library",
|
|
454
|
-
"react-dom" => "React DOM library"
|
|
455
|
-
"@babel/preset-react" => "Babel React preset"
|
|
462
|
+
"react-dom" => "React DOM library"
|
|
456
463
|
}
|
|
464
|
+
|
|
465
|
+
deps["@babel/preset-react"] = "Babel React preset" if using_babel_transpiler?
|
|
466
|
+
deps
|
|
467
|
+
end
|
|
468
|
+
|
|
469
|
+
def using_babel_transpiler?
|
|
470
|
+
transpiler = detected_javascript_transpiler
|
|
471
|
+
return true if transpiler.nil?
|
|
472
|
+
|
|
473
|
+
transpiler == "babel"
|
|
474
|
+
end
|
|
475
|
+
|
|
476
|
+
def detected_javascript_transpiler
|
|
477
|
+
config = parsed_shakapacker_config
|
|
478
|
+
unless config
|
|
479
|
+
if File.exist?("config/shakapacker.yml")
|
|
480
|
+
add_info("ℹ️ Unable to parse config/shakapacker.yml — defaulting to Babel assumption")
|
|
481
|
+
end
|
|
482
|
+
return nil
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
rails_env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
486
|
+
env_config = config[rails_env] || {}
|
|
487
|
+
default_config = config["default"] || {}
|
|
488
|
+
transpiler = env_config["javascript_transpiler"] || default_config["javascript_transpiler"]
|
|
489
|
+
normalize_transpiler_value(transpiler)
|
|
490
|
+
end
|
|
491
|
+
|
|
492
|
+
def parsed_shakapacker_config
|
|
493
|
+
shakapacker_config_path = "config/shakapacker.yml"
|
|
494
|
+
return nil unless File.exist?(shakapacker_config_path)
|
|
495
|
+
|
|
496
|
+
raw_content = File.read(shakapacker_config_path)
|
|
497
|
+
rendered_content = ERB.new(raw_content).result
|
|
498
|
+
parsed = YAML.safe_load(rendered_content, aliases: true)
|
|
499
|
+
parsed.is_a?(Hash) ? parsed : nil
|
|
500
|
+
rescue StandardError, ScriptError
|
|
501
|
+
nil
|
|
502
|
+
end
|
|
503
|
+
|
|
504
|
+
def normalize_transpiler_value(transpiler)
|
|
505
|
+
normalized = transpiler.to_s.strip.downcase
|
|
506
|
+
normalized.empty? ? nil : normalized
|
|
457
507
|
end
|
|
458
508
|
|
|
459
509
|
def additional_build_dependencies
|
|
@@ -468,10 +518,10 @@ module ReactOnRails
|
|
|
468
518
|
}
|
|
469
519
|
end
|
|
470
520
|
|
|
471
|
-
# rubocop:disable Metrics/CyclomaticComplexity
|
|
521
|
+
# rubocop:disable Metrics/CyclomaticComplexity
|
|
472
522
|
def check_build_dependencies(package_json)
|
|
473
523
|
build_deps = additional_build_dependencies
|
|
474
|
-
all_deps = package_json["dependencies"]
|
|
524
|
+
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
475
525
|
|
|
476
526
|
present_deps = []
|
|
477
527
|
missing_deps = []
|
|
@@ -496,7 +546,7 @@ module ReactOnRails
|
|
|
496
546
|
suffix = missing_deps.length > 3 ? "..." : ""
|
|
497
547
|
add_info("ℹ️ Optional build dependencies: #{short_list}#{suffix}")
|
|
498
548
|
end
|
|
499
|
-
# rubocop:enable Metrics/CyclomaticComplexity
|
|
549
|
+
# rubocop:enable Metrics/CyclomaticComplexity
|
|
500
550
|
|
|
501
551
|
def parse_package_json
|
|
502
552
|
JSON.parse(File.read("package.json"))
|
|
@@ -506,12 +556,12 @@ module ReactOnRails
|
|
|
506
556
|
end
|
|
507
557
|
|
|
508
558
|
def find_missing_dependencies(package_json, required_deps)
|
|
509
|
-
all_deps = package_json["dependencies"]
|
|
559
|
+
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
510
560
|
required_deps.keys.reject { |dep| all_deps[dep] }
|
|
511
561
|
end
|
|
512
562
|
|
|
513
563
|
def report_dependency_status(required_deps, missing_deps, package_json)
|
|
514
|
-
all_deps = package_json["dependencies"]
|
|
564
|
+
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
515
565
|
|
|
516
566
|
required_deps.each do |dep, description|
|
|
517
567
|
add_success("✅ #{description} (#{dep}) is installed") if all_deps[dep]
|
|
@@ -572,7 +622,7 @@ module ReactOnRails
|
|
|
572
622
|
end
|
|
573
623
|
|
|
574
624
|
def report_dependency_versions(package_json)
|
|
575
|
-
all_deps = package_json["dependencies"]
|
|
625
|
+
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
576
626
|
|
|
577
627
|
react_version = all_deps["react"]
|
|
578
628
|
react_dom_version = all_deps["react-dom"]
|
|
@@ -643,7 +693,7 @@ module ReactOnRails
|
|
|
643
693
|
|
|
644
694
|
begin
|
|
645
695
|
package_json = JSON.parse(File.read("package.json"))
|
|
646
|
-
all_deps = package_json["dependencies"]
|
|
696
|
+
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
647
697
|
|
|
648
698
|
webpack_version = all_deps["webpack"]
|
|
649
699
|
add_info("📦 Webpack version: #{webpack_version}") if webpack_version
|
|
@@ -1,19 +1,278 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "date"
|
|
3
4
|
require "English"
|
|
4
5
|
require "bundler"
|
|
6
|
+
require "open3"
|
|
5
7
|
require_relative "task_helpers"
|
|
6
8
|
|
|
7
9
|
CLAUDE_CODE_TIP = <<~TIP
|
|
8
10
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
|
9
|
-
│ TIP: This task
|
|
10
|
-
│ For full
|
|
11
|
+
│ TIP: This task adds version headers and links, not changelog entry text. │
|
|
12
|
+
│ For full commit analysis + entry writing, run /update-changelog in Claude. │
|
|
11
13
|
│ │
|
|
12
14
|
│ After running this task, manually add entries under the new header: │
|
|
13
15
|
│ #### Fixed / #### Added / #### Changed / etc. │
|
|
14
16
|
└─────────────────────────────────────────────────────────────────────────────┘
|
|
15
17
|
TIP
|
|
16
18
|
|
|
19
|
+
def monorepo_root_for_changelog
|
|
20
|
+
File.expand_path("../..", __dir__)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def prerelease_version?(version)
|
|
24
|
+
version.to_s.match?(/\.(test|beta|alpha|rc|pre)\./i)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def normalize_version_string(version_or_tag)
|
|
28
|
+
version = version_or_tag.to_s.strip
|
|
29
|
+
version = version.delete_prefix("v")
|
|
30
|
+
version = version.sub(/-(test|beta|alpha|rc|pre)\./i, '.\1.')
|
|
31
|
+
|
|
32
|
+
unless version.match?(/\A\d+\.\d+\.\d+(\.(test|beta|alpha|rc|pre)\.\d+)?\z/i)
|
|
33
|
+
abort "Failed to parse version from #{version_or_tag.inspect}. Expected format like 16.4.0 or 16.4.0.rc.1."
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
version.downcase
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def parse_release_tag_to_version(tag)
|
|
40
|
+
version_pattern = /\d+\.\d+\.\d+(?:\.(?:test|beta|alpha|rc|pre)\.\d+)?|\d+\.\d+\.\d+-(?:test|beta|alpha|rc|pre)\.\d+/
|
|
41
|
+
tag_match = tag.to_s.strip.match(/\Av(?<version>#{version_pattern})\z/i)
|
|
42
|
+
return nil unless tag_match
|
|
43
|
+
|
|
44
|
+
normalize_version_string(tag_match[:version])
|
|
45
|
+
rescue SystemExit
|
|
46
|
+
nil
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def fetch_git_tags!(monorepo_root)
|
|
50
|
+
remotes_output, remotes_status = Open3.capture2e("git", "-C", monorepo_root, "remote")
|
|
51
|
+
abort "Failed to list git remotes.\n#{remotes_output}" unless remotes_status.success?
|
|
52
|
+
|
|
53
|
+
remote_names = remotes_output.lines.map(&:strip).reject(&:empty?)
|
|
54
|
+
return if remote_names.empty?
|
|
55
|
+
|
|
56
|
+
remote_name = remote_names.include?("origin") ? "origin" : remote_names.first
|
|
57
|
+
fetch_output, fetch_status = Open3.capture2e("git", "-C", monorepo_root, "fetch", remote_name, "--tags", "--quiet")
|
|
58
|
+
abort "Failed to fetch git tags from #{remote_name}.\n#{fetch_output}" unless fetch_status.success?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def tag_versions(monorepo_root)
|
|
62
|
+
tags_output, status = Open3.capture2e("git", "-C", monorepo_root, "tag", "-l", "v*")
|
|
63
|
+
abort "Failed to list git tags.\n#{tags_output}" unless status.success?
|
|
64
|
+
|
|
65
|
+
tags_output.lines.map(&:strip).filter_map { |tag| parse_release_tag_to_version(tag) }.uniq
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def stable_tag_versions(monorepo_root)
|
|
69
|
+
tag_versions(monorepo_root).reject { |version| prerelease_version?(version) }
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def latest_stable_tag_version(monorepo_root)
|
|
73
|
+
versions = stable_tag_versions(monorepo_root)
|
|
74
|
+
abort "Failed to compute latest stable tag: no stable v* tags found." if versions.empty?
|
|
75
|
+
|
|
76
|
+
versions.max_by { |version| Gem::Version.new(version) }
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def extract_unreleased_section(changelog)
|
|
80
|
+
lines = changelog.lines
|
|
81
|
+
start_index = lines.index { |line| line.start_with?("### [Unreleased]") }
|
|
82
|
+
abort "Failed to find '### [Unreleased]' in CHANGELOG.md" unless start_index
|
|
83
|
+
|
|
84
|
+
end_index = ((start_index + 1)...lines.length).find { |idx| lines[idx].start_with?("### [") } || lines.length
|
|
85
|
+
lines[start_index...end_index].join
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def inferred_bump_type_from_unreleased(changelog)
|
|
89
|
+
section = extract_unreleased_section(changelog)
|
|
90
|
+
return :major if section.match?(/^####\s+(?:⚠️\s*)?Breaking(?:\s+Changes?)?\b/i)
|
|
91
|
+
return :minor if section.match?(/^####\s+(Added|New\s+Features?|Features?|Enhancements?)\b/i)
|
|
92
|
+
return :patch if section.match?(/^####\s+(Fixed|Fixes|Bug\s+Fixes?|Security|Improved|Changed|Deprecated|Removed)\b/i)
|
|
93
|
+
|
|
94
|
+
:patch
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def bump_stable_version(version, bump_type)
|
|
98
|
+
match = version.match(/\A(\d+)\.(\d+)\.(\d+)\z/)
|
|
99
|
+
abort "Failed to bump version: stable version #{version.inspect} is invalid." unless match
|
|
100
|
+
|
|
101
|
+
major = match[1].to_i
|
|
102
|
+
minor = match[2].to_i
|
|
103
|
+
patch = match[3].to_i
|
|
104
|
+
|
|
105
|
+
case bump_type
|
|
106
|
+
when :major
|
|
107
|
+
"#{major + 1}.0.0"
|
|
108
|
+
when :minor
|
|
109
|
+
"#{major}.#{minor + 1}.0"
|
|
110
|
+
else
|
|
111
|
+
"#{major}.#{minor}.#{patch + 1}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def prerelease_indices_from_tags(monorepo_root, base_version, channel)
|
|
116
|
+
tags_output, status = Open3.capture2e("git", "-C", monorepo_root, "tag", "-l", "v#{base_version}*")
|
|
117
|
+
abort "Failed to list prerelease tags.\n#{tags_output}" unless status.success?
|
|
118
|
+
|
|
119
|
+
tags_output.lines.map(&:strip).filter_map do |tag|
|
|
120
|
+
normalized_version = parse_release_tag_to_version(tag)
|
|
121
|
+
match = normalized_version&.match(/\A#{Regexp.escape(base_version)}\.#{channel}\.(\d+)\z/i)
|
|
122
|
+
match&.captures&.first&.to_i
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def prerelease_indices_from_changelog(changelog, base_version, channel)
|
|
127
|
+
changelog.scan(/^### \[#{Regexp.escape(base_version)}\.#{channel}\.(\d+)\]/i).flatten.map(&:to_i)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def parse_changelog_sections(changelog)
|
|
131
|
+
lines = changelog.lines
|
|
132
|
+
headers = []
|
|
133
|
+
lines.each_with_index do |line, index|
|
|
134
|
+
match = line.match(/^### \[([^\]]+)\].*$/)
|
|
135
|
+
headers << { index: index, version: match[1], header: line } if match
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
return { prefix: changelog, sections: [] } if headers.empty?
|
|
139
|
+
|
|
140
|
+
prefix = lines[0...headers.first[:index]].join
|
|
141
|
+
sections = headers.each_with_index.map do |header, section_index|
|
|
142
|
+
section_end = if section_index + 1 < headers.length
|
|
143
|
+
headers[section_index + 1][:index]
|
|
144
|
+
else
|
|
145
|
+
lines.length
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
{
|
|
149
|
+
version: header[:version],
|
|
150
|
+
header: header[:header],
|
|
151
|
+
body: lines[(header[:index] + 1)...section_end].join
|
|
152
|
+
}
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
{ prefix: prefix, sections: sections }
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def render_changelog_sections(prefix, sections)
|
|
159
|
+
"#{prefix}#{sections.map { |section| "#{section[:header]}#{section[:body]}" }.join}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def changelog_versions(changelog)
|
|
163
|
+
parse_changelog_sections(changelog)[:sections]
|
|
164
|
+
.map { |section| section[:version] }
|
|
165
|
+
.reject { |version| version == "Unreleased" }
|
|
166
|
+
.map { |version| normalize_version_string(version) }
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def prerelease_base_version(version)
|
|
170
|
+
version.to_s.sub(/\.(test|beta|alpha|rc|pre)\.\d+\z/i, "")
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def active_prerelease_base_version(monorepo_root, changelog)
|
|
174
|
+
latest_stable = latest_stable_tag_version(monorepo_root)
|
|
175
|
+
prerelease_bases = (tag_versions(monorepo_root) + changelog_versions(changelog))
|
|
176
|
+
.uniq
|
|
177
|
+
.select { |version| prerelease_version?(version) }
|
|
178
|
+
.map { |version| prerelease_base_version(version) }
|
|
179
|
+
.select do |base_version|
|
|
180
|
+
Gem::Version.new(base_version) > Gem::Version.new(latest_stable)
|
|
181
|
+
end
|
|
182
|
+
.uniq
|
|
183
|
+
|
|
184
|
+
prerelease_bases.max_by { |base_version| Gem::Version.new(base_version) }
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def collapse_prerelease_series(changelog, base_version)
|
|
188
|
+
%w[test beta alpha rc pre].reduce(changelog) do |current_changelog, channel|
|
|
189
|
+
collapse_prerelease_sections(current_changelog, base_version, channel)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def prepare_changelog_for_auto_version(changelog, monorepo_root)
|
|
194
|
+
active_base_version = active_prerelease_base_version(monorepo_root, changelog)
|
|
195
|
+
return changelog unless active_base_version
|
|
196
|
+
|
|
197
|
+
collapse_prerelease_series(changelog, active_base_version)
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def changelog_section_blocks(section_body)
|
|
201
|
+
block_lines = []
|
|
202
|
+
blocks = []
|
|
203
|
+
|
|
204
|
+
section_body.lines.each do |line|
|
|
205
|
+
normalized_line = line.rstrip
|
|
206
|
+
if normalized_line.match?(/^####+\s+/) && !block_lines.empty?
|
|
207
|
+
blocks << normalize_changelog_block(block_lines)
|
|
208
|
+
block_lines = [normalized_line]
|
|
209
|
+
else
|
|
210
|
+
block_lines << normalized_line
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
blocks << normalize_changelog_block(block_lines) unless block_lines.empty?
|
|
215
|
+
blocks.reject(&:empty?)
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def normalize_changelog_block(lines)
|
|
219
|
+
normalized_lines = lines.map(&:rstrip)
|
|
220
|
+
normalized_lines.shift while normalized_lines.first == ""
|
|
221
|
+
normalized_lines.pop while normalized_lines.last == ""
|
|
222
|
+
normalized_lines.join("\n")
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
226
|
+
def collapse_prerelease_sections(changelog, base_version, channel)
|
|
227
|
+
parsed = parse_changelog_sections(changelog)
|
|
228
|
+
sections = parsed[:sections]
|
|
229
|
+
unreleased_section = sections.find { |section| section[:version] == "Unreleased" }
|
|
230
|
+
return changelog unless unreleased_section
|
|
231
|
+
|
|
232
|
+
target_regex = /\A#{Regexp.escape(base_version)}\.#{channel}\.\d+\z/i
|
|
233
|
+
matching_sections = sections.select { |section| section[:version].match?(target_regex) }
|
|
234
|
+
return changelog if matching_sections.empty?
|
|
235
|
+
|
|
236
|
+
merged_body = matching_sections
|
|
237
|
+
.flat_map { |section| changelog_section_blocks(section[:body]) }
|
|
238
|
+
.uniq
|
|
239
|
+
.join("\n\n")
|
|
240
|
+
.strip
|
|
241
|
+
sections.reject! { |section| section[:version].match?(target_regex) }
|
|
242
|
+
|
|
243
|
+
unless merged_body.empty?
|
|
244
|
+
unreleased_body = unreleased_section[:body].rstrip
|
|
245
|
+
unreleased_section[:body] = if unreleased_body.empty?
|
|
246
|
+
"#{merged_body}\n"
|
|
247
|
+
else
|
|
248
|
+
"#{unreleased_body}\n\n#{merged_body}\n"
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
render_changelog_sections(parsed[:prefix], sections)
|
|
253
|
+
end
|
|
254
|
+
# rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
|
255
|
+
|
|
256
|
+
def compute_auto_version(changelog, mode, monorepo_root, changelog_for_bump: changelog)
|
|
257
|
+
bump_type = inferred_bump_type_from_unreleased(changelog_for_bump)
|
|
258
|
+
latest_stable = latest_stable_tag_version(monorepo_root)
|
|
259
|
+
base_version = bump_stable_version(latest_stable, bump_type)
|
|
260
|
+
|
|
261
|
+
return base_version if mode == "release"
|
|
262
|
+
|
|
263
|
+
indices = prerelease_indices_from_tags(monorepo_root, base_version, mode) +
|
|
264
|
+
prerelease_indices_from_changelog(changelog, base_version, mode)
|
|
265
|
+
next_index = indices.empty? ? 0 : indices.max + 1
|
|
266
|
+
"#{base_version}.#{mode}.#{next_index}"
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def fetch_git_tag_date(monorepo_root, git_tag)
|
|
270
|
+
output, status = Open3.capture2e("git", "-C", monorepo_root, "show", "-s", "--format=%cs", git_tag)
|
|
271
|
+
return nil unless status.success?
|
|
272
|
+
|
|
273
|
+
output.split("\n").last&.strip
|
|
274
|
+
end
|
|
275
|
+
|
|
17
276
|
# Update the compare links at the bottom of the changelog
|
|
18
277
|
# version: version string without 'v' prefix (e.g., "16.2.0.beta.20")
|
|
19
278
|
# anchor: markdown anchor (e.g., "[16.2.0.beta.20]")
|
|
@@ -39,26 +298,64 @@ def insert_version_header(changelog, anchor, tag_date)
|
|
|
39
298
|
false
|
|
40
299
|
end
|
|
41
300
|
|
|
42
|
-
desc "Updates CHANGELOG.md inserting
|
|
43
|
-
Argument:
|
|
301
|
+
desc "Updates CHANGELOG.md by inserting a version header and compare links.
|
|
302
|
+
Argument: Mode (`release`, `rc`, `beta`) or explicit git tag/version.
|
|
303
|
+
|
|
304
|
+
Modes:
|
|
305
|
+
- release: auto-compute next stable version from Unreleased section headings
|
|
306
|
+
- rc: auto-compute next RC version and collapse prior RC sections of same base version
|
|
307
|
+
- beta: auto-compute next beta version and collapse prior beta sections of same base version
|
|
308
|
+
|
|
309
|
+
Explicit argument examples:
|
|
310
|
+
- v16.4.0.rc.6
|
|
311
|
+
- 16.4.0.rc.6
|
|
312
|
+
|
|
313
|
+
No argument: use latest git tag.
|
|
44
314
|
TIP: Use /update-changelog in Claude Code for full automation."
|
|
45
315
|
|
|
46
|
-
|
|
316
|
+
# rubocop:disable Metrics/BlockLength
|
|
317
|
+
task :update_changelog, %i[mode_or_tag] do |_, args|
|
|
47
318
|
puts CLAUDE_CODE_TIP
|
|
48
319
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
320
|
+
monorepo_root = monorepo_root_for_changelog
|
|
321
|
+
changelog_path = File.join(monorepo_root, "CHANGELOG.md")
|
|
322
|
+
changelog = File.read(changelog_path)
|
|
323
|
+
input = args[:mode_or_tag].to_s.strip
|
|
324
|
+
auto_mode = %w[release rc beta].find { |mode| mode == input.downcase }
|
|
54
325
|
|
|
55
|
-
if
|
|
56
|
-
|
|
57
|
-
|
|
326
|
+
if auto_mode
|
|
327
|
+
fetch_git_tags!(monorepo_root)
|
|
328
|
+
prepared_changelog = prepare_changelog_for_auto_version(changelog, monorepo_root)
|
|
329
|
+
changelog_version = compute_auto_version(
|
|
330
|
+
changelog,
|
|
331
|
+
auto_mode,
|
|
332
|
+
monorepo_root,
|
|
333
|
+
changelog_for_bump: prepared_changelog
|
|
334
|
+
)
|
|
335
|
+
changelog = prepared_changelog
|
|
336
|
+
tag_date = Date.today.strftime("%Y-%m-%d")
|
|
337
|
+
puts "Auto-computed #{auto_mode} version: #{changelog_version}"
|
|
338
|
+
else
|
|
339
|
+
git_tag = if input.empty?
|
|
340
|
+
git_output, git_status = Open3.capture2e("git", "-C", monorepo_root, "describe", "--tags", "--abbrev=0")
|
|
341
|
+
abort "Failed to get latest git tag.\n#{git_output}" unless git_status.success?
|
|
342
|
+
git_output.strip
|
|
343
|
+
else
|
|
344
|
+
input
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
changelog_version = normalize_version_string(git_tag)
|
|
348
|
+
tag_candidates = [git_tag, git_tag.start_with?("v") ? git_tag : "v#{git_tag}", "v#{changelog_version}"].uniq
|
|
349
|
+
tag_date = tag_candidates.filter_map { |candidate| fetch_git_tag_date(monorepo_root, candidate) }.first ||
|
|
350
|
+
Date.today.strftime("%Y-%m-%d")
|
|
58
351
|
end
|
|
59
352
|
|
|
60
|
-
|
|
61
|
-
|
|
353
|
+
anchor = "[#{changelog_version}]"
|
|
354
|
+
header = "### #{anchor}"
|
|
355
|
+
if changelog.include?(header)
|
|
356
|
+
puts "Version #{changelog_version} is already documented in CHANGELOG.md"
|
|
357
|
+
next
|
|
358
|
+
end
|
|
62
359
|
|
|
63
360
|
unless insert_version_header(changelog, anchor, tag_date)
|
|
64
361
|
abort("Failed to insert version header: could not find '### [Unreleased]' " \
|
|
@@ -67,7 +364,8 @@ task :update_changelog, %i[tag] do |_, args|
|
|
|
67
364
|
|
|
68
365
|
update_changelog_links(changelog, changelog_version, anchor)
|
|
69
366
|
|
|
70
|
-
File.write(
|
|
71
|
-
puts "Updated CHANGELOG.md with version header for #{
|
|
367
|
+
File.write(changelog_path, changelog)
|
|
368
|
+
puts "Updated CHANGELOG.md with version header for #{changelog_version}"
|
|
72
369
|
puts "NOTE: You still need to write the changelog entries manually."
|
|
73
370
|
end
|
|
371
|
+
# rubocop:enable Metrics/BlockLength
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: react_on_rails
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 16.4.0.rc.
|
|
4
|
+
version: 16.4.0.rc.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Justin Gordon
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-03-
|
|
11
|
+
date: 2026-03-11 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: addressable
|