react-manifest-rails 0.2.23 → 0.2.25

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 907e5e7bb8b3e6a8554009c4e8a87a39379d8bd09c4455fd9c02b082a014c18b
4
- data.tar.gz: 11d4e6ca8833973107937eee1b82881f620f3b2e74442ab0883521ea5803d58b
3
+ metadata.gz: d9e8231e4932b06cc8cd5d4684cf0361be694e0a0ae6e677e5bea4d3dfa2c17e
4
+ data.tar.gz: 4d1f52eb5463788fac2eb5cce0e777081f11f56a1401a7eab15fbc8cb8601cf6
5
5
  SHA512:
6
- metadata.gz: 950abc3b34f1ffe0e8eb6fcd4ae7798469b60746531d0bb0171de0975881cb2572f35182e07785255fcf932ce14ccaac369a1238e2d5e2fe3b8689d323861d06
7
- data.tar.gz: dc6fcbfb087a8aa9f95ff109147ee0345b2aa32fc1a0281118ffb60d12313735d659ecb55200832eb940bf6def8675ab711e64208fcc0ec17514e71e0ea57f42
6
+ metadata.gz: 20ab3b9ad41551d772ac6fe89a17676728afe52d688ebd6b61cd4d7cb7d169cc246003a3c38ac468d8f8e3c10ec26e80e02330f78cebdad4a9a26a82cf700020
7
+ data.tar.gz: a3829ad0e2f0eb4f7d3cf61ce31bad0a570b40a6fb96082d37da692c9826c3d60c7e72710d64b5b014b9cb7c1fba18b85169d8d62fd3033fda66a67e652df1ae
data/CHANGELOG.md CHANGED
@@ -7,6 +7,27 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.25] - 2026-04-22
11
+
12
+ ### Fixed
13
+ - Restored `build_shared` method in `Generator` that had been deleted, causing `ux_shared.js` to never be written. This was the root cause of a major regression where shared components (`components/`, `hooks/`, `lib/`) were absent from the asset pipeline on clean deploys.
14
+ - `Generator#run!` now generates `ux_shared.js` (all files from shared dirs) as the first manifest before controller manifests.
15
+ - Controller manifests (`ux_<controller>.js`) no longer inline shared-dir files (`lib_reqs`, `shared_reqs`). Shared files live exclusively in `ux_shared.js`, restoring the original lean-manifest architecture and preventing duplication.
16
+ - `resolve_bundles` view helper no longer silently drops the shared bundle when `ux_shared.js` is absent; the file is now always generated so the helper correctly returns `[ux_shared, ux_<controller>]` for every page.
17
+
18
+ ### Added
19
+ - Release preflight hook (`.github/hooks/release-preflight.json` + script) that blocks `git tag`/`git push` commands when `VERSION`, `CHANGELOG.md`, and `Gemfile.lock` are out of sync.
20
+ - Session-start hook (`.github/hooks/release-session.json`) that injects the release protocol into the Copilot agent context.
21
+
22
+ ## [0.2.24] - 2026-04-22
23
+
24
+ ### Fixed
25
+ - Controller manifests now inline files from bundles listed in `always_include` (for example `ux_main`), so runtime symbol availability no longer depends on cross-bundle script execution order in production.
26
+ - Scanner analysis no longer emits warnings for ux/app file naming convention mismatches, reducing noise for apps that intentionally use custom filename patterns.
27
+
28
+ ### Changed
29
+ - Updated `Gemfile.lock` to keep lockfile state aligned with the released codebase.
30
+
10
31
  ## [0.2.23] - 2026-04-21
11
32
 
12
33
  ### Fixed
@@ -43,11 +43,12 @@ module ReactManifest
43
43
  def run!
44
44
  classification = @classifier.classify
45
45
  scan_result = Scanner.new(@config).scan(classification)
46
- controller_context = build_controller_context(classification.controller_dirs, classification.shared_dirs, scan_result)
46
+ controller_context = build_controller_context(classification.controller_dirs, classification.shared_dirs,
47
+ scan_result)
47
48
 
48
49
  # Phase 1: build all content in memory — no I/O.
49
- manifests = []
50
- classification.controller_dirs.each { |ctrl| manifests << build_controller(ctrl, controller_context) }
50
+ shared_manifest = build_shared(classification.shared_dirs)
51
+ manifests = [shared_manifest] + classification.controller_dirs.map { |ctrl| build_controller(ctrl, controller_context) }
51
52
 
52
53
  migrate_legacy_manifests!
53
54
 
@@ -62,12 +63,28 @@ module ReactManifest
62
63
 
63
64
  # ------------------------------------------------------------------ shared
64
65
 
66
+ def build_shared(shared_dirs)
67
+ lines = header_lines
68
+ reqs = shared_dirs
69
+ .flat_map { |d| js_files_in(d[:path]) }
70
+ .map { |f| normalize_require_path(relative_require_path(f)) }
71
+ .uniq
72
+ .sort
73
+
74
+ if reqs.empty?
75
+ lines << "// (no shared JS files found)"
76
+ else
77
+ reqs.each { |req| lines << "//= require #{req}" }
78
+ end
65
79
 
80
+ { filename: "#{@config.shared_bundle}.js", content: "#{lines.join("\n")}\n" }
81
+ end
66
82
 
67
83
  # --------------------------------------------------------------- controller
68
84
 
69
85
  def build_controller(ctrl, controller_context)
70
86
  lines = header_lines
87
+ always_include_reqs = controller_context[:always_include_requires].fetch(ctrl[:bundle_name], [])
71
88
  dep_requires = controller_dependency_requires(ctrl[:bundle_name], controller_context)
72
89
  lib_reqs = controller_context[:shared_lib_requires]
73
90
  shared_reqs = controller_context[:shared_requires].fetch(ctrl[:bundle_name], Set.new).to_a.sort
@@ -75,7 +92,7 @@ module ReactManifest
75
92
 
76
93
  files = js_files_in(ctrl[:path])
77
94
  own_requires = files.map { |f| relative_require_path(f) }
78
- all_requires = (dep_requires + lib_reqs + shared_reqs + ext_reqs + own_requires).uniq
95
+ all_requires = (always_include_reqs + dep_requires + ext_reqs + own_requires).uniq
79
96
 
80
97
  if all_requires.empty?
81
98
  lines << "// (no JSX files found in #{ctrl[:name]}/)"
@@ -86,6 +103,7 @@ module ReactManifest
86
103
  { filename: "#{ctrl[:bundle_name]}.js", content: "#{lines.join("\n")}\n" }
87
104
  end
88
105
 
106
+ # rubocop:disable Metrics/AbcSize
89
107
  def build_controller_context(controller_dirs, shared_dirs, scan_result)
90
108
  bundle_files = {}
91
109
  symbol_to_bundle = {}
@@ -103,10 +121,8 @@ module ReactManifest
103
121
  end
104
122
  shared_requires[ctrl[:bundle_name]] = expand_shared_requires(shared_requires[ctrl[:bundle_name]],
105
123
  shared_dependency_map)
106
- end
107
124
 
108
- # Index controller-defined symbols for cross-app detection
109
- controller_dirs.each do |ctrl|
125
+ # Index controller-defined symbols for cross-app detection
110
126
  bundle_name = ctrl[:bundle_name]
111
127
  files = js_files_in(ctrl[:path])
112
128
  bundle_files[bundle_name] = files
@@ -152,14 +168,18 @@ module ReactManifest
152
168
  end
153
169
  end
154
170
 
171
+ always_include_requires = build_always_include_requires(bundle_files, dependencies)
172
+
155
173
  {
156
174
  bundle_files: bundle_files,
157
175
  dependencies: dependencies,
176
+ always_include_requires: always_include_requires,
158
177
  shared_lib_requires: shared_lib_requires,
159
178
  shared_requires: shared_requires,
160
179
  external_requires: external_requires
161
180
  }
162
181
  end
182
+ # rubocop:enable Metrics/AbcSize
163
183
 
164
184
  def controller_dependency_requires(bundle_name, controller_context)
165
185
  deps = transitive_dependencies(bundle_name, controller_context[:dependencies])
@@ -189,6 +209,32 @@ module ReactManifest
189
209
  ordered
190
210
  end
191
211
 
212
+ def build_always_include_requires(bundle_files, dependencies)
213
+ bundles = @config.always_include.map(&:to_s).reject(&:empty?).uniq
214
+ return Hash.new { |h, k| h[k] = [] } if bundles.empty?
215
+
216
+ requires_by_bundle = Hash.new { |h, k| h[k] = [] }
217
+
218
+ bundle_files.each_key do |bundle_name|
219
+ requires = Set.new
220
+
221
+ bundles.each do |always_bundle|
222
+ next if always_bundle == bundle_name
223
+
224
+ transitive = [always_bundle] + transitive_dependencies(always_bundle, dependencies)
225
+ transitive.each do |dep_bundle|
226
+ bundle_files.fetch(dep_bundle, []).each do |abs_path|
227
+ requires << relative_require_path(abs_path)
228
+ end
229
+ end
230
+ end
231
+
232
+ requires_by_bundle[bundle_name] = requires.to_a.sort
233
+ end
234
+
235
+ requires_by_bundle
236
+ end
237
+
192
238
  # --------------------------------------------------------------- write
193
239
 
194
240
  def write_manifest(filename, content)
@@ -23,7 +23,7 @@ module ReactManifest
23
23
  /function\s+([A-Z][A-Za-z0-9_]*)\s*\(/, # function FooBar(
24
24
  /class\s+([A-Z][A-Za-z0-9_]*)\s*(?:extends|\{)/, # class FooBar
25
25
  /(?:const|let|var)\s+(use[A-Z][A-Za-z0-9_]*)\s*=/, # const useFoo = (hooks)
26
- /function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/, # function useFoo(
26
+ /function\s+(use[A-Z][A-Za-z0-9_]*)\s*\(/, # function useFoo(
27
27
 
28
28
  # ES module style (export default / named exports)
29
29
  /^export\s+default\s+(?:function|class)\s+([A-Z][A-Za-z0-9_]*)/,
@@ -130,7 +130,6 @@ module ReactManifest
130
130
  warnings.add("Controller dir '#{ctrl[:name]}' has no JS/JSX files") if files.empty? && @config.verbose?
131
131
 
132
132
  files.each do |file_path|
133
- validate_naming(file_path, ctrl[:name], warnings)
134
133
  content = read_controller_file(file_path, warnings)
135
134
  next unless content
136
135
 
@@ -191,15 +190,6 @@ module ReactManifest
191
190
  rel.sub(/\.js\.jsx$/, "").sub(/\.jsx$/, "").sub(/\.js$/, "")
192
191
  end
193
192
 
194
- def validate_naming(file_path, ctrl_name, warnings)
195
- basename = File.basename(file_path, ".*").sub(/\.js$/, "")
196
- # Expected: <controller>_index, <controller>_show, <controller>_form, etc.
197
- return if basename.start_with?("#{ctrl_name}_") || basename == ctrl_name
198
-
199
- warnings.add("File '#{File.basename(file_path)}' in '#{ctrl_name}' does not follow " \
200
- "'#{ctrl_name}_<action>.js.jsx' naming convention")
201
- end
202
-
203
193
  def detect_shared_violations(shared_file_paths, controller_symbol_index, warnings)
204
194
  violations = []
205
195
  shared_file_paths.each do |file_path, relative|
@@ -222,8 +212,8 @@ module ReactManifest
222
212
  violations << { shared_file: relative, symbol: sym,
223
213
  controller: info[:controller], app_file: info[:file] }
224
214
  warnings.add("Shared file '#{relative}' uses app-dir symbol '#{sym}' " \
225
- "(from ux/app/#{info[:controller]}). " \
226
- "Move '#{sym}' to a shared dir or the shared file will be incomplete.")
215
+ "(from ux/app/#{info[:controller]}). " \
216
+ "Move '#{sym}' to a shared dir or the shared file will be incomplete.")
227
217
  end
228
218
  end
229
219
  end
@@ -252,8 +242,8 @@ module ReactManifest
252
242
  violations << { external_file: relative, symbol: sym,
253
243
  controller: info[:controller], app_file: info[:file] }
254
244
  warnings.add("External file '#{relative}' uses app-dir symbol '#{sym}' " \
255
- "(from ux/app/#{info[:controller]}). " \
256
- "Move '#{sym}' into a shared ux dir to avoid duplicate runtime declarations.")
245
+ "(from ux/app/#{info[:controller]}). " \
246
+ "Move '#{sym}' into a shared ux dir to avoid duplicate runtime declarations.")
257
247
  end
258
248
  end
259
249
  end
@@ -268,9 +258,7 @@ module ReactManifest
268
258
  end
269
259
 
270
260
  fanout.each do |file, count|
271
- if count > 3
272
- warnings.add("High fan-out: '#{file}' is used by #{count} controllers")
273
- end
261
+ warnings.add("High fan-out: '#{file}' is used by #{count} controllers") if count > 3
274
262
  end
275
263
  end
276
264
 
@@ -1,3 +1,3 @@
1
1
  module ReactManifest
2
- VERSION = "0.2.23".freeze
2
+ VERSION = "0.2.25".freeze
3
3
  end
@@ -75,7 +75,7 @@ module ReactManifest
75
75
  if respond_to?(:request, true) && request
76
76
  request.env["react_manifest.emitted_bundles"] ||= []
77
77
  else
78
- @_react_manifest_emitted_bundles ||= []
78
+ @emitted_bundles ||= []
79
79
  end
80
80
  end
81
81
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: react-manifest-rails
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.23
4
+ version: 0.2.25
5
5
  platform: ruby
6
6
  authors:
7
7
  - Oliver Noonan