ruact 0.0.11 → 0.0.13

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 (137) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +147 -48
  3. data/CONTRIBUTING.md +233 -0
  4. data/README.md +18 -8
  5. data/RELEASING.md +125 -139
  6. data/lib/generators/ruact/install/install_generator.rb +305 -126
  7. data/lib/generators/ruact/install/templates/AGENTS.md.tt +14 -13
  8. data/lib/generators/ruact/install/templates/initializer.rb.tt +29 -7
  9. data/lib/generators/ruact/install/templates/tsconfig.json.tt +3 -0
  10. data/lib/generators/ruact/layout/layout_generator.rb +52 -0
  11. data/lib/generators/ruact/scaffold/templates/controller.rb.tt +5 -3
  12. data/lib/ruact/configuration.rb +65 -13
  13. data/lib/ruact/controller/document_rendering.rb +72 -16
  14. data/lib/ruact/controller/page_rendering.rb +134 -0
  15. data/lib/ruact/controller/pages.rb +116 -0
  16. data/lib/ruact/controller.rb +78 -11
  17. data/lib/ruact/doctor.rb +233 -25
  18. data/lib/ruact/layout_source.rb +29 -7
  19. data/lib/ruact/navigation_boundary.rb +240 -0
  20. data/lib/ruact/packaging.rb +68 -0
  21. data/lib/ruact/railtie.rb +30 -0
  22. data/lib/ruact/server.rb +10 -1
  23. data/lib/ruact/version.rb +1 -1
  24. data/lib/ruact/view_helper.rb +158 -1
  25. data/lib/ruact/views/layouts/ruact.html.erb +32 -0
  26. data/lib/ruact.rb +29 -0
  27. data/vendor/javascript/vite-plugin-ruact/flight-client.test.mjs +321 -0
  28. data/vendor/javascript/vite-plugin-ruact/package-lock.json +11 -0
  29. data/vendor/javascript/vite-plugin-ruact/package.json +1 -0
  30. data/vendor/javascript/vite-plugin-ruact/ruact-router.test.mjs +433 -0
  31. data/vendor/javascript/vite-plugin-ruact/runtime/flight-client.js +14 -3
  32. data/vendor/javascript/vite-plugin-ruact/runtime/ruact-router.js +170 -7
  33. metadata +12 -107
  34. data/.codecov.yml +0 -31
  35. data/.github/workflows/ci.yml +0 -284
  36. data/.github/workflows/server-functions-bench.yml +0 -54
  37. data/.rubocop.yml +0 -107
  38. data/.rubocop_todo.yml +0 -63
  39. data/Rakefile +0 -10
  40. data/bench/server_functions_dispatch_bench.rb +0 -276
  41. data/bench/server_functions_dispatch_bench.results.md +0 -150
  42. data/docs/internal/README.md +0 -9
  43. data/docs/internal/decisions/server-functions-api.md +0 -2236
  44. data/spec/benchmarks/baseline.json +0 -12
  45. data/spec/benchmarks/render_pipeline_benchmark_spec.rb +0 -109
  46. data/spec/fixtures/flight/README.md +0 -136
  47. data/spec/fixtures/flight/array.txt +0 -1
  48. data/spec/fixtures/flight/as_json_object.txt +0 -2
  49. data/spec/fixtures/flight/bigint.txt +0 -1
  50. data/spec/fixtures/flight/boolean_false.txt +0 -1
  51. data/spec/fixtures/flight/boolean_true.txt +0 -1
  52. data/spec/fixtures/flight/client_component_with_props.txt +0 -2
  53. data/spec/fixtures/flight/client_reference.txt +0 -2
  54. data/spec/fixtures/flight/hash.txt +0 -1
  55. data/spec/fixtures/flight/infinity.txt +0 -1
  56. data/spec/fixtures/flight/nan.txt +0 -1
  57. data/spec/fixtures/flight/negative_infinity.txt +0 -1
  58. data/spec/fixtures/flight/nil.txt +0 -1
  59. data/spec/fixtures/flight/number_float.txt +0 -1
  60. data/spec/fixtures/flight/number_integer.txt +0 -1
  61. data/spec/fixtures/flight/react_element_no_props.txt +0 -1
  62. data/spec/fixtures/flight/redirect_row.txt +0 -1
  63. data/spec/fixtures/flight/serializable_object.txt +0 -2
  64. data/spec/fixtures/flight/string_basic.txt +0 -1
  65. data/spec/fixtures/flight/string_dollar_escape.txt +0 -1
  66. data/spec/fixtures/flight/undefined.txt +0 -1
  67. data/spec/fixtures/readme/children-error.html.erb +0 -3
  68. data/spec/fixtures/readme/children-error.txt +0 -1
  69. data/spec/fixtures/story_7_9_views/controller_request_spec_support/demo/show.html.erb +0 -3
  70. data/spec/fixtures/story_7_9_views/controller_request_spec_support/errors_demo/new.html.erb +0 -3
  71. data/spec/fixtures/story_7_9_views/controller_request_spec_support/exploding_layout_demo/show.html.erb +0 -3
  72. data/spec/fixtures/story_7_9_views/controller_request_spec_support/ghost_layout_demo/show.html.erb +0 -3
  73. data/spec/fixtures/story_7_9_views/controller_request_spec_support/layout_demo/show.html.erb +0 -3
  74. data/spec/fixtures/story_7_9_views/controller_request_spec_support/rootless_layout_demo/show.html.erb +0 -3
  75. data/spec/fixtures/story_7_9_views/controller_request_spec_support/unwired_layout_demo/show.html.erb +0 -3
  76. data/spec/fixtures/story_7_9_views/layouts/bare_host.html.erb +0 -16
  77. data/spec/fixtures/story_7_9_views/layouts/exploding_host.html.erb +0 -24
  78. data/spec/fixtures/story_7_9_views/layouts/rootless_host.html.erb +0 -15
  79. data/spec/fixtures/story_7_9_views/layouts/ruact_host.html.erb +0 -17
  80. data/spec/readme_demo_message_spec.rb +0 -67
  81. data/spec/readme_spec.rb +0 -282
  82. data/spec/ruact/client_manifest_spec.rb +0 -270
  83. data/spec/ruact/component_contract_spec.rb +0 -119
  84. data/spec/ruact/configuration_spec.rb +0 -518
  85. data/spec/ruact/controller_request_spec.rb +0 -671
  86. data/spec/ruact/controller_spec.rb +0 -343
  87. data/spec/ruact/doctor_spec.rb +0 -769
  88. data/spec/ruact/erb_preprocessor_hook_spec.rb +0 -55
  89. data/spec/ruact/erb_preprocessor_spec.rb +0 -361
  90. data/spec/ruact/errors_spec.rb +0 -93
  91. data/spec/ruact/flight/renderer_spec.rb +0 -133
  92. data/spec/ruact/flight/serializer_spec.rb +0 -494
  93. data/spec/ruact/html_converter_spec.rb +0 -375
  94. data/spec/ruact/install_generator_spec.rb +0 -1549
  95. data/spec/ruact/layout_source_spec.rb +0 -108
  96. data/spec/ruact/manifest_resolver_spec.rb +0 -174
  97. data/spec/ruact/query_request_spec.rb +0 -706
  98. data/spec/ruact/query_spec.rb +0 -105
  99. data/spec/ruact/railtie_spec.rb +0 -155
  100. data/spec/ruact/render_context_spec.rb +0 -58
  101. data/spec/ruact/render_pipeline_concurrency_spec.rb +0 -78
  102. data/spec/ruact/render_pipeline_spec.rb +0 -928
  103. data/spec/ruact/scaffold_generator_spec.rb +0 -1849
  104. data/spec/ruact/serializable_spec.rb +0 -179
  105. data/spec/ruact/server_bucket_request_spec.rb +0 -785
  106. data/spec/ruact/server_function_name_spec.rb +0 -53
  107. data/spec/ruact/server_functions/backtrace_cleaner_spec.rb +0 -63
  108. data/spec/ruact/server_functions/bucket_two_payload_spec.rb +0 -200
  109. data/spec/ruact/server_functions/codegen_spec.rb +0 -397
  110. data/spec/ruact/server_functions/error_payload_spec.rb +0 -222
  111. data/spec/ruact/server_functions/error_suggestion_spec.rb +0 -79
  112. data/spec/ruact/server_functions/introspection_spec.rb +0 -135
  113. data/spec/ruact/server_functions/name_bridge_spec.rb +0 -212
  114. data/spec/ruact/server_functions/query_context_spec.rb +0 -72
  115. data/spec/ruact/server_functions/query_source_spec.rb +0 -193
  116. data/spec/ruact/server_functions/railtie_integration_spec.rb +0 -215
  117. data/spec/ruact/server_functions/rake_spec.rb +0 -86
  118. data/spec/ruact/server_functions/route_source_spec.rb +0 -202
  119. data/spec/ruact/server_functions/snapshot_spec.rb +0 -96
  120. data/spec/ruact/server_functions/snapshot_writer_spec.rb +0 -71
  121. data/spec/ruact/server_rescue_request_spec.rb +0 -416
  122. data/spec/ruact/server_spec.rb +0 -179
  123. data/spec/ruact/server_upload_request_spec.rb +0 -311
  124. data/spec/ruact/signed_references_spec.rb +0 -164
  125. data/spec/ruact/string_distance_spec.rb +0 -38
  126. data/spec/ruact/tasks_json_introspection_spec.rb +0 -141
  127. data/spec/ruact/testing/have_ruact_component_spec.rb +0 -170
  128. data/spec/ruact/testing/no_production_load_spec.rb +0 -41
  129. data/spec/ruact/validation_errors_spec.rb +0 -116
  130. data/spec/ruact/view_helper_spec.rb +0 -131
  131. data/spec/spec_helper.rb +0 -77
  132. data/spec/support/fixtures/pixel.png +0 -0
  133. data/spec/support/flight_wire_parser.rb +0 -21
  134. data/spec/support/flight_wire_parser_spec.rb +0 -93
  135. data/spec/support/matchers/flight_fixture_matcher.rb +0 -130
  136. data/spec/support/matchers/flight_fixture_matcher_spec.rb +0 -250
  137. data/spec/support/rails_stub.rb +0 -115
@@ -1,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "ruact"
5
-
6
- # The single definition of "is this layout migrated?", shared by the runtime and
7
- # by `ruact:install`. Every example here is a shape that fooled an earlier,
8
- # looser check — a NAME where only a CALL counts, or a substring where only an
9
- # attribute counts.
10
- RSpec.describe Ruact::LayoutSource do
11
- describe ".wired?" do
12
- it "accepts a plain output tag" do
13
- expect(described_class.wired?("<%= ruact_js_assets %>")).to be true
14
- end
15
-
16
- it "accepts the raw-output form and tight whitespace" do
17
- expect(described_class.wired?("<%==ruact_js_assets%>")).to be true
18
- end
19
-
20
- it "accepts a call with an argument" do
21
- expect(described_class.wired?("<%= ruact_js_assets(payload) %>")).to be true
22
- end
23
-
24
- # Round-1 finding: a mention in prose read as wired.
25
- it "rejects a mention inside an ERB comment" do
26
- expect(described_class.wired?("<%# remember to add ruact_js_assets here %>")).to be false
27
- end
28
-
29
- # Round-2 finding, and the sharper version of the same mistake: a genuinely
30
- # COMMENTED-OUT call. ERB comments do not nest, so this emits nothing — but
31
- # a regex looking only for `<%=` sees a call and reports the layout ready,
32
- # which sends the runtime off to render an unmigrated layout.
33
- it "rejects a commented-out call" do
34
- expect(described_class.wired?("<%# <%= ruact_js_assets %> %>")).to be false
35
- end
36
-
37
- it "rejects a multi-line comment that wraps a call" do
38
- source = <<~ERB
39
- <%#
40
- disabled for now:
41
- <%= ruact_js_assets %>
42
- %>
43
- ERB
44
- expect(described_class.wired?(source)).to be false
45
- end
46
-
47
- it "still sees a real call that follows a comment mentioning it" do
48
- expect(described_class.wired?("<%# ruact_js_assets %>\n<%= ruact_js_assets %>")).to be true
49
- end
50
-
51
- # Round-3 finding: ERB's trim-mode comment (`<%-# ... -%>`) is a comment in
52
- # Erubi too — verified by compiling it — but the stripper only knew `<%#`,
53
- # so a call disabled this way read as wired.
54
- it "rejects a call disabled with a trim-mode comment" do
55
- expect(described_class.wired?("<%-# <%= ruact_js_assets %> -%>")).to be false
56
- end
57
-
58
- # Round-3 finding, the other direction: forbidding `%` before the name to
59
- # avoid crossing tag boundaries also rejected legitimate calls. A false
60
- # "unwired" is safer than a false "wired", but it is still a wrong answer —
61
- # the app would silently keep ruact's CSS-less shell.
62
- it "accepts a call whose expression contains a percent sign" do
63
- expect(described_class.wired?(%(<%= raw("100%") + ruact_js_assets %>))).to be true
64
- expect(described_class.wired?("<%= ruact_js_assets if 50 % 2 == 0 %>")).to be true
65
- end
66
-
67
- it "still refuses to match across a tag boundary" do
68
- expect(described_class.wired?("<%= something %> then a bare ruact_js_assets mention")).to be false
69
- end
70
-
71
- it "rejects a layout that never names it" do
72
- expect(described_class.wired?("<html><body><%= yield %></body></html>")).to be false
73
- end
74
- end
75
-
76
- describe ".root?" do
77
- it "accepts the emitted form" do
78
- expect(described_class.root?(%(<div id="root"></div>))).to be true
79
- end
80
-
81
- it "accepts single quotes, extra attributes and unquoted values" do
82
- expect(described_class.root?(%(<div id='root'></div>))).to be true
83
- expect(described_class.root?(%(<div class="a" id="root" data-x="1"></div>))).to be true
84
- expect(described_class.root?(%(<div id=root></div>))).to be true
85
- end
86
-
87
- # Round-2 finding: `data-id="root"` is not a mount point, and a document
88
- # carrying one but no real root gives React nothing to mount into.
89
- it "rejects a look-alike attribute" do
90
- expect(described_class.root?(%(<body data-id="root"></body>))).to be false
91
- end
92
-
93
- it "rejects a different id that merely starts with root" do
94
- expect(described_class.root?(%(<div id="rootish"></div>))).to be false
95
- end
96
- end
97
-
98
- # The anchor `ruact:install` injects after.
99
- describe "::ROOT_ELEMENT" do
100
- it "anchors on a whole empty root div" do
101
- expect(described_class::ROOT_ELEMENT).to match(%(<div id="root"></div>))
102
- end
103
-
104
- it "does not anchor on a look-alike attribute" do
105
- expect(described_class::ROOT_ELEMENT).not_to match(%(<div data-id="root"></div>))
106
- end
107
- end
108
- end
@@ -1,174 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "spec_helper"
4
- require "net/http"
5
- require "tmpdir"
6
-
7
- module Ruact
8
- RSpec.describe ManifestResolver do
9
- # spec_helper pins resolve/resolve_soft to the boot manifest suite-wide for
10
- # hermeticity; this spec is the one place that exercises the REAL paths.
11
- before do
12
- allow(described_class).to receive(:resolve).and_call_original
13
- allow(described_class).to receive(:resolve_soft).and_call_original
14
- end
15
-
16
- let(:manifest_hash) do
17
- {
18
- "LikeButton" => {
19
- "id" => "/LikeButton.jsx",
20
- "name" => "LikeButton",
21
- "chunks" => ["/LikeButton.jsx"]
22
- }
23
- }
24
- end
25
- let(:manifest_json) { JSON.generate(manifest_hash) }
26
-
27
- # A real Net::HTTPSuccess (HTTPOK < HTTPSuccess) whose body we override, so
28
- # the resolver's `response.is_a?(Net::HTTPSuccess)` branch is exercised for
29
- # real without touching the network.
30
- def http_ok(body)
31
- response = Net::HTTPOK.new("1.1", "200", "OK")
32
- response.define_singleton_method(:body) { body }
33
- response
34
- end
35
-
36
- def in_dev
37
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("development"))
38
- end
39
-
40
- describe ".resolve in development (the boot-race)" do
41
- before do
42
- in_dev
43
- # Simulate the race: boot read the missing file, so the cached manifest is nil.
44
- allow(Ruact).to receive(:manifest).and_return(nil)
45
- end
46
-
47
- it "resolves from the Vite dev server over HTTP (no 500, ref resolvable)" do
48
- allow(Net::HTTP).to receive(:start).and_return(http_ok(manifest_json))
49
-
50
- manifest = described_class.resolve
51
-
52
- expect(manifest).to be_a(ClientManifest)
53
- ref = manifest.reference_for("LikeButton")
54
- expect(ref.module_id).to eq("/LikeButton.jsx")
55
- end
56
-
57
- it "fetches the manifest from the configured vite_dev_server URL" do
58
- allow(Net::HTTP).to receive(:start).and_return(http_ok(manifest_json))
59
-
60
- described_class.resolve
61
-
62
- expect(Net::HTTP).to have_received(:start).with(
63
- "localhost", 5173, hash_including(:open_timeout, :read_timeout)
64
- )
65
- end
66
-
67
- it "does not double-slash the path when vite_dev_server has a trailing slash" do
68
- allow(described_class).to receive(:base_url).and_return("http://localhost:5173/")
69
- http = instance_double(Net::HTTP)
70
- allow(http).to receive(:get).and_return(http_ok(manifest_json))
71
- allow(Net::HTTP).to receive(:start).and_yield(http).and_return(http_ok(manifest_json))
72
-
73
- described_class.resolve
74
-
75
- expect(http).to have_received(:get).with("/__ruact/manifest")
76
- end
77
-
78
- it "speaks TLS when vite_dev_server is https" do
79
- allow(described_class).to receive(:base_url).and_return("https://localhost:5173")
80
- allow(Net::HTTP).to receive(:start).and_return(http_ok(manifest_json))
81
-
82
- described_class.resolve
83
-
84
- expect(Net::HTTP).to have_received(:start).with(
85
- "localhost", 5173, hash_including(use_ssl: true)
86
- )
87
- end
88
-
89
- it "falls back to the on-disk file when the dev server is unreachable" do
90
- allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
91
-
92
- Dir.mktmpdir do |dir|
93
- path = File.join(dir, "react-client-manifest.json")
94
- File.write(path, manifest_json)
95
- allow(described_class).to receive(:file_path).and_return(path)
96
-
97
- manifest = described_class.resolve
98
- expect(manifest).to be_a(ClientManifest)
99
- expect(manifest.reference_for("LikeButton").module_id).to eq("/LikeButton.jsx")
100
- end
101
- end
102
-
103
- it "raises a clear, actionable error (not NoMethodError) when neither HTTP nor file is available" do
104
- allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
105
- allow(described_class).to receive(:file_path).and_return("/no/such/manifest.json")
106
-
107
- expect { described_class.resolve }.to raise_error(ManifestError, %r{Vite dev server.*bin/dev}m)
108
- end
109
-
110
- # Story 15.0 (F8) — an error message is a prompt: agents (and humans)
111
- # regex-match error text, so the ENGLISH wording and its stable tokens
112
- # (`Vite dev server`, `react-client-manifest.json`, `bin/dev`) are pinned.
113
- it "raises the ENGLISH message naming the dev-server URL, the manifest path and the bin/dev fix " \
114
- "(Story 15.0)", :story_15_0 do
115
- allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
116
- allow(described_class).to receive(:file_path).and_return("/no/such/manifest.json")
117
-
118
- expect { described_class.resolve }.to raise_error(ManifestError) do |error|
119
- expect(error.message).to include("[ruact] Vite dev server unreachable at http://localhost:5173")
120
- expect(error.message).to include("no react-client-manifest.json found at /no/such/manifest.json")
121
- expect(error.message).to include("run `bin/dev`")
122
- end
123
- end
124
-
125
- it "ignores a non-2xx dev-server response and falls back" do
126
- allow(Net::HTTP).to receive(:start).and_return(Net::HTTPNotFound.new("1.1", "404", "Not Found"))
127
- allow(described_class).to receive(:file_path).and_return("/no/such/manifest.json")
128
-
129
- expect { described_class.resolve }.to raise_error(ManifestError)
130
- end
131
- end
132
-
133
- describe ".resolve_soft in development (fail-open for FR100)" do
134
- before do
135
- in_dev
136
- allow(Ruact).to receive(:manifest).and_return(nil)
137
- end
138
-
139
- it "returns nil (no raise) when nothing is resolvable" do
140
- allow(Net::HTTP).to receive(:start).and_raise(Errno::ECONNREFUSED)
141
- allow(described_class).to receive(:file_path).and_return("/no/such/manifest.json")
142
-
143
- expect(described_class.resolve_soft).to be_nil
144
- end
145
-
146
- it "returns the dev manifest when the server is up" do
147
- allow(Net::HTTP).to receive(:start).and_return(http_ok(manifest_json))
148
- expect(described_class.resolve_soft).to be_a(ClientManifest)
149
- end
150
- end
151
-
152
- describe "production / non-development (untouched)" do
153
- it "returns Ruact.manifest verbatim without any HTTP call" do
154
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("production"))
155
- allow(Net::HTTP).to receive(:start)
156
- boot_manifest = ClientManifest.from_hash(manifest_hash)
157
- allow(Ruact).to receive(:manifest).and_return(boot_manifest)
158
-
159
- expect(described_class.resolve).to be(boot_manifest)
160
- expect(Net::HTTP).not_to have_received(:start)
161
- end
162
-
163
- it "returns Ruact.manifest in the test environment too (no dev fetch)" do
164
- allow(Rails).to receive(:env).and_return(ActiveSupport::StringInquirer.new("test"))
165
- allow(Net::HTTP).to receive(:start)
166
- boot_manifest = ClientManifest.from_hash(manifest_hash)
167
- allow(Ruact).to receive(:manifest).and_return(boot_manifest)
168
-
169
- expect(described_class.resolve).to be(boot_manifest)
170
- expect(Net::HTTP).not_to have_received(:start)
171
- end
172
- end
173
- end
174
- end