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.
Files changed (78) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +166 -0
  3. data/.rubocop.yml +89 -0
  4. data/CHANGELOG.md +32 -0
  5. data/README.md +35 -0
  6. data/RELEASING.md +203 -0
  7. data/Rakefile +10 -0
  8. data/SECURITY.md +62 -0
  9. data/Users/luiz/workspace/rails-rsc/gem/vendor/javascript/vite-plugin-ruact/index.js +163 -0
  10. data/lib/generators/ruact/install/install_generator.rb +100 -0
  11. data/lib/generators/ruact/install/templates/application.jsx.tt +51 -0
  12. data/lib/generators/ruact/install/templates/initializer.rb.tt +18 -0
  13. data/lib/generators/ruact/install/templates/vite.config.js.tt +26 -0
  14. data/lib/ruact/client_manifest.rb +115 -0
  15. data/lib/ruact/component_registry.rb +31 -0
  16. data/lib/ruact/configuration.rb +32 -0
  17. data/lib/ruact/controller.rb +195 -0
  18. data/lib/ruact/doctor.rb +84 -0
  19. data/lib/ruact/erb_preprocessor.rb +120 -0
  20. data/lib/ruact/erb_preprocessor_hook.rb +20 -0
  21. data/lib/ruact/errors.rb +14 -0
  22. data/lib/ruact/flight/react_element.rb +40 -0
  23. data/lib/ruact/flight/renderer.rb +73 -0
  24. data/lib/ruact/flight/request.rb +54 -0
  25. data/lib/ruact/flight/row_emitter.rb +37 -0
  26. data/lib/ruact/flight/serializer.rb +215 -0
  27. data/lib/ruact/flight.rb +12 -0
  28. data/lib/ruact/html_converter.rb +159 -0
  29. data/lib/ruact/railtie.rb +99 -0
  30. data/lib/ruact/render_pipeline.rb +107 -0
  31. data/lib/ruact/serializable.rb +58 -0
  32. data/lib/ruact/version.rb +5 -0
  33. data/lib/ruact/view_helper.rb +23 -0
  34. data/lib/ruact.rb +48 -0
  35. data/lib/rubocop/cop/ruact/no_extend_self.rb +46 -0
  36. data/lib/rubocop/cop/ruact/no_io_in_flight.rb +72 -0
  37. data/lib/rubocop/cop/ruact/no_shared_state.rb +49 -0
  38. data/lib/rubocop/cop/ruact.rb +5 -0
  39. data/lib/tasks/benchmark.rake +70 -0
  40. data/lib/tasks/rsc.rake +9 -0
  41. data/sig/ruact.rbs +4 -0
  42. data/spec/benchmarks/baseline.json +1 -0
  43. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +92 -0
  44. data/spec/fixtures/flight/README.md +88 -0
  45. data/spec/fixtures/flight/array.txt +1 -0
  46. data/spec/fixtures/flight/as_json_object.txt +2 -0
  47. data/spec/fixtures/flight/boolean_false.txt +1 -0
  48. data/spec/fixtures/flight/boolean_true.txt +1 -0
  49. data/spec/fixtures/flight/client_component_with_props.txt +2 -0
  50. data/spec/fixtures/flight/client_reference.txt +2 -0
  51. data/spec/fixtures/flight/hash.txt +1 -0
  52. data/spec/fixtures/flight/nil.txt +1 -0
  53. data/spec/fixtures/flight/number_float.txt +1 -0
  54. data/spec/fixtures/flight/number_integer.txt +1 -0
  55. data/spec/fixtures/flight/react_element_no_props.txt +1 -0
  56. data/spec/fixtures/flight/redirect_row.txt +1 -0
  57. data/spec/fixtures/flight/serializable_object.txt +2 -0
  58. data/spec/fixtures/flight/string_basic.txt +1 -0
  59. data/spec/fixtures/flight/string_dollar_escape.txt +1 -0
  60. data/spec/ruact/client_manifest_spec.rb +126 -0
  61. data/spec/ruact/controller_spec.rb +213 -0
  62. data/spec/ruact/doctor_spec.rb +234 -0
  63. data/spec/ruact/erb_preprocessor_hook_spec.rb +52 -0
  64. data/spec/ruact/erb_preprocessor_spec.rb +89 -0
  65. data/spec/ruact/errors_spec.rb +43 -0
  66. data/spec/ruact/flight/renderer_spec.rb +122 -0
  67. data/spec/ruact/flight/serializer_spec.rb +453 -0
  68. data/spec/ruact/html_converter_spec.rb +147 -0
  69. data/spec/ruact/install_generator_spec.rb +212 -0
  70. data/spec/ruact/railtie_spec.rb +156 -0
  71. data/spec/ruact/render_pipeline_spec.rb +474 -0
  72. data/spec/ruact/serializable_spec.rb +53 -0
  73. data/spec/ruact/view_helper_spec.rb +46 -0
  74. data/spec/spec_helper.rb +16 -0
  75. data/spec/support/matchers/flight_fixture_matcher.rb +25 -0
  76. data/spec/support/rails_stub.rb +45 -0
  77. data/vendor/javascript/vite-plugin-ruact/index.js +163 -0
  78. 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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rspec/core/rake_task"
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ Dir["lib/tasks/**/*.rake"].each { |f| load f }
9
+
10
+ task default: :spec
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.