Skip to content

Commit 173f8f8

Browse files
justin808claude
andcommitted
Add gemspec validation and documentation
Adds comprehensive gemspec file exclusion validation to prevent Pro code from leaking into the MIT gem. This addresses the critical bug where the MIT gem was accidentally including Pro files. Changes: 1. **New RSpec test** (spec/react_on_rails/gemspec_file_inclusion_spec.rb): - Validates MIT gem excludes all Pro files - Validates Pro gem includes all necessary Pro files - Prevents cross-contamination between packages - Tests run automatically in CI 2. **Updated MIT gemspec** (react_on_rails.gemspec): - Added explicit exclusions for CHANGELOG_PRO.md - Added explicit exclusion for react_on_rails_pro.gemspec - These files were being included in MIT gem due to default glob patterns 3. **Documentation** (CONTRIBUTING.md): - Added section on gemspec file management - Explains whitelisting approach for Pro package - Provides troubleshooting guidance - Documents how to validate changes 4. **GitHub Actions documentation** (.github/read-me.md): - Documents REACT_ON_RAILS_PRO_LICENSE_V2 secret requirement - Lists workflows that need this secret - Provides setup instructions for new contributors These changes ensure that: - ✅ MIT gem never includes Pro code (licensing violation) - ✅ Pro gem includes all necessary Pro files - ✅ CI automatically validates file inclusion - ✅ Contributors have clear guidance on gemspec management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent f827d43 commit 173f8f8

File tree

4 files changed

+235
-1
lines changed

4 files changed

+235
-1
lines changed

.github/read-me.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,36 @@ Most workflows use minimal permissions. The comment-triggered workflows require:
105105
- `pull-requests: write` - To post comments and reactions
106106
- `actions: write` - To trigger other workflows
107107

108+
## Required Secrets for Pro Workflows
109+
110+
Workflows that test React on Rails Pro features require the following repository secret:
111+
112+
### `REACT_ON_RAILS_PRO_LICENSE_V2`
113+
114+
This secret contains the Pro license key needed to run Pro features during CI. It must be configured as a repository secret in GitHub Actions.
115+
116+
**Workflows that require this secret:**
117+
118+
- `examples.yml` - When generating/testing examples with Pro features
119+
- `integration-tests.yml` - When running integration tests that may use Pro features
120+
- Any workflow that runs example apps or tests that might invoke Pro functionality
121+
122+
**Setting up the secret:**
123+
124+
1. Go to repository Settings → Secrets and variables → Actions
125+
2. Add a new secret named `REACT_ON_RAILS_PRO_LICENSE_V2`
126+
3. Set the value to a valid React on Rails Pro license key
127+
128+
**When adding new workflows:**
129+
If your workflow runs any Rails application that might have the Pro gem available (example apps, dummy apps, integration tests), add this environment variable:
130+
131+
```yaml
132+
env:
133+
REACT_ON_RAILS_PRO_LICENSE: ${{ secrets.REACT_ON_RAILS_PRO_LICENSE_V2 }}
134+
```
135+
136+
This prevents license validation failures during test runs in the monorepo where both MIT and Pro packages are present.
137+
108138
## Conditional Execution
109139
110140
Many workflows use change detection to skip unnecessary jobs:

CONTRIBUTING.md

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,60 @@ npm install <tarball path/URL>
193193

194194
or the equivalent command for your package manager.
195195

196+
## Gemspec File Management
197+
198+
**Important for Pro package contributors:**
199+
200+
The React on Rails Pro gemspec (`react_on_rails_pro.gemspec`) uses **explicit whitelisting** for included files rather than blacklisting. This is a security measure to ensure no unintended files are included in the Pro gem.
201+
202+
### What this means
203+
204+
When you add new Ruby files to the Pro package (`lib/react_on_rails_pro/`), they will **NOT** be automatically included in the gem. You must explicitly add them to the gemspec.
205+
206+
### How to update the gemspec
207+
208+
When adding new files to the Pro package:
209+
210+
1. **Add new Ruby files** to `lib/react_on_rails_pro/` as needed
211+
2. **Update `react_on_rails_pro.gemspec`** to include them:
212+
213+
```ruby
214+
s.files = Dir.glob("{lib/react_on_rails_pro.rb,lib/react_on_rails_pro/**/*}") +
215+
Dir.glob("lib/tasks/{assets_pro.rake,v8_log_processor.rake}") +
216+
# ... other files
217+
```
218+
219+
The glob patterns should automatically pick up new files under `lib/react_on_rails_pro/`, but if you add new directories or file types, verify they're included.
220+
221+
3. **Run the gemspec validation test** to ensure your files are included:
222+
223+
```bash
224+
bundle exec rspec spec/react_on_rails/gemspec_file_inclusion_spec.rb
225+
```
226+
227+
This test verifies:
228+
229+
- MIT gem doesn't include any Pro files
230+
- Pro gem includes all intended Pro files
231+
- No cross-contamination between MIT and Pro packages
232+
233+
### Why whitelisting?
234+
235+
The previous blacklist approach accidentally included Pro files in the MIT gem, which is a licensing violation. Whitelisting ensures:
236+
237+
- ✅ Only intended files are included
238+
- ✅ New files require conscious decision to include
239+
- ✅ Prevents licensing violations
240+
- ✅ CI will catch missing files via automated tests
241+
242+
### Troubleshooting
243+
244+
If you see test failures about missing files in the Pro gem:
245+
246+
1. Check if your new files are under `lib/react_on_rails_pro/`
247+
2. Verify the glob patterns in `react_on_rails_pro.gemspec` match your file structure
248+
3. Run `gem build react_on_rails_pro.gemspec` and inspect the built gem with `gem specification <gem-file>.gem files`
249+
196250
# Development Setup for Gem and Node Package Contributors
197251

198252
## Dev Initial Setup

react_on_rails.gemspec

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,9 @@ Gem::Specification.new do |s|
2222
# Exclude Pro-only files from MIT gem
2323
f.match(/^react_on_rails_pro/) ||
2424
f.match(%r{^lib/react_on_rails_pro}) ||
25-
f.match(%r{^lib/tasks/(assets_pro|v8_log_processor)\.rake$})
25+
f.match(%r{^lib/tasks/(assets_pro|v8_log_processor)\.rake$}) ||
26+
f == "CHANGELOG_PRO.md" ||
27+
f == "react_on_rails_pro.gemspec"
2628
end
2729
s.bindir = "exe"
2830
s.executables = s.files.grep(%r{^exe/}) { |f| File.basename(f) }
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# frozen_string_literal: true
2+
3+
require_relative "spec_helper"
4+
5+
RSpec.describe "Gemspec file inclusion" do
6+
describe "react_on_rails.gemspec" do
7+
let(:gemspec_path) { File.expand_path("../../react_on_rails.gemspec", __dir__) }
8+
let(:gemspec) { Gem::Specification.load(gemspec_path) }
9+
10+
it "exists and is valid" do
11+
expect(gemspec).not_to be_nil
12+
expect(gemspec.name).to eq("react_on_rails")
13+
end
14+
15+
context "when checking Pro file exclusion" do
16+
it "does not include any files from lib/react_on_rails_pro/" do
17+
pro_lib_files = gemspec.files.select { |f| f.start_with?("lib/react_on_rails_pro") }
18+
expect(pro_lib_files).to be_empty,
19+
"MIT gem should not include Pro lib files, but found: #{pro_lib_files.join(', ')}"
20+
end
21+
22+
it "does not include any files from react_on_rails_pro/ directory" do
23+
pro_dir_files = gemspec.files.select { |f| f.start_with?("react_on_rails_pro/") }
24+
expect(pro_dir_files).to be_empty,
25+
"MIT gem should not include Pro directory files, " \
26+
"but found: #{pro_dir_files.join(', ')}"
27+
end
28+
29+
it "does not include Pro-specific rake tasks" do
30+
pro_rake_tasks = gemspec.files.grep(%r{^lib/tasks/(assets_pro|v8_log_processor)\.rake$})
31+
expect(pro_rake_tasks).to be_empty,
32+
"MIT gem should not include Pro rake tasks, but found: #{pro_rake_tasks.join(', ')}"
33+
end
34+
35+
it "does not include Pro gemspec file" do
36+
pro_gemspec = gemspec.files.select { |f| f == "react_on_rails_pro.gemspec" }
37+
expect(pro_gemspec).to be_empty,
38+
"MIT gem should not include Pro gemspec"
39+
end
40+
41+
it "does not include CHANGELOG_PRO.md" do
42+
pro_changelog = gemspec.files.select { |f| f == "CHANGELOG_PRO.md" }
43+
expect(pro_changelog).to be_empty,
44+
"MIT gem should not include Pro changelog"
45+
end
46+
47+
it "does not include spec/pro/ test files" do
48+
pro_specs = gemspec.files.select { |f| f.start_with?("spec/pro/") }
49+
expect(pro_specs).to be_empty,
50+
"MIT gem should not include Pro spec files, but found: #{pro_specs.join(', ')}"
51+
end
52+
end
53+
54+
context "when checking MIT file inclusion" do
55+
it "includes lib/react_on_rails.rb" do
56+
expect(gemspec.files).to include("lib/react_on_rails.rb")
57+
end
58+
59+
it "includes files from lib/react_on_rails/ directory" do
60+
ror_files = gemspec.files.select { |f| f.start_with?("lib/react_on_rails/") }
61+
expect(ror_files).not_to be_empty
62+
end
63+
64+
it "includes standard MIT rake tasks" do
65+
rake_tasks = gemspec.files.grep(%r{^lib/tasks/.*\.rake$})
66+
# Should include standard tasks but not Pro tasks
67+
expect(rake_tasks).not_to be_empty
68+
expect(rake_tasks).to all(satisfy { |f| !f.match?(/assets_pro|v8_log_processor/) })
69+
end
70+
end
71+
end
72+
73+
describe "react_on_rails_pro.gemspec" do
74+
let(:gemspec_path) { File.expand_path("../../react_on_rails_pro.gemspec", __dir__) }
75+
let(:gemspec) do
76+
# The Pro gemspec requires the version files, so we need to allow that
77+
Gem::Specification.load(gemspec_path)
78+
end
79+
80+
it "exists and is valid" do
81+
expect(gemspec).not_to be_nil
82+
expect(gemspec.name).to eq("react_on_rails_pro")
83+
end
84+
85+
context "when checking Pro file inclusion" do
86+
it "includes lib/react_on_rails_pro.rb main file" do
87+
expect(gemspec.files).to include("lib/react_on_rails_pro.rb")
88+
end
89+
90+
it "includes all files from lib/react_on_rails_pro/ directory" do
91+
# Get actual files in the directory
92+
actual_pro_files = Dir.glob("lib/react_on_rails_pro/**/*")
93+
.reject { |f| File.directory?(f) }
94+
.sort
95+
96+
# Get files included in gemspec
97+
included_pro_files = gemspec.files.select { |f| f.start_with?("lib/react_on_rails_pro/") }
98+
.sort
99+
100+
missing_files = actual_pro_files - included_pro_files
101+
102+
expect(missing_files).to be_empty,
103+
"Pro gemspec is missing files: #{missing_files.join(', ')}"
104+
end
105+
106+
it "includes Pro-specific rake tasks" do
107+
expect(gemspec.files).to include("lib/tasks/assets_pro.rake")
108+
expect(gemspec.files).to include("lib/tasks/v8_log_processor.rake")
109+
end
110+
111+
it "includes CHANGELOG_PRO.md" do
112+
expect(gemspec.files).to include("CHANGELOG_PRO.md")
113+
end
114+
115+
it "includes react_on_rails_pro.gemspec" do
116+
expect(gemspec.files).to include("react_on_rails_pro.gemspec")
117+
end
118+
end
119+
120+
context "when checking file exclusions" do
121+
it "does not include test infrastructure from react_on_rails_pro/ directory" do
122+
# Should not include the dummy app or test infrastructure
123+
test_files = gemspec.files.select do |f|
124+
f.start_with?("react_on_rails_pro/") && !f.match?(%r{^react_on_rails_pro/(README|LICENSE|CHANGELOG)})
125+
end
126+
expect(test_files).to be_empty,
127+
"Pro gem should not include test infrastructure, but found: #{test_files.join(', ')}"
128+
end
129+
130+
it "does not include spec/pro/ files (they're test files, not library code)" do
131+
# NOTE: spec/pro/ contains the Pro *tests*, not the Pro library code
132+
# The Pro library code is in lib/react_on_rails_pro/
133+
spec_files = gemspec.files.select { |f| f.start_with?("spec/pro/") }
134+
expect(spec_files).to be_empty,
135+
"Pro gem should not include spec files, but found: #{spec_files.join(', ')}"
136+
end
137+
138+
it "does not include MIT-only files" do
139+
# Make sure we're not accidentally including the base gem's files
140+
mit_files = gemspec.files.select do |f|
141+
f.start_with?("lib/react_on_rails/") || f == "lib/react_on_rails.rb"
142+
end
143+
expect(mit_files).to be_empty,
144+
"Pro gem should not include MIT gem files, but found: #{mit_files.join(', ')}"
145+
end
146+
end
147+
end
148+
end

0 commit comments

Comments
 (0)