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 +4 -4
- data/CHANGELOG.md +21 -0
- data/lib/react_manifest/generator.rb +53 -7
- data/lib/react_manifest/scanner.rb +6 -18
- data/lib/react_manifest/version.rb +1 -1
- data/lib/react_manifest/view_helpers.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d9e8231e4932b06cc8cd5d4684cf0361be694e0a0ae6e677e5bea4d3dfa2c17e
|
|
4
|
+
data.tar.gz: 4d1f52eb5463788fac2eb5cce0e777081f11f56a1401a7eab15fbc8cb8601cf6
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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,
|
|
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
|
-
|
|
50
|
-
classification.controller_dirs.
|
|
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 = (
|
|
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
|
-
|
|
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*\(/,
|
|
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
|
-
|
|
226
|
-
|
|
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
|
-
|
|
256
|
-
|
|
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
|
|