ruact 0.0.1
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 +7 -0
- data/.github/workflows/ci.yml +166 -0
- data/.rubocop.yml +89 -0
- data/CHANGELOG.md +32 -0
- data/README.md +35 -0
- data/RELEASING.md +203 -0
- data/Rakefile +10 -0
- data/SECURITY.md +62 -0
- data/Users/luiz/workspace/rails-rsc/gem/vendor/javascript/vite-plugin-ruact/index.js +163 -0
- data/lib/generators/ruact/install/install_generator.rb +100 -0
- data/lib/generators/ruact/install/templates/application.jsx.tt +51 -0
- data/lib/generators/ruact/install/templates/initializer.rb.tt +18 -0
- data/lib/generators/ruact/install/templates/vite.config.js.tt +26 -0
- data/lib/ruact/client_manifest.rb +115 -0
- data/lib/ruact/component_registry.rb +31 -0
- data/lib/ruact/configuration.rb +32 -0
- data/lib/ruact/controller.rb +195 -0
- data/lib/ruact/doctor.rb +84 -0
- data/lib/ruact/erb_preprocessor.rb +120 -0
- data/lib/ruact/erb_preprocessor_hook.rb +20 -0
- data/lib/ruact/errors.rb +14 -0
- data/lib/ruact/flight/react_element.rb +40 -0
- data/lib/ruact/flight/renderer.rb +73 -0
- data/lib/ruact/flight/request.rb +54 -0
- data/lib/ruact/flight/row_emitter.rb +37 -0
- data/lib/ruact/flight/serializer.rb +215 -0
- data/lib/ruact/flight.rb +12 -0
- data/lib/ruact/html_converter.rb +159 -0
- data/lib/ruact/railtie.rb +99 -0
- data/lib/ruact/render_pipeline.rb +107 -0
- data/lib/ruact/serializable.rb +58 -0
- data/lib/ruact/version.rb +5 -0
- data/lib/ruact/view_helper.rb +23 -0
- data/lib/ruact.rb +48 -0
- data/lib/rubocop/cop/ruact/no_extend_self.rb +46 -0
- data/lib/rubocop/cop/ruact/no_io_in_flight.rb +72 -0
- data/lib/rubocop/cop/ruact/no_shared_state.rb +49 -0
- data/lib/rubocop/cop/ruact.rb +5 -0
- data/lib/tasks/benchmark.rake +70 -0
- data/lib/tasks/rsc.rake +9 -0
- data/sig/ruact.rbs +4 -0
- data/spec/benchmarks/baseline.json +1 -0
- data/spec/benchmarks/render_pipeline_benchmark_spec.rb +92 -0
- data/spec/fixtures/flight/README.md +88 -0
- data/spec/fixtures/flight/array.txt +1 -0
- data/spec/fixtures/flight/as_json_object.txt +2 -0
- data/spec/fixtures/flight/boolean_false.txt +1 -0
- data/spec/fixtures/flight/boolean_true.txt +1 -0
- data/spec/fixtures/flight/client_component_with_props.txt +2 -0
- data/spec/fixtures/flight/client_reference.txt +2 -0
- data/spec/fixtures/flight/hash.txt +1 -0
- data/spec/fixtures/flight/nil.txt +1 -0
- data/spec/fixtures/flight/number_float.txt +1 -0
- data/spec/fixtures/flight/number_integer.txt +1 -0
- data/spec/fixtures/flight/react_element_no_props.txt +1 -0
- data/spec/fixtures/flight/redirect_row.txt +1 -0
- data/spec/fixtures/flight/serializable_object.txt +2 -0
- data/spec/fixtures/flight/string_basic.txt +1 -0
- data/spec/fixtures/flight/string_dollar_escape.txt +1 -0
- data/spec/ruact/client_manifest_spec.rb +126 -0
- data/spec/ruact/controller_spec.rb +213 -0
- data/spec/ruact/doctor_spec.rb +234 -0
- data/spec/ruact/erb_preprocessor_hook_spec.rb +52 -0
- data/spec/ruact/erb_preprocessor_spec.rb +89 -0
- data/spec/ruact/errors_spec.rb +43 -0
- data/spec/ruact/flight/renderer_spec.rb +122 -0
- data/spec/ruact/flight/serializer_spec.rb +453 -0
- data/spec/ruact/html_converter_spec.rb +147 -0
- data/spec/ruact/install_generator_spec.rb +212 -0
- data/spec/ruact/railtie_spec.rb +156 -0
- data/spec/ruact/render_pipeline_spec.rb +474 -0
- data/spec/ruact/serializable_spec.rb +53 -0
- data/spec/ruact/view_helper_spec.rb +46 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/matchers/flight_fixture_matcher.rb +25 -0
- data/spec/support/rails_stub.rb +45 -0
- data/vendor/javascript/vite-plugin-ruact/index.js +163 -0
- metadata +136 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 2178853f396d86e745518fafefe519f54ee2d941aefa956d14dce639567e0dc3
|
|
4
|
+
data.tar.gz: c48ae371e9ba39e90a4db67e6a815fc6e5b2a982d9fe87709b41b36e9b83ff6b
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 3343bf04f0e4133aad881e65c97b1d701de33f00337a9eab1a98d9369d30dd2d355e7e566356505602a5041b575efc8547a142bc6ac997a8e36f753a919123bf
|
|
7
|
+
data.tar.gz: 328442e7e003bed71f9757805ad2620f89f57a49a8f5823b38a5082c7886618054dcfd0276bc68b59568514b2f1d6a69217e4213b5a947514f5a6d7e767b9324
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
rspec:
|
|
11
|
+
# Required status check — all 8 matrix combinations must pass to merge.
|
|
12
|
+
name: RSpec (Ruby ${{ matrix.ruby }} / Rails ${{ matrix.rails }})
|
|
13
|
+
runs-on: ubuntu-latest
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
ruby: ["3.2", "3.3"]
|
|
18
|
+
rails: ["7.0", "7.1", "7.2", "8.0"]
|
|
19
|
+
env:
|
|
20
|
+
RAILS_VERSION: ${{ matrix.rails }}
|
|
21
|
+
steps:
|
|
22
|
+
- uses: actions/checkout@v4
|
|
23
|
+
- uses: ruby/setup-ruby@v1
|
|
24
|
+
with:
|
|
25
|
+
ruby-version: ${{ matrix.ruby }}
|
|
26
|
+
bundler-cache: true
|
|
27
|
+
working-directory: gem
|
|
28
|
+
- name: Run RSpec
|
|
29
|
+
working-directory: gem
|
|
30
|
+
run: bundle exec rspec --format progress
|
|
31
|
+
|
|
32
|
+
rubocop:
|
|
33
|
+
# Required status check.
|
|
34
|
+
name: RuboCop
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
steps:
|
|
37
|
+
- uses: actions/checkout@v4
|
|
38
|
+
- uses: ruby/setup-ruby@v1
|
|
39
|
+
with:
|
|
40
|
+
ruby-version: "3.3"
|
|
41
|
+
bundler-cache: true
|
|
42
|
+
working-directory: gem
|
|
43
|
+
- name: Run RuboCop
|
|
44
|
+
working-directory: gem
|
|
45
|
+
run: bundle exec rubocop --format github
|
|
46
|
+
|
|
47
|
+
yard:
|
|
48
|
+
# Required status check — fails if any public method is missing YARD docs.
|
|
49
|
+
name: YARD Docs
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
steps:
|
|
52
|
+
- uses: actions/checkout@v4
|
|
53
|
+
- uses: ruby/setup-ruby@v1
|
|
54
|
+
with:
|
|
55
|
+
ruby-version: "3.3"
|
|
56
|
+
bundler-cache: true
|
|
57
|
+
working-directory: gem
|
|
58
|
+
- name: Validate YARD docs
|
|
59
|
+
working-directory: gem
|
|
60
|
+
run: bundle exec yard --fail-on-warning
|
|
61
|
+
|
|
62
|
+
benchmark:
|
|
63
|
+
# Required status check — fails if memory allocations exceed 120% of baseline.json.
|
|
64
|
+
name: Memory Benchmark
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
steps:
|
|
67
|
+
- uses: actions/checkout@v4
|
|
68
|
+
- uses: ruby/setup-ruby@v1
|
|
69
|
+
with:
|
|
70
|
+
ruby-version: "3.3"
|
|
71
|
+
bundler-cache: true
|
|
72
|
+
working-directory: gem
|
|
73
|
+
- name: Run memory benchmark
|
|
74
|
+
working-directory: gem
|
|
75
|
+
run: bundle exec rake benchmark:memory
|
|
76
|
+
|
|
77
|
+
e2e:
|
|
78
|
+
# Required status check — both react@19.0.0 (pinned) and react@19.x (latest) must pass.
|
|
79
|
+
name: E2E (React ${{ matrix.react }})
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
strategy:
|
|
82
|
+
fail-fast: false
|
|
83
|
+
matrix:
|
|
84
|
+
react: ["19.0.0", "19.x"]
|
|
85
|
+
steps:
|
|
86
|
+
- uses: actions/checkout@v4
|
|
87
|
+
- uses: ruby/setup-ruby@v1
|
|
88
|
+
with:
|
|
89
|
+
ruby-version: "3.3"
|
|
90
|
+
bundler-cache: true
|
|
91
|
+
working-directory: e2e
|
|
92
|
+
- uses: actions/setup-node@v4
|
|
93
|
+
with:
|
|
94
|
+
node-version: "20"
|
|
95
|
+
- name: Install npm packages
|
|
96
|
+
working-directory: e2e
|
|
97
|
+
run: npm install
|
|
98
|
+
- name: Pin React version
|
|
99
|
+
working-directory: e2e
|
|
100
|
+
run: npm install react@${{ matrix.react }} react-dom@${{ matrix.react }}
|
|
101
|
+
- name: Build Vite assets
|
|
102
|
+
working-directory: e2e
|
|
103
|
+
run: npx vite build
|
|
104
|
+
- name: Run system tests
|
|
105
|
+
working-directory: e2e
|
|
106
|
+
env:
|
|
107
|
+
RAILS_ENV: test
|
|
108
|
+
run: bundle exec rails test:system
|
|
109
|
+
- name: Upload screenshots on failure
|
|
110
|
+
uses: actions/upload-artifact@v4
|
|
111
|
+
if: failure()
|
|
112
|
+
with:
|
|
113
|
+
name: e2e-screenshots-react-${{ matrix.react }}
|
|
114
|
+
path: e2e/tmp/screenshots
|
|
115
|
+
if-no-files-found: ignore
|
|
116
|
+
|
|
117
|
+
e2e-next:
|
|
118
|
+
# Non-blocking — failures open a GitHub issue with label react-next-compat.
|
|
119
|
+
# This job is NOT a required status check for branch protection.
|
|
120
|
+
name: E2E (React next — non-blocking)
|
|
121
|
+
runs-on: ubuntu-latest
|
|
122
|
+
continue-on-error: true
|
|
123
|
+
steps:
|
|
124
|
+
- uses: actions/checkout@v4
|
|
125
|
+
- uses: ruby/setup-ruby@v1
|
|
126
|
+
with:
|
|
127
|
+
ruby-version: "3.3"
|
|
128
|
+
bundler-cache: true
|
|
129
|
+
working-directory: e2e
|
|
130
|
+
- uses: actions/setup-node@v4
|
|
131
|
+
with:
|
|
132
|
+
node-version: "20"
|
|
133
|
+
- name: Install npm packages
|
|
134
|
+
working-directory: e2e
|
|
135
|
+
run: npm install
|
|
136
|
+
- name: Pin React@next
|
|
137
|
+
working-directory: e2e
|
|
138
|
+
run: npm install react@next react-dom@next
|
|
139
|
+
- name: Build Vite assets
|
|
140
|
+
working-directory: e2e
|
|
141
|
+
run: npx vite build
|
|
142
|
+
- name: Run system tests
|
|
143
|
+
id: system-tests
|
|
144
|
+
working-directory: e2e
|
|
145
|
+
env:
|
|
146
|
+
RAILS_ENV: test
|
|
147
|
+
run: bundle exec rails test:system
|
|
148
|
+
- name: Upload screenshots on failure
|
|
149
|
+
uses: actions/upload-artifact@v4
|
|
150
|
+
if: failure()
|
|
151
|
+
with:
|
|
152
|
+
name: e2e-screenshots-react-next
|
|
153
|
+
path: e2e/tmp/screenshots
|
|
154
|
+
if-no-files-found: ignore
|
|
155
|
+
- name: Open compatibility issue on failure
|
|
156
|
+
if: failure()
|
|
157
|
+
uses: actions/github-script@v7
|
|
158
|
+
with:
|
|
159
|
+
script: |
|
|
160
|
+
await github.rest.issues.create({
|
|
161
|
+
owner: context.repo.owner,
|
|
162
|
+
repo: context.repo.repo,
|
|
163
|
+
title: `react@next compatibility failure (run #${context.runNumber})`,
|
|
164
|
+
labels: ['react-next-compat'],
|
|
165
|
+
body: `CI run [#${context.runNumber}](${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}) failed against react@next on commit \`${context.sha}\`.\n\nTriaged within 2 weeks per NFR7. Does not block release.`
|
|
166
|
+
});
|
data/.rubocop.yml
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require:
|
|
2
|
+
- rubocop/cop/ruact
|
|
3
|
+
|
|
4
|
+
plugins:
|
|
5
|
+
- rubocop-rspec
|
|
6
|
+
- rubocop-performance
|
|
7
|
+
|
|
8
|
+
AllCops:
|
|
9
|
+
TargetRubyVersion: 3.2
|
|
10
|
+
NewCops: enable
|
|
11
|
+
Exclude:
|
|
12
|
+
- "vendor/**/*"
|
|
13
|
+
- "sig/**/*"
|
|
14
|
+
|
|
15
|
+
# --- Layout ---
|
|
16
|
+
Layout/LineLength:
|
|
17
|
+
Max: 120
|
|
18
|
+
|
|
19
|
+
# --- Metrics ---
|
|
20
|
+
Metrics/MethodLength:
|
|
21
|
+
Max: 30
|
|
22
|
+
CountAsOne: ["array", "hash", "heredoc"]
|
|
23
|
+
# Note: case/when chain in serializer.rb and converter is intentional by architecture decision
|
|
24
|
+
|
|
25
|
+
Metrics/BlockLength:
|
|
26
|
+
Exclude:
|
|
27
|
+
- "spec/**/*"
|
|
28
|
+
- "Gemfile"
|
|
29
|
+
- "lib/tasks/**/*.rake"
|
|
30
|
+
|
|
31
|
+
Naming/VariableNumber:
|
|
32
|
+
Exclude:
|
|
33
|
+
- "lib/tasks/**/*.rake"
|
|
34
|
+
|
|
35
|
+
Metrics/ClassLength:
|
|
36
|
+
Max: 150
|
|
37
|
+
|
|
38
|
+
Metrics/ModuleLength:
|
|
39
|
+
Max: 150
|
|
40
|
+
Exclude:
|
|
41
|
+
- "spec/**/*"
|
|
42
|
+
|
|
43
|
+
Metrics/AbcSize:
|
|
44
|
+
Max: 50
|
|
45
|
+
# Note: PoC methods (rsc_render, each, convert_element) will be refactored
|
|
46
|
+
# in later stories. Current values reflect tech debt, not design intent.
|
|
47
|
+
|
|
48
|
+
Metrics/CyclomaticComplexity:
|
|
49
|
+
Max: 20
|
|
50
|
+
|
|
51
|
+
Metrics/PerceivedComplexity:
|
|
52
|
+
Max: 20
|
|
53
|
+
|
|
54
|
+
# --- Style ---
|
|
55
|
+
Style/Documentation:
|
|
56
|
+
Enabled: false
|
|
57
|
+
|
|
58
|
+
Style/FrozenStringLiteralComment:
|
|
59
|
+
Enabled: true
|
|
60
|
+
EnforcedStyle: always
|
|
61
|
+
|
|
62
|
+
Style/StringLiterals:
|
|
63
|
+
EnforcedStyle: double_quotes
|
|
64
|
+
|
|
65
|
+
# --- RSpec ---
|
|
66
|
+
RSpec/MultipleExpectations:
|
|
67
|
+
Max: 5
|
|
68
|
+
|
|
69
|
+
RSpec/ExampleLength:
|
|
70
|
+
Max: 25
|
|
71
|
+
|
|
72
|
+
RSpec/MultipleMemoizedHelpers:
|
|
73
|
+
Max: 7
|
|
74
|
+
|
|
75
|
+
RSpec/NestedGroups:
|
|
76
|
+
Max: 4
|
|
77
|
+
|
|
78
|
+
RSpec/DescribeClass:
|
|
79
|
+
Enabled: false
|
|
80
|
+
|
|
81
|
+
# --- Custom Ruact Cops ---
|
|
82
|
+
Ruact/NoIoInFlight:
|
|
83
|
+
Enabled: true
|
|
84
|
+
|
|
85
|
+
Ruact/NoSharedState:
|
|
86
|
+
Enabled: true
|
|
87
|
+
|
|
88
|
+
Ruact/NoExtendSelf:
|
|
89
|
+
Enabled: true
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-03-24
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **ERB preprocessor** — PascalCase RSC component tags (`<Button />`, `<LikeButton postId={@post.id} />`) are transformed to Flight placeholders in ERB templates before Ruby evaluation.
|
|
15
|
+
- **`<Suspense>` support** — `<Suspense fallback="Loading...">` in ERB templates maps to React Suspense boundaries in the Flight payload.
|
|
16
|
+
- **React Flight wire format serializer** — Full Ruby-to-Flight protocol implementation covering: nil, booleans, integers, floats (NaN/Infinity/-0), strings (with `$` escaping), arrays, hashes, `Time`/`DateTime`, large strings (T rows), `ReactElement`, `SuspenseElement`, and `ClientReference`.
|
|
17
|
+
- **`Ruact::Controller` concern** — Include in `ApplicationController` to enable RSC rendering. Provides `rsc_render`, RSC request detection (`text/x-component` / `RSC-Request: 1` header), HTML shell generation with inline `__FLIGHT_DATA`, and Flight-aware `redirect_to`.
|
|
18
|
+
- **Streaming mode** — When `ActionController::Live` is included, Flight rows are streamed to the client as they are produced (Suspense-aware).
|
|
19
|
+
- **Client component resolution** — `Ruact::ClientManifest` reads `public/react-client-manifest.json` (generated by the Vite plugin) and resolves component names to `ClientReference` objects via a dual-path resolver.
|
|
20
|
+
- **`Ruact::Serializable` mixin** — `rsc_props` DSL for declaring safe prop attributes on Ruby model objects.
|
|
21
|
+
- **Install generator** — `rails generate ruact:install` scaffolds the initializer, Vite config patch, and JavaScript entry point.
|
|
22
|
+
- **`rsc:doctor` Rake task** — Checks manifest presence, Vite server accessibility, controller setup, and streaming mode configuration.
|
|
23
|
+
- **`vite-plugin-ruact`** — Vite plugin (npm package, co-versioned) that scans `"use client"` components and emits `public/react-client-manifest.json`.
|
|
24
|
+
- **Client-side navigation** — JavaScript `rsc-router.js` intercepts same-origin link clicks and form submissions, fetches Flight payloads, and updates the React tree without full-page reloads.
|
|
25
|
+
- **Error overlay** — Development-mode React error boundary with dismissible overlay for Flight parse and rendering errors.
|
|
26
|
+
- **RSpec test suite** — 223 examples covering all modules: Flight serializer, ERB preprocessor, HTML converter, render pipeline, controller, client manifest, serializable, install generator, and `rsc:doctor`.
|
|
27
|
+
- **Memory benchmark** — `rake benchmark:memory` enforces a 120% allocation regression gate against `spec/benchmarks/baseline.json`.
|
|
28
|
+
- **CI matrix** — GitHub Actions: RSpec across Ruby 3.2 × 3.3 × Rails 7.0 × 7.1 × 7.2 × 8.0; RuboCop; YARD docs; memory benchmark; E2E system tests against React 19.0.0 and 19.x (Capybara + Cuprite); non-blocking React@next job with auto-issue on failure.
|
|
29
|
+
- **E2E test app** — `e2e/` Rails app (no DB, in-memory Post model) with full CRUD system tests validating the complete request cycle.
|
|
30
|
+
|
|
31
|
+
[Unreleased]: https://github.com/luizcg/ruact/compare/v0.1.0...HEAD
|
|
32
|
+
[0.1.0]: https://github.com/luizcg/ruact/releases/tag/v0.1.0
|
data/README.md
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Ruact
|
|
2
|
+
|
|
3
|
+
TODO: Delete this and the text below, and describe your gem
|
|
4
|
+
|
|
5
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/ruact`. To experiment with that code, run `bin/console` for an interactive prompt.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
TODO: Replace `UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG` with your gem name right after releasing it to RubyGems.org. Please do not do it earlier due to security reasons. Alternatively, replace this section with instructions to install your gem from git if you don't plan to release to RubyGems.org.
|
|
10
|
+
|
|
11
|
+
Install the gem and add to the application's Gemfile by executing:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
bundle add UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
gem install UPDATE_WITH_YOUR_GEM_NAME_IMMEDIATELY_AFTER_RELEASE_TO_RUBYGEMS_ORG
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
TODO: Write usage instructions here
|
|
26
|
+
|
|
27
|
+
## Development
|
|
28
|
+
|
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
|
30
|
+
|
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
|
32
|
+
|
|
33
|
+
## Contributing
|
|
34
|
+
|
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/ruact.
|
data/RELEASING.md
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
# Releasing ruact
|
|
2
|
+
|
|
3
|
+
This document describes the complete release process for `ruact`. Following these steps in order enables any maintainer to cut a release independently.
|
|
4
|
+
|
|
5
|
+
The gem and the `vite-plugin-ruact` npm package share the same version number and are **always released together** as a single operation.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Pre-Release Checklist
|
|
10
|
+
|
|
11
|
+
Before starting, confirm all of the following:
|
|
12
|
+
|
|
13
|
+
- [ ] All CI jobs are green on `main` (rspec matrix, rubocop, yard, benchmark, e2e)
|
|
14
|
+
- [ ] No open issues or PRs labelled `release-blocker`
|
|
15
|
+
- [ ] `gem/ruact.gemspec` has no TODO placeholder values (`summary`, `homepage_uri`, `source_code_uri`, `allowed_push_host` must all be set to real values before `gem build` will succeed)
|
|
16
|
+
- [ ] You have a RubyGems account with push access to `ruact` and an OTP authenticator configured (MFA is required — `rubygems_mfa_required: true`)
|
|
17
|
+
- [ ] You have an npm account with publish access to `vite-plugin-ruact`
|
|
18
|
+
- [ ] You have GPG or SSH commit signing configured (recommended)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## Version Decision (SemVer)
|
|
23
|
+
|
|
24
|
+
Given the current version `X.Y.Z`, choose the next version:
|
|
25
|
+
|
|
26
|
+
| Change type | Version bump | Example |
|
|
27
|
+
|---|---|---|
|
|
28
|
+
| Breaking change (see below) | Major: `X+1.0.0` | `0.1.0` → `1.0.0` |
|
|
29
|
+
| New feature, backwards-compatible | Minor: `X.Y+1.0` | `0.1.0` → `0.2.0` |
|
|
30
|
+
| Bug fix, patch | Patch: `X.Y.Z+1` | `0.1.0` → `0.1.1` |
|
|
31
|
+
|
|
32
|
+
**What counts as a breaking change**: any change to the public API that requires consumers to update their code — e.g. renaming a public method, changing method signatures, removing a configuration option, or changing the Flight wire format in a non-backwards-compatible way.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## Release Steps
|
|
37
|
+
|
|
38
|
+
### 1. Create a release branch
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
git checkout -b release/v{X.Y.Z}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### 2. Bump the gem version
|
|
45
|
+
|
|
46
|
+
Edit `gem/lib/ruact/version.rb`:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
module Ruact
|
|
50
|
+
VERSION = "{X.Y.Z}"
|
|
51
|
+
end
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### 3. Bump the npm package version
|
|
55
|
+
|
|
56
|
+
Edit `packages/vite-plugin-ruact/package.json`:
|
|
57
|
+
|
|
58
|
+
```json
|
|
59
|
+
{
|
|
60
|
+
"version": "{X.Y.Z}"
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 4. Update `gem/CHANGELOG.md`
|
|
65
|
+
|
|
66
|
+
1. Move all items under `## [Unreleased]` into a new section `## [{X.Y.Z}] - {YYYY-MM-DD}`.
|
|
67
|
+
2. Add a new empty `## [Unreleased]` section at the very top (above the new release section).
|
|
68
|
+
3. Add or update the link footer at the bottom:
|
|
69
|
+
```
|
|
70
|
+
[Unreleased]: https://github.com/luizcg/ruact/compare/v{X.Y.Z}...HEAD
|
|
71
|
+
[{X.Y.Z}]: https://github.com/luizcg/ruact/compare/v{PREV}...v{X.Y.Z}
|
|
72
|
+
```
|
|
73
|
+
4. If this release contains breaking changes, ensure the section includes a `[BREAKING]` subsection with a **Migration Guide** (see "Breaking Changes" section below).
|
|
74
|
+
|
|
75
|
+
### 5. Update `packages/vite-plugin-ruact/CHANGELOG.md`
|
|
76
|
+
|
|
77
|
+
Same format as step 4 for the npm package changelog.
|
|
78
|
+
|
|
79
|
+
### 6. Commit the release
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
git add gem/lib/ruact/version.rb \
|
|
83
|
+
packages/vite-plugin-ruact/package.json \
|
|
84
|
+
gem/CHANGELOG.md \
|
|
85
|
+
packages/vite-plugin-ruact/CHANGELOG.md
|
|
86
|
+
git commit -m "Release v{X.Y.Z}"
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 7. Push the release branch and open a PR
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
git push origin release/v{X.Y.Z}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Open a PR from `release/v{X.Y.Z}` → `main`. Wait for CI to go green before continuing.
|
|
96
|
+
|
|
97
|
+
### 8. Merge, verify CI, and tag
|
|
98
|
+
|
|
99
|
+
Merge the PR. Confirm all CI jobs pass on the merge commit, then tag the verified merge commit:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
git checkout main
|
|
103
|
+
git pull origin main
|
|
104
|
+
git tag v{X.Y.Z}
|
|
105
|
+
git push origin v{X.Y.Z}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
> **Why tag after merge?** Tagging after CI passes on the merge commit ensures the tag always points to a verified, releasable commit. Tagging the branch commit before merge risks tagging code that fails CI after merge.
|
|
109
|
+
|
|
110
|
+
### 9. Publish the gem to RubyGems
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
cd gem
|
|
114
|
+
gem build ruact.gemspec
|
|
115
|
+
gem push ruact-{X.Y.Z}.gem
|
|
116
|
+
# Enter OTP when prompted (MFA is required)
|
|
117
|
+
rm ruact-{X.Y.Z}.gem
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
> **Note**: `spec.files` in the gemspec is populated via `git ls-files -z`. The files `CHANGELOG.md`, `RELEASING.md`, and `SECURITY.md` must be committed to git to appear in the gem tarball. Verify with:
|
|
121
|
+
> ```bash
|
|
122
|
+
> gem contents ruact-{X.Y.Z} | grep -E 'CHANGELOG|RELEASING|SECURITY'
|
|
123
|
+
> ```
|
|
124
|
+
|
|
125
|
+
### 10. Publish the npm package
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
cd packages/vite-plugin-ruact
|
|
129
|
+
npm publish
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### 11. Create a GitHub Release
|
|
133
|
+
|
|
134
|
+
1. Go to [Releases](https://github.com/luizcg/ruact/releases/new)
|
|
135
|
+
2. Select tag `v{X.Y.Z}`
|
|
136
|
+
3. Title: `v{X.Y.Z}`
|
|
137
|
+
4. Body: paste the `## [{X.Y.Z}]` section from `gem/CHANGELOG.md`
|
|
138
|
+
5. Publish
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## Breaking Changes
|
|
143
|
+
|
|
144
|
+
When a release contains a breaking change:
|
|
145
|
+
|
|
146
|
+
1. Bump the **major** version (e.g. `0.1.0` → `1.0.0`).
|
|
147
|
+
2. Mark the CHANGELOG entry with `[BREAKING]` and include a **Migration Guide** sub-section:
|
|
148
|
+
|
|
149
|
+
```markdown
|
|
150
|
+
## [1.0.0] - YYYY-MM-DD
|
|
151
|
+
|
|
152
|
+
### Changed
|
|
153
|
+
- [BREAKING] `rsc_render` now requires explicit `template:` keyword for non-standard action names
|
|
154
|
+
|
|
155
|
+
#### Migration Guide
|
|
156
|
+
|
|
157
|
+
**Before:**
|
|
158
|
+
```ruby
|
|
159
|
+
render_rsc "posts/custom"
|
|
160
|
+
```
|
|
161
|
+
**After:**
|
|
162
|
+
```ruby
|
|
163
|
+
rsc_render template: "posts/custom"
|
|
164
|
+
```
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
3. Announce the breaking change prominently in the GitHub Release body.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Rollback
|
|
172
|
+
|
|
173
|
+
If a release contains a critical defect and must be pulled:
|
|
174
|
+
|
|
175
|
+
**RubyGems** (within 30 days):
|
|
176
|
+
```bash
|
|
177
|
+
gem yank ruact -v {X.Y.Z}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**npm** (within 72 hours):
|
|
181
|
+
```bash
|
|
182
|
+
npm unpublish vite-plugin-ruact@{X.Y.Z}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
After yanking, cut a patch release (`{X.Y.Z+1}`) with the fix immediately. Do not leave the version yanked without a replacement.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Troubleshooting
|
|
190
|
+
|
|
191
|
+
**`gem push` fails with "MFA required"**: Run `gem signin` first and ensure your OTP authenticator is set up at https://rubygems.org/settings/edit.
|
|
192
|
+
|
|
193
|
+
**Files missing from gem tarball**: The gemspec uses `git ls-files -z`. Ensure all new files (e.g. `CHANGELOG.md`) are committed to git before running `gem build`.
|
|
194
|
+
|
|
195
|
+
**CI fails on release branch**: Fix the issue on the release branch and push the fix. Wait for CI to pass before tagging. If you already pushed a tag pointing to a broken commit, delete it and recreate after the fix:
|
|
196
|
+
```bash
|
|
197
|
+
git tag -d v{X.Y.Z} # delete local tag
|
|
198
|
+
git push origin :refs/tags/v{X.Y.Z} # delete remote tag
|
|
199
|
+
# fix the issue, merge, then re-tag from the correct commit
|
|
200
|
+
git checkout main && git pull origin main
|
|
201
|
+
git tag v{X.Y.Z} && git push origin v{X.Y.Z}
|
|
202
|
+
```
|
|
203
|
+
Do not use `git push --force` on tags — force-pushing a tag rewrite history for anyone who has already fetched it.
|
data/Rakefile
ADDED
data/SECURITY.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
Only the latest patch release of the current minor version is actively maintained for security fixes.
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
|---------|-----------|
|
|
9
|
+
| 0.1.x | ✅ Yes |
|
|
10
|
+
|
|
11
|
+
Once a new minor version is released, the previous minor version receives security fixes for **90 days** after the new release, then it is no longer supported.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Reporting a Vulnerability
|
|
16
|
+
|
|
17
|
+
**Do not open a public GitHub issue for security vulnerabilities.**
|
|
18
|
+
|
|
19
|
+
Please use [GitHub Security Advisories](https://github.com/luizcg/ruact/security/advisories/new) to report vulnerabilities privately. This keeps the report confidential until a fix is prepared and released.
|
|
20
|
+
|
|
21
|
+
### What to include
|
|
22
|
+
|
|
23
|
+
- A description of the vulnerability and its potential impact
|
|
24
|
+
- Steps to reproduce (a minimal proof-of-concept if possible)
|
|
25
|
+
- The version(s) of `ruact` you tested against
|
|
26
|
+
- Any suggested mitigations or patches, if you have them
|
|
27
|
+
|
|
28
|
+
### Response timeline
|
|
29
|
+
|
|
30
|
+
| Milestone | Target |
|
|
31
|
+
|-----------|--------|
|
|
32
|
+
| Initial acknowledgement | **Within 14 days** of receiving the report |
|
|
33
|
+
| Triage and severity assessment | Within 30 days |
|
|
34
|
+
| Patch release for confirmed vulnerabilities | Within 90 days |
|
|
35
|
+
|
|
36
|
+
If a reported vulnerability is accepted, you will be credited in the CHANGELOG and GitHub Security Advisory unless you prefer to remain anonymous.
|
|
37
|
+
|
|
38
|
+
If a report is declined (e.g. the reported behaviour is by design or the impact is below our threshold), we will explain our reasoning.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Disclosure Policy
|
|
43
|
+
|
|
44
|
+
We follow [Coordinated Vulnerability Disclosure](https://vuls.cert.org/confluence/display/CVD): we ask that you give us a reasonable amount of time to fix the issue before publishing details publicly. We aim to meet the timelines above so that you are not kept waiting.
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Out of Scope
|
|
49
|
+
|
|
50
|
+
The following are generally not considered security vulnerabilities for this gem:
|
|
51
|
+
|
|
52
|
+
- Vulnerabilities in Rails, React, Nokogiri, or other dependencies — report those to the respective projects
|
|
53
|
+
- Theoretical attacks with no practical exploitability
|
|
54
|
+
- Denial-of-service via extremely large inputs (unless the gem has no guard and the impact is severe)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Contact
|
|
59
|
+
|
|
60
|
+
Primary channel: [GitHub Security Advisories](https://github.com/luizcg/ruact/security/advisories/new) (preferred — keeps reports private)
|
|
61
|
+
|
|
62
|
+
Maintainer: Luiz Garcia — see GitHub profile for additional contact options.
|