Skip to content

Commit 3104127

Browse files
authored
Fix release script after pnpm monorepo restructure (#2173)
## Summary - Adds root Rakefile to enable running rake tasks from monorepo root (fixes "No Rakefile found" error) - Fixes path references in release.rake that were incorrectly using `gem_root` instead of `monorepo_root` Fixes #2158 ## Details After the recent pnpm/directory restructure: 1. Running `rake release[...]` from the monorepo root failed because there was no Rakefile at the root level 2. The release.rake script had incorrect paths for package.json files and npm publish directories ### Changes: 1. **New Rakefile at monorepo root**: Delegates to `react_on_rails/Rakefile` and loads all `*.rake` files from `react_on_rails/rakelib/` (same pattern as root Gemfile) 2. **Fixed release.rake paths**: Changed `gem_root` to `monorepo_root` for: - package.json files (root, packages/react-on-rails, packages/react-on-rails-pro) - react_on_rails_pro/spec/dummy path - pnpm publish directories for npm packages ## Test plan - [x] Verified `rake -T` shows release task from monorepo root - [x] Tested `rake release[16.2.0.beta.13,true,npm,skip_push]` dry-run completes successfully - [x] All package.json files at correct paths are updated - [x] RuboCop passes on all changed files
1 parent cb426de commit 3104127

File tree

9 files changed

+328
-121
lines changed

9 files changed

+328
-121
lines changed

Rakefile

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# frozen_string_literal: true
2+
3+
# Root Rakefile for monorepo development
4+
#
5+
# This file enables running rake tasks from the monorepo root directory.
6+
# It loads:
7+
# 1. Monorepo-level tasks from ./rakelib/ (e.g., release task for both gems)
8+
# 2. The react_on_rails gem's Rakefile
9+
# 3. The react_on_rails gem's rakelib tasks
10+
#
11+
# Usage: bundle exec rake -T (from monorepo root)
12+
#
13+
# Note: This is for development only. When the gem is installed in a Rails app,
14+
# Rails::Engine handles rake task loading automatically from lib/tasks/.
15+
16+
# Define gem_root helper for use by rake tasks
17+
def gem_root
18+
File.expand_path("react_on_rails", __dir__)
19+
end
20+
21+
# Load the open-source gem's Rakefile
22+
load File.expand_path("Rakefile", gem_root)
23+
24+
# Load all rake tasks from the gem's rakelib directory
25+
# Rake only auto-loads from ./rakelib in the current working directory,
26+
# so we must explicitly load from the subdirectory.
27+
Dir[File.join(gem_root, "rakelib", "*.rake")].each { |rake_file| load rake_file }
28+
29+
# NOTE: Monorepo-level rake tasks from ./rakelib/ are auto-loaded by Rake.
30+
# Do NOT explicitly load them here, as that would cause tasks to be defined twice
31+
# and their bodies would run twice (Rake appends duplicate task definitions).
Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ Version argument can be:
6161
- Explicit version: '16.2.0'
6262
- Pre-release version: '16.2.0.beta.1' (rubygem format with dots, converted to 16.2.0-beta.1 for NPM)
6363
64+
Note: Pre-release versions (containing .test., .beta., .alpha., .rc., or .pre.) automatically
65+
skip git branch checks, allowing releases from non-master branches.
66+
6467
This will update and release:
6568
PUBLIC (npmjs.org + rubygems.org):
6669
- react-on-rails NPM package
@@ -123,7 +126,9 @@ task :release, %i[version dry_run registry skip_push] do |_t, args|
123126

124127
skip_push = skip_push_value == "skip_push"
125128

129+
# Detect if this is a test/pre-release version (contains test, beta, alpha, rc, etc.)
126130
version_input = args_hash.fetch(:version, "")
131+
is_prerelease = version_input.match?(/\.(test|beta|alpha|rc|pre)\./i)
127132

128133
if version_input.strip.empty?
129134
raise ArgumentError,
@@ -168,7 +173,6 @@ task :release, %i[version dry_run registry skip_push] do |_t, args|
168173

169174
# Update react_on_rails_pro gem version to match
170175
puts "\nUpdating react_on_rails_pro gem version to #{actual_gem_version}..."
171-
pro_gem_root = File.join(monorepo_root, "react_on_rails_pro")
172176
pro_version_file = File.join(pro_gem_root, "lib", "react_on_rails_pro", "version.rb")
173177
pro_version_content = File.read(pro_version_file)
174178
# We use gsub instead of `gem bump` here because the git tree is already dirty
@@ -181,12 +185,12 @@ task :release, %i[version dry_run registry skip_push] do |_t, args|
181185

182186
puts "\nUpdating package.json files to version #{actual_npm_version}..."
183187

184-
# Update all package.json files
188+
# Update all package.json files (only publishable packages)
185189
package_json_files = [
186-
File.join(gem_root, "package.json"),
187-
File.join(gem_root, "packages", "react-on-rails", "package.json"),
188-
File.join(gem_root, "packages", "react-on-rails-pro", "package.json"),
189-
File.join(gem_root, "react_on_rails_pro", "package.json")
190+
File.join(monorepo_root, "package.json"),
191+
File.join(monorepo_root, "packages", "react-on-rails", "package.json"),
192+
File.join(monorepo_root, "packages", "react-on-rails-pro", "package.json"),
193+
File.join(monorepo_root, "packages", "react-on-rails-pro-node-renderer", "package.json")
190194
]
191195

192196
package_json_files.each do |file|
@@ -209,7 +213,6 @@ task :release, %i[version dry_run registry skip_push] do |_t, args|
209213
# Update all Gemfile.lock files
210214
unbundled_sh_in_dir(gem_root, "bundle install#{bundle_quiet_flag}")
211215
unbundled_sh_in_dir(dummy_app_dir, "bundle install#{bundle_quiet_flag}")
212-
pro_dummy_app_dir = File.join(gem_root, "react_on_rails_pro", "spec", "dummy")
213216
unbundled_sh_in_dir(pro_dummy_app_dir, "bundle install#{bundle_quiet_flag}") if Dir.exist?(pro_dummy_app_dir)
214217
unbundled_sh_in_dir(pro_gem_root, "bundle install#{bundle_quiet_flag}")
215218

@@ -232,10 +235,23 @@ task :release, %i[version dry_run registry skip_push] do |_t, args|
232235
unless is_dry_run
233236
# Commit all version changes (skip git hooks to save time)
234237
sh_in_dir(monorepo_root, "LEFTHOOK=0 git add -A")
235-
sh_in_dir(monorepo_root, "LEFTHOOK=0 git commit -m 'Bump version to #{actual_gem_version}'")
236238

237-
# Create git tag
238-
sh_in_dir(monorepo_root, "git tag v#{actual_gem_version}")
239+
# Only commit if there are staged changes (version might already be set)
240+
git_status = `cd #{monorepo_root} && git diff --cached --quiet; echo $?`.strip
241+
if git_status == "0"
242+
puts "No version changes to commit (version already set to #{actual_gem_version})"
243+
else
244+
sh_in_dir(monorepo_root, "LEFTHOOK=0 git commit -m 'Bump version to #{actual_gem_version}'")
245+
end
246+
247+
# Create git tag (skip if it already exists)
248+
tag_name = "v#{actual_gem_version}"
249+
tag_exists = system("cd #{monorepo_root} && git rev-parse #{tag_name} >/dev/null 2>&1")
250+
if tag_exists
251+
puts "Git tag #{tag_name} already exists, skipping tag creation"
252+
else
253+
sh_in_dir(monorepo_root, "git tag #{tag_name}")
254+
end
239255

240256
# Push commits and tags (skip git hooks)
241257
unless skip_push
@@ -256,25 +272,30 @@ task :release, %i[version dry_run registry skip_push] do |_t, args|
256272
puts "TIP: Set NPM_OTP environment variable to avoid repeated prompts."
257273
end
258274

275+
# For pre-release versions, skip git branch checks (allows releasing from non-master branches)
276+
if is_prerelease
277+
npm_publish_args += " --no-git-checks"
278+
puts "Pre-release version detected - skipping git branch checks for NPM publish"
279+
end
280+
259281
# Publish react-on-rails NPM package
260282
puts "\nPublishing react-on-rails@#{actual_npm_version}..."
261-
sh_in_dir(File.join(gem_root, "packages", "react-on-rails"), "pnpm publish #{npm_publish_args}")
283+
sh_in_dir(File.join(monorepo_root, "packages", "react-on-rails"), "pnpm publish #{npm_publish_args}")
262284

263285
# Publish react-on-rails-pro NPM package
264286
puts "\nPublishing react-on-rails-pro@#{actual_npm_version}..."
265-
sh_in_dir(File.join(gem_root, "packages", "react-on-rails-pro"), "pnpm publish #{npm_publish_args}")
287+
sh_in_dir(File.join(monorepo_root, "packages", "react-on-rails-pro"), "pnpm publish #{npm_publish_args}")
266288

267289
# Publish node-renderer NPM package (PUBLIC on npmjs.org)
268290
puts "\n#{'=' * 80}"
269291
puts "Publishing PUBLIC node-renderer to #{use_verdaccio ? 'Verdaccio (local)' : 'npmjs.org'}..."
270292
puts "=" * 80
271293

272294
# Publish react-on-rails-pro-node-renderer NPM package
273-
# Note: Uses plain `pnpm publish` because the node-renderer
274-
# package.json is in react_on_rails_pro/ which is not defined as a workspace
275295
node_renderer_name = "react-on-rails-pro-node-renderer"
296+
node_renderer_dir = File.join(monorepo_root, "packages", "react-on-rails-pro-node-renderer")
276297
puts "\nPublishing #{node_renderer_name}@#{actual_npm_version}..."
277-
sh_in_dir(pro_gem_root, "pnpm publish --no-git-checks #{npm_publish_args}")
298+
sh_in_dir(node_renderer_dir, "pnpm publish #{npm_publish_args}")
278299

279300
if use_verdaccio
280301
puts "\nSkipping Ruby gem publication (Verdaccio is NPM-only)"
@@ -320,7 +341,7 @@ task :release, %i[version dry_run registry skip_push] do |_t, args|
320341
puts " - package.json (root)"
321342
puts " - packages/react-on-rails/package.json"
322343
puts " - packages/react-on-rails-pro/package.json (version + dependency)"
323-
puts " - react_on_rails_pro/package.json (node-renderer)"
344+
puts " - packages/react-on-rails-pro-node-renderer/package.json"
324345
puts " - Gemfile.lock files (root, dummy apps, pro)"
325346
puts "\nAuto-synced (no write needed):"
326347
puts " - react_on_rails_pro/react_on_rails_pro.gemspec (uses ReactOnRails::VERSION)"

rakelib/task_helpers.rb

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
# frozen_string_literal: true
2+
3+
require "English"
4+
5+
module ReactOnRails
6+
module TaskHelpers
7+
# Returns the root folder of the monorepo
8+
def monorepo_root
9+
File.expand_path("..", __dir__)
10+
end
11+
12+
# Returns the root folder of the react_on_rails gem
13+
def gem_root
14+
File.join(monorepo_root, "react_on_rails")
15+
end
16+
17+
# Returns the root folder of the react_on_rails_pro gem
18+
def pro_gem_root
19+
File.join(monorepo_root, "react_on_rails_pro")
20+
end
21+
22+
# Returns the folder where examples are located
23+
def examples_dir
24+
File.join(monorepo_root, "gen-examples", "examples")
25+
end
26+
27+
def dummy_app_dir
28+
File.join(gem_root, "spec/dummy")
29+
end
30+
31+
def pro_dummy_app_dir
32+
File.join(pro_gem_root, "spec", "dummy")
33+
end
34+
35+
# Executes a string or an array of strings in a shell in the given directory in an unbundled environment
36+
def sh_in_dir(dir, *shell_commands)
37+
shell_commands.flatten.each { |shell_command| sh %(cd #{dir} && #{shell_command.strip}) }
38+
end
39+
40+
# Executes a string or an array of strings in a shell in the given directory
41+
def unbundled_sh_in_dir(dir, *shell_commands)
42+
Dir.chdir(dir) do
43+
# Without `with_unbundled_env`, running bundle in the child directories won't correctly
44+
# update the Gemfile.lock
45+
Bundler.with_unbundled_env do
46+
shell_commands.flatten.each do |shell_command|
47+
sh(shell_command.strip)
48+
end
49+
end
50+
end
51+
end
52+
53+
def bundle_install_in(dir)
54+
required_version = detect_bundler_ruby_version(dir)
55+
56+
if required_version && required_version != RUBY_VERSION
57+
puts " Switching Ruby version: #{RUBY_VERSION}#{required_version}"
58+
# Run version switch and bundle install in the same shell context
59+
bundle_install_with_ruby_version(dir, required_version)
60+
else
61+
unbundled_sh_in_dir(dir, "bundle install")
62+
end
63+
end
64+
65+
private
66+
67+
# Runs bundle install with the specified Ruby version in the same shell context
68+
def bundle_install_with_ruby_version(dir, version)
69+
version_manager = ENV.fetch("RUBY_VERSION_MANAGER", "rvm")
70+
71+
command = case version_manager
72+
when "rvm"
73+
"rvm #{version} do bundle install"
74+
when "rbenv"
75+
"RBENV_VERSION=#{version} bundle install"
76+
when "asdf"
77+
"asdf shell ruby #{version} && bundle install"
78+
else
79+
# TODO: add support for chruby
80+
puts " ⚠️ Unknown RUBY_VERSION_MANAGER: #{version_manager}"
81+
puts " Supported values: rvm, rbenv, asdf"
82+
raise "Ruby version #{version} required. Current: #{RUBY_VERSION}"
83+
end
84+
85+
unbundled_sh_in_dir(dir, command)
86+
rescue StandardError => e
87+
puts " ⚠️ Failed to switch Ruby version and run bundle install: #{e.message}"
88+
puts " Please manually switch to Ruby #{version} and try again"
89+
raise
90+
end
91+
92+
# Detects the required Ruby version using Bundler
93+
def detect_bundler_ruby_version(dir)
94+
output = nil
95+
exit_status = nil
96+
97+
# Run in unbundled environment to avoid conflicts with parent Bundler context
98+
Bundler.with_unbundled_env do
99+
Dir.chdir(dir) do
100+
output = `bundle platform --ruby 2>&1`
101+
exit_status = $CHILD_STATUS.exitstatus
102+
end
103+
end
104+
105+
unless exit_status.zero?
106+
puts " ⚠️ Failed to detect Ruby version in #{dir}"
107+
puts " Error: #{output.strip}" unless output.strip.empty?
108+
return nil
109+
end
110+
111+
# Parse "ruby 3.3.7" or "ruby 3.3.7-rc1" or "ruby 3.4.0-preview1"
112+
# Regex matches: digits.dots followed by optional -prerelease
113+
match = output.strip.match(/ruby\s+([\d.]+(?:-[a-zA-Z0-9.]+)?)/)
114+
match ? match[1] : nil
115+
rescue StandardError => e
116+
puts " ⚠️ Error detecting Ruby version: #{e.message}"
117+
nil
118+
end
119+
120+
public
121+
122+
def bundle_install_in_no_turbolinks(dir)
123+
sh_in_dir(dir, "DISABLE_TURBOLINKS=TRUE bundle install")
124+
end
125+
126+
# Runs bundle exec using that directory's Gemfile
127+
def bundle_exec(dir: nil, args: nil, env_vars: "")
128+
sh_in_dir(dir, "#{env_vars} bundle exec #{args}")
129+
end
130+
131+
def generators_source_dir
132+
File.join(gem_root, "lib/generators/react_on_rails")
133+
end
134+
135+
def symbolize_keys(hash)
136+
hash.each_with_object({}) do |(key, value), new_hash|
137+
new_key = key.is_a?(String) ? key.to_sym : key
138+
new_value = value.is_a?(Hash) ? symbolize_keys(value) : value
139+
new_hash[new_key] = new_value
140+
end
141+
end
142+
end
143+
end

react_on_rails/rakelib/example_type.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "rake"
44

55
require_relative "task_helpers"
6+
require_relative File.join(__dir__, "..", "lib", "react_on_rails", "utils")
67

78
# Defines the ExampleType class, where each object represents a unique type of example
89
# app that we can generate.

react_on_rails/rakelib/run_rspec.rake

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
require "coveralls/rake/task" if ENV["USE_COVERALLS"] == "TRUE"
44

55
require "pathname"
6+
require "yaml"
67

78
require_relative "task_helpers"
89
require_relative "example_type"
@@ -115,7 +116,7 @@ task :js_tests do
115116
sh "pnpm run test"
116117
end
117118

118-
msg = <<-DESC.strip_heredoc
119+
msg = <<~DESC
119120
Runs all tests, run `rake -D run_rspec` to see all available test options.
120121
"rake run_rspec:example_basic" is a good way to run only one generator test.
121122
DESC

react_on_rails/spec/dummy/Gemfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ GEM
345345
rubyzip (>= 1.2.2, < 3.0)
346346
websocket (~> 1.0)
347347
semantic_range (3.1.0)
348-
shakapacker (9.3.0)
348+
shakapacker (9.4.0)
349349
activesupport (>= 5.2)
350350
package_json
351351
rack-proxy (>= 0.6.1)
@@ -461,7 +461,7 @@ DEPENDENCIES
461461
sass-rails (~> 6.0)
462462
sdoc
463463
selenium-webdriver (= 4.9.0)
464-
shakapacker (= 9.3.0)
464+
shakapacker (= 9.4.0)
465465
spring (~> 4.0)
466466
sprockets (~> 4.0)
467467
sqlite3 (~> 1.6)

0 commit comments

Comments
 (0)