Skip to content

Commit 3a58396

Browse files
justin808claude
andcommitted
Improve precompile validation with empty output detection and gem errors
- Detect empty output (0 bytes) as failure, indicating command failed before producing any output - Add warning for suspiciously small output (<10 lines by default) - Add Pattern 5b: Detect bundler/gem errors like "Could not find gem" - Fix shellcheck warning: use single quotes in trap command - Update usage docs to emphasize pipefail requirement when piping - Add timeout-minutes: 15 comment to CI workflow step The empty output detection addresses the case where rake fails immediately (e.g., missing gem) but produces no stdout, causing the validation to incorrectly pass when piped without pipefail. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 3bc8ea0 commit 3a58396

File tree

2 files changed

+118
-18
lines changed

2 files changed

+118
-18
lines changed

.github/workflows/precompile-check.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,9 @@ jobs:
133133
- name: Generate file system-based packs
134134
run: cd react_on_rails/spec/dummy && RAILS_ENV=production bundle exec rake react_on_rails:generate_packs
135135
- name: Run assets:precompile and capture output
136+
# Timeout prevents hung webpack processes from running for 6 hours.
137+
# Typical precompile takes 2-5 minutes; 15 minutes is generous.
138+
timeout-minutes: 15
136139
run: |
137140
cd react_on_rails/spec/dummy
138141

script/validate-precompile-output

Lines changed: 115 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,27 @@
44
#
55
# Usage:
66
# script/validate-precompile-output <output_file>
7+
#
8+
# # When piping, MUST use pipefail to catch rake failures:
9+
# set -o pipefail
710
# RAILS_ENV=production rake assets:precompile 2>&1 | script/validate-precompile-output -
811
#
12+
# # Or use tee to capture output and check exit code separately:
13+
# RAILS_ENV=production rake assets:precompile 2>&1 | tee output.txt
14+
# if [ ${PIPESTATUS[0]} -ne 0 ]; then echo "Rake failed!"; exit 1; fi
15+
# script/validate-precompile-output output.txt
16+
#
917
# Exit codes:
1018
# 0 - All checks passed
1119
# 1 - One or more failure patterns detected
1220
#
1321
# This script checks for known issues that don't cause precompile to fail
1422
# but indicate bugs like duplicate task execution or missing dependencies.
1523
#
24+
# Environment variables:
25+
# MIN_EXPECTED_LINES - Minimum lines expected in output (default: 10)
26+
# WARNING_THRESHOLD - Warning count threshold before alerting (default: 20)
27+
#
1628
# See: https://github.com/shakacode/react_on_rails/issues/2081
1729

1830
set -e
@@ -24,14 +36,39 @@ if [ "$OUTPUT_FILE" = "-" ]; then
2436
TEMP_FILE=$(mktemp)
2537
cat > "$TEMP_FILE"
2638
OUTPUT_FILE="$TEMP_FILE"
27-
trap "rm -f $TEMP_FILE" EXIT
39+
trap 'rm -f "$TEMP_FILE"' EXIT
2840
fi
2941

3042
if [ ! -f "$OUTPUT_FILE" ]; then
3143
echo "Error: Output file '$OUTPUT_FILE' not found"
3244
exit 1
3345
fi
3446

47+
# Check for empty or suspiciously small output
48+
# A successful precompile produces substantial output (webpack stats, asset listing, etc.)
49+
# Empty or very small output usually indicates the command failed before producing anything.
50+
OUTPUT_SIZE=$(wc -c < "$OUTPUT_FILE" | tr -d ' ')
51+
OUTPUT_LINES=$(wc -l < "$OUTPUT_FILE" | tr -d ' ')
52+
53+
if [ "$OUTPUT_SIZE" -eq 0 ]; then
54+
echo "Error: Output file is empty."
55+
echo "This usually means the precompile command failed before producing any output."
56+
echo "Check that all dependencies are installed (bundle install, pnpm install)."
57+
exit 1
58+
fi
59+
60+
# Minimum expected lines for a successful precompile (webpack output alone is 50+ lines)
61+
MIN_EXPECTED_LINES="${MIN_EXPECTED_LINES:-10}"
62+
if [ "$OUTPUT_LINES" -lt "$MIN_EXPECTED_LINES" ]; then
63+
echo "Warning: Output file has only $OUTPUT_LINES lines (expected at least $MIN_EXPECTED_LINES)."
64+
echo "This may indicate the precompile failed early. Output contents:"
65+
echo "---"
66+
cat "$OUTPUT_FILE"
67+
echo "---"
68+
echo ""
69+
echo "If this is expected, set MIN_EXPECTED_LINES=0 to disable this check."
70+
fi
71+
3572
echo "Validating assets:precompile output..."
3673
echo ""
3774

@@ -57,8 +94,14 @@ report_warning() {
5794
fi
5895
}
5996

97+
# ==============================================================================
6098
# Pattern 1: Duplicate webpack compilation (indicates rake tasks running twice)
61-
# Look for webpack's "Compiled successfully" message which appears once per compilation
99+
# ==============================================================================
100+
# Prevents: Rails::Engine loading rake tasks twice, causing double webpack builds
101+
# History: PR #2052 fixed this - Engine's rake_tasks block was explicitly loading
102+
# tasks from lib/tasks/, but Rails::Engine already does this automatically.
103+
# Symptom: "Compiled successfully" appears 2+ times in output, doubling build time.
104+
# See: https://github.com/shakacode/react_on_rails/pull/2052
62105
if grep -q "Compiled successfully" "$OUTPUT_FILE"; then
63106
COMPILE_SUCCESS_COUNT=$(grep -c "Compiled successfully" "$OUTPUT_FILE" || true)
64107
if [ "$COMPILE_SUCCESS_COUNT" -gt 1 ]; then
@@ -70,7 +113,12 @@ if grep -q "Compiled successfully" "$OUTPUT_FILE"; then
70113
fi
71114
fi
72115

73-
# Pattern 2: Duplicate task execution messages (generate_packs)
116+
# ==============================================================================
117+
# Pattern 2: Duplicate task execution (react_on_rails rake tasks)
118+
# ==============================================================================
119+
# Prevents: Same root cause as Pattern 1 - rake tasks executing twice.
120+
# Symptom: react_on_rails:generate_packs or react_on_rails:locale appear multiple times.
121+
# These tasks generate files, so running twice wastes time and may cause issues.
74122
if grep -q "react_on_rails:generate_packs" "$OUTPUT_FILE"; then
75123
GENERATE_PACKS_COUNT=$(grep -c "react_on_rails:generate_packs" "$OUTPUT_FILE" || true)
76124
if [ "$GENERATE_PACKS_COUNT" -gt 1 ]; then
@@ -81,7 +129,7 @@ if grep -q "react_on_rails:generate_packs" "$OUTPUT_FILE"; then
81129
fi
82130
fi
83131

84-
# Pattern 2b: Duplicate locale generation
132+
# Pattern 2b: Duplicate locale generation (same root cause as 2a)
85133
if grep -q "react_on_rails:locale" "$OUTPUT_FILE"; then
86134
LOCALE_COUNT=$(grep -c "react_on_rails:locale" "$OUTPUT_FILE" || true)
87135
if [ "$LOCALE_COUNT" -gt 1 ]; then
@@ -92,7 +140,7 @@ if grep -q "react_on_rails:locale" "$OUTPUT_FILE"; then
92140
fi
93141
fi
94142

95-
# Pattern 2c: Duplicate webpack builds (check "Built at:" messages)
143+
# Pattern 2c: Duplicate webpack builds (alternative detection via "Built at:" timestamp)
96144
BUILT_AT_COUNT=$(grep -c "Built at:" "$OUTPUT_FILE" || true)
97145
if [ "$BUILT_AT_COUNT" -gt 1 ]; then
98146
report_error "Detected $BUILT_AT_COUNT webpack builds (expected 1). Tasks may be running twice."
@@ -101,63 +149,112 @@ if [ "$BUILT_AT_COUNT" -gt 1 ]; then
101149
FAILURES_FOUND=1
102150
fi
103151

104-
# Pattern 3: Module not found errors
105-
if grep -Ei "module not found|cannot find module|can't resolve" "$OUTPUT_FILE"; then
152+
# ==============================================================================
153+
# Pattern 3: Module resolution errors
154+
# ==============================================================================
155+
# Prevents: Missing npm packages or incorrect import paths that precompile may not fail on.
156+
# Important: Uses strict matching to avoid false positives from deprecation warnings
157+
# or informational messages that contain similar phrases.
158+
# Only matches when preceded by ERROR, Error:, or webpack's "Module not found: Error:" format.
159+
if grep -Ei "(^|\s)(ERROR|Error:).*module not found|(^|\s)(ERROR|Error:).*cannot find module|(^|\s)(ERROR|Error:).*can't resolve|Module not found: Error:" "$OUTPUT_FILE"; then
106160
report_error "Module resolution errors detected in precompile output."
107161
echo " Sample matching lines:"
108-
grep -Ei "module not found|cannot find module|can't resolve" "$OUTPUT_FILE" | head -3
162+
grep -Ei "(^|\s)(ERROR|Error:).*module not found|(^|\s)(ERROR|Error:).*cannot find module|(^|\s)(ERROR|Error:).*can't resolve|Module not found: Error:" "$OUTPUT_FILE" | head -3
109163
FAILURES_FOUND=1
110164
fi
111165

112-
# Pattern 3b: ENOENT errors (missing files/directories)
166+
# Pattern 3b: ENOENT errors (missing files/directories during build)
113167
if grep -q "Error: ENOENT" "$OUTPUT_FILE"; then
114168
report_error "Missing file or directory errors detected."
115169
echo " Sample matching lines:"
116170
grep -n "Error: ENOENT" "$OUTPUT_FILE" | head -3
117171
FAILURES_FOUND=1
118172
fi
119173

120-
# Pattern 4: Webpack build errors (use specific webpack error markers)
121-
if grep -Ei "webpack.*error|failed to compile|compilation failed|ERROR in" "$OUTPUT_FILE"; then
174+
# ==============================================================================
175+
# Pattern 4: Webpack compilation errors
176+
# ==============================================================================
177+
# Prevents: Silent webpack build failures that don't cause precompile to exit non-zero.
178+
# Important: Uses specific error markers to avoid false positives from benign text
179+
# like "webpack handles errors gracefully" in documentation or comments.
180+
# Matches:
181+
# - "^ERROR in ./": Webpack's standard error format for file-specific errors
182+
# - "failed to compile": Direct compilation failure message
183+
# - "compilation failed": Explicit failure message
184+
# - "Module build failed": Loader/build errors (e.g., babel, css-loader failures)
185+
if grep -Ei "^ERROR in [./]|failed to compile|compilation failed|Module build failed" "$OUTPUT_FILE"; then
122186
report_error "Webpack compilation errors detected."
123187
echo " Sample matching lines:"
124-
grep -Ei "webpack.*error|failed to compile|compilation failed|ERROR in" "$OUTPUT_FILE" | head -3
188+
grep -Ei "^ERROR in [./]|failed to compile|compilation failed|Module build failed" "$OUTPUT_FILE" | head -3
125189
FAILURES_FOUND=1
126190
fi
127191

128-
# Pattern 5: Ruby/Rails errors during precompile (match error class format)
192+
# ==============================================================================
193+
# Pattern 5: Ruby/Rails errors during precompile
194+
# ==============================================================================
195+
# Prevents: Ruby exceptions that are caught/logged but don't fail the rake task.
196+
# Matches specific exception class names to avoid false positives.
129197
if grep -E "(NameError|LoadError|NoMethodError|SyntaxError):" "$OUTPUT_FILE"; then
130198
report_error "Ruby errors detected during precompile."
131199
echo " Sample matching lines:"
132200
grep -E "(NameError|LoadError|NoMethodError|SyntaxError):" "$OUTPUT_FILE" | head -3
133201
FAILURES_FOUND=1
134202
fi
135203

136-
# Pattern 6: Asset pipeline errors
204+
# Pattern 5b: Bundler/Gem errors (missing or version mismatch)
205+
# Detects errors like "Could not find gem 'shakapacker (= 9.4.0)'"
206+
# These indicate bundle install wasn't run or Gemfile.lock is out of sync.
207+
if grep -Ei "Could not find gem|Run \`bundle install\`|Bundler::GemNotFound|Gem::MissingSpecError" "$OUTPUT_FILE"; then
208+
report_error "Bundler/gem dependency errors detected."
209+
echo " This usually means 'bundle install' needs to be run."
210+
echo " Sample matching lines:"
211+
grep -Ei "Could not find gem|Run \`bundle install\`|Bundler::GemNotFound|Gem::MissingSpecError" "$OUTPUT_FILE" | head -3
212+
FAILURES_FOUND=1
213+
fi
214+
215+
# ==============================================================================
216+
# Pattern 6: Sprockets/Asset pipeline errors
217+
# ==============================================================================
218+
# Prevents: Missing assets that Sprockets can't find or undeclared asset dependencies.
137219
if grep -Ei "Sprockets::FileNotFound|Asset.*was not declared" "$OUTPUT_FILE"; then
138220
report_error "Asset pipeline errors detected."
139221
echo " Sample matching lines:"
140222
grep -Ei "Sprockets::FileNotFound|Asset.*was not declared" "$OUTPUT_FILE" | head -3
141223
FAILURES_FOUND=1
142224
fi
143225

226+
# ==============================================================================
144227
# Pattern 7: Memory issues
228+
# ==============================================================================
229+
# Prevents: OOM kills that can happen silently with webpack on large codebases.
230+
# Note: "killed" alone may have false positives; consider tightening if needed.
145231
if grep -Ei "javascript heap out of memory|killed|out of memory" "$OUTPUT_FILE"; then
146232
report_error "Memory-related errors detected."
147233
echo " Sample matching lines:"
148234
grep -Ei "javascript heap out of memory|killed|out of memory" "$OUTPUT_FILE" | head -3
149235
FAILURES_FOUND=1
150236
fi
151237

152-
# Pattern 8: Check for warnings that might indicate problems
238+
# ==============================================================================
239+
# Pattern 8: Warning count threshold (advisory, does not fail)
240+
# ==============================================================================
241+
# Purpose: Alert when warnings spike, which may indicate new issues worth investigating.
242+
# Threshold is configurable via WARNING_THRESHOLD env var.
243+
# Default of 20 chosen based on typical precompile output having ~10-15 benign warnings
244+
# from node deprecations and peer dependency notices.
245+
WARNING_THRESHOLD="${WARNING_THRESHOLD:-20}"
153246
WARNING_COUNT=$(grep -ci "warning" "$OUTPUT_FILE" || true)
154-
if [ "$WARNING_COUNT" -gt 10 ]; then
155-
report_warning "High number of warnings detected: $WARNING_COUNT warnings found. Please review."
247+
if [ "$WARNING_COUNT" -gt "$WARNING_THRESHOLD" ]; then
248+
report_warning "High number of warnings detected: $WARNING_COUNT warnings found (threshold: $WARNING_THRESHOLD). Please review."
156249
echo " Sample warnings:"
157250
grep -i "warning" "$OUTPUT_FILE" | head -5
158251
fi
159252

160-
# Pattern 9: Deprecation warnings (log for visibility, don't fail)
253+
# ==============================================================================
254+
# Pattern 9: Deprecation warnings (advisory, does not fail)
255+
# ==============================================================================
256+
# Purpose: Track deprecations that may cause future breakage after upgrades.
257+
# Rails deprecations are logged with "DEPRECATION" prefix.
161258
DEPRECATION_COUNT=$(grep -c "DEPRECATION" "$OUTPUT_FILE" || true)
162259
if [ "$DEPRECATION_COUNT" -gt 0 ]; then
163260
report_warning "Found $DEPRECATION_COUNT deprecation warnings. These may indicate future breakage."

0 commit comments

Comments
 (0)