railpack 1.2.17 → 1.3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 46b9c7c2b58bd90c44c1c5190e784c6eabf29454802854b816872d5b3aa43de1
4
- data.tar.gz: 125b3c1c1aed10fc5fc805d50f55a553d857d2981ea11a60f2054e302e11ecf2
3
+ metadata.gz: f0e77f850d344d1b29f2e275c3cd49be7a4a684d0f3d5c880d03146b5cbeed6b
4
+ data.tar.gz: 2d2874defb0fd76f9c2c0ca3d4e746edf9067ce59fba07ef151ff096ffad5016
5
5
  SHA512:
6
- metadata.gz: 1e73c7157d97946a72d56115d8fd79df65305fb86738d7d319d9ef9a9acebd48d7dfaedd313b5a6b9d73446d8bc25ed62ef749462d93a588163e44581f4091d2
7
- data.tar.gz: 2d3c9ea8212dec04b2b9afbd2a51798ec8f11a9d7f0315d63b8cfe9c3c909a04fa38bf4112e8cdd3c8c58b4a32bf6eccb86abbbc0dff5920fa6a185763510935
6
+ metadata.gz: e7308ab9513cfbdb703dac82f3534b8382948e9ee705dc39505014ad951141e3e75c7c2277977e20912975236b9f0efbe9acecf0032ad4203723a273f48cb8d9
7
+ data.tar.gz: 652705db3d4555423ea5ddcfbf8aecb6aa3584063a1c594934594052e5d7ec370c8ac44e7804092963a5b550f740fc15b414e86d03cb53811f732a0638b384d6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.3.1] - 2026-01-26
4
+
5
+ This patch release includes final polish and documentation improvements.
6
+
7
+ ### Changes
8
+
9
+ - **Code style**: Properly indented private methods and removed duplicate `private` keyword
10
+ - **Pre-build validation**: Added `FileUtils.mkdir_p(outdir)` to ensure output directories exist before build
11
+ - **Documentation**: Added comprehensive examples for `analyze_bundle` (gzip output), build hooks (payload details), and manifest delegation
12
+
13
+ ## [1.3.0] - 2026-01-26
14
+
15
+ ### 🚀 **Major Architecture Refactoring**
16
+
17
+ This release includes comprehensive refactoring of Railpack's two core classes, representing a significant architectural improvement while maintaining full backward compatibility.
18
+
19
+ #### ✨ **Config Class Overhaul (Railpack::Config)**
20
+ - **Security Hardening**: Implemented YAML safe loading with `permitted_classes: [], aliases: false`
21
+ - **Deep Immutability**: All configurations are now deep-frozen to prevent runtime mutations
22
+ - **Production Validation**: Critical settings validation in production environment
23
+ - **Developer Experience**: Explicit accessor methods, comprehensive documentation, deprecation warnings
24
+ - **Performance**: Cached configurations per environment, thread-safe access
25
+
26
+ #### 🏗️ **Manager Class Refactoring (Railpack::Manager)**
27
+ - **Manifest Extraction**: Created dedicated `Railpack::Manifest::Propshaft` and `::Sprockets` classes
28
+ - **Improved Pipeline Detection**: Direct `Rails.application.config.assets` class inspection
29
+ - **Enhanced Bundle Analysis**: Optional gzip size reporting for realistic metrics
30
+ - **Better Error Context**: Rich manifest generation error messages with pipeline type and asset counts
31
+ - **Pre-build Validation**: Output directory existence warnings before build starts
32
+
33
+ #### 📊 **Architecture Improvements**
34
+ - **Separation of Concerns**: Manifest generation isolated from orchestration logic
35
+ - **Testability**: Core logic now independently testable with 75 tests passing
36
+ - **Maintainability**: Smaller, focused classes with single responsibilities
37
+ - **Extensibility**: Easy to add new asset pipelines and manifest formats
38
+ - **Documentation**: Comprehensive class and method documentation throughout
39
+
40
+ #### 🔧 **Technical Enhancements**
41
+ - **Bundle Size Reporting**: Human-readable units (B, KB, MB, GB) with optional gzip analysis
42
+ - **Error Handling**: Enhanced error logging with contextual information
43
+ - **Hook System**: Improved build lifecycle hooks with detailed payload documentation
44
+ - **Validation**: Pre-build checks and comprehensive input validation
45
+
46
+ #### 📚 **Migration Notes**
47
+ - All changes are backward compatible - no breaking changes
48
+ - Existing configurations and APIs continue to work unchanged
49
+ - New features are opt-in (like gzip analysis via `analyze_bundle: true`)
50
+ - Enhanced error messages provide better debugging information
51
+
3
52
  ## [1.2.17] - 2026-01-26
4
53
 
5
54
  ### ✨ **Manager Class Final Polish - Production Perfection**
data/README.md CHANGED
@@ -43,6 +43,7 @@ default:
43
43
  sourcemap: false
44
44
  entrypoint: "./app/javascript/application.js"
45
45
  outdir: "app/assets/builds"
46
+ analyze_bundle: false # Enable for gzip size reporting
46
47
 
47
48
  # Bundler-specific config
48
49
  bun:
@@ -52,9 +53,11 @@ bun:
52
53
  # Environment overrides
53
54
  development:
54
55
  sourcemap: true
56
+ analyze_bundle: true # Show gzip sizes in dev
55
57
 
56
58
  production:
57
59
  minify: true
60
+ analyze_bundle: true # Production bundle analysis
58
61
  ```
59
62
 
60
63
  ## Usage
@@ -111,6 +114,61 @@ Railpack.on_build_complete do |result|
111
114
  end
112
115
  ```
113
116
 
117
+ ### Advanced Usage
118
+
119
+ #### Bundle Analysis with Gzip
120
+
121
+ Enable `analyze_bundle: true` to see both uncompressed and gzipped bundle sizes:
122
+
123
+ ```yaml
124
+ # config/railpack.yml
125
+ default:
126
+ analyze_bundle: true
127
+ ```
128
+
129
+ Output example:
130
+ ```
131
+ ✅ Build completed successfully in 45.23ms (1.23 MB (0.45 MB gzipped))
132
+ ```
133
+
134
+ #### Build Hooks with Payload Details
135
+
136
+ Hook payloads provide detailed information about build results:
137
+
138
+ ```ruby
139
+ # config/initializers/railpack.rb
140
+ Railpack.on_build_complete do |payload|
141
+ if payload[:success]
142
+ # Success payload: { success: true, config: {...}, duration: 45.23, bundle_size: "1.23 MB" }
143
+ Rails.logger.info "Build succeeded in #{payload[:duration]}ms - #{payload[:bundle_size]}"
144
+ else
145
+ # Error payload: { success: false, error: #<Error>, config: {...}, duration: 12.34 }
146
+ Rails.logger.error "Build failed after #{payload[:duration]}ms: #{payload[:error].message}"
147
+ end
148
+ end
149
+
150
+ Railpack.on_build_start do |config|
151
+ # Config hash contains all current settings
152
+ Rails.logger.info "Starting #{config['bundler']} build for #{Rails.env}"
153
+ end
154
+ ```
155
+
156
+ #### Manifest Generation
157
+
158
+ Railpack automatically detects your asset pipeline and generates appropriate manifests:
159
+
160
+ ```ruby
161
+ # For Propshaft (Rails 7+ default)
162
+ # Generates: app/assets/builds/.manifest.json
163
+ Railpack::Manifest::Propshaft.generate(config)
164
+
165
+ # For Sprockets (Rails < 7)
166
+ # Generates: app/assets/builds/.sprockets-manifest-*.json
167
+ Railpack::Manifest::Sprockets.generate(config)
168
+ ```
169
+
170
+ The manifest generation is handled automatically after each build, but you can trigger it manually if needed.
171
+
114
172
  ## Supported Bundlers
115
173
 
116
174
  ### Bun (Default)
@@ -56,6 +56,8 @@ module Railpack
56
56
  # Calculate bundle size if output directory exists
57
57
  bundle_size = calculate_bundle_size(config)
58
58
 
59
+ # Build result hash passed to build_complete hooks
60
+ # Contains: success status, config used, build duration, and bundle size
59
61
  success_result = {
60
62
  success: true,
61
63
  config: config,
@@ -68,12 +70,15 @@ module Railpack
68
70
  # Generate asset manifest for Rails
69
71
  generate_asset_manifest(config)
70
72
 
73
+ # Trigger build_complete hooks with success result
71
74
  Railpack.trigger_build_complete(success_result)
72
75
  result
73
76
  rescue => error
74
77
  duration = ((Time.now - start_time) * 1000).round(2)
75
78
  Railpack.logger.error "❌ Build failed after #{duration}ms: #{error.message}"
76
79
 
80
+ # Error result hash passed to build_complete hooks
81
+ # Contains: failure status, error object, config used, and build duration
77
82
  error_result = {
78
83
  success: false,
79
84
  error: error,
@@ -127,109 +132,107 @@ module Railpack
127
132
 
128
133
  private
129
134
 
130
- def create_bundler
131
- bundler_name = Railpack.config.bundler
132
- bundler_class = BUNDLERS[bundler_name]
135
+ def create_bundler
136
+ bundler_name = Railpack.config.bundler
137
+ bundler_class = BUNDLERS[bundler_name]
133
138
 
134
- unless bundler_class
135
- raise Error, "Unsupported bundler: #{bundler_name}. Available: #{BUNDLERS.keys.join(', ')}"
136
- end
139
+ unless bundler_class
140
+ raise Error, "Unsupported bundler: #{bundler_name}. Available: #{BUNDLERS.keys.join(', ')}"
141
+ end
137
142
 
138
- bundler_class.new(Railpack.config)
139
- end
143
+ bundler_class.new(Railpack.config)
144
+ end
140
145
 
141
- # Calculate human-readable bundle size with optional gzip compression
142
- def calculate_bundle_size(config)
143
- outdir = config['outdir']
144
- return 'unknown' unless outdir && Dir.exist?(outdir)
146
+ # Calculate human-readable bundle size with optional gzip compression
147
+ def calculate_bundle_size(config)
148
+ outdir = config['outdir']
149
+ return 'unknown' unless outdir && Dir.exist?(outdir)
145
150
 
146
- total_size = 0
147
- Dir.glob("#{outdir}/**/*.{js,css,map}").each do |file|
148
- total_size += File.size(file) if File.file?(file)
149
- end
151
+ total_size = 0
152
+ Dir.glob("#{outdir}/**/*.{js,css,map}").each do |file|
153
+ total_size += File.size(file) if File.file?(file)
154
+ end
150
155
 
151
- # Include gzip size if analyze_bundle is enabled
152
- if config['analyze_bundle']
153
- gzip_size = calculate_gzip_size(outdir)
154
- "#{human_size(total_size)} (#{human_size(gzip_size)} gzipped)"
155
- else
156
- human_size(total_size)
156
+ # Include gzip size if analyze_bundle is enabled
157
+ if config['analyze_bundle']
158
+ gzip_size = calculate_gzip_size(outdir)
159
+ "#{human_size(total_size)} (#{human_size(gzip_size)} gzipped)"
160
+ else
161
+ human_size(total_size)
162
+ end
163
+ rescue => error
164
+ Railpack.logger.debug "Bundle size calculation failed: #{error.message}"
165
+ 'unknown'
157
166
  end
158
- rescue => error
159
- Railpack.logger.debug "Bundle size calculation failed: #{error.message}"
160
- 'unknown'
161
- end
162
167
 
163
- # Calculate total gzip-compressed size of assets
164
- def calculate_gzip_size(outdir)
165
- Dir.glob("#{outdir}/**/*.{js,css}").sum do |file|
166
- next 0 unless File.file?(file)
167
- Zlib::Deflate.deflate(File.read(file)).bytesize
168
+ # Calculate total gzip-compressed size of assets
169
+ def calculate_gzip_size(outdir)
170
+ Dir.glob("#{outdir}/**/*.{js,css}").sum do |file|
171
+ next 0 unless File.file?(file)
172
+ Zlib::Deflate.deflate(File.read(file)).bytesize
173
+ end
174
+ rescue => error
175
+ Railpack.logger.debug "Gzip size calculation failed: #{error.message}"
176
+ 0
168
177
  end
169
- rescue => error
170
- Railpack.logger.debug "Gzip size calculation failed: #{error.message}"
171
- 0
172
- end
173
178
 
174
- # Convert bytes to human-readable format (B, KB, MB, GB)
175
- def human_size(bytes)
176
- units = %w[B KB MB GB]
177
- size = bytes.to_f
178
- units.each do |unit|
179
- return "#{(size).round(2)} #{unit}" if size < 1024
180
- size /= 1024
179
+ # Convert bytes to human-readable format (B, KB, MB, GB)
180
+ def human_size(bytes)
181
+ units = %w[B KB MB GB]
182
+ size = bytes.to_f
183
+ units.each do |unit|
184
+ return "#{(size).round(2)} #{unit}" if size < 1024
185
+ size /= 1024
186
+ end
181
187
  end
182
- end
183
188
 
184
- def generate_asset_manifest(config)
185
- outdir = config['outdir']
186
- return unless outdir && Dir.exist?(outdir)
189
+ def generate_asset_manifest(config)
190
+ outdir = config['outdir']
191
+ return unless outdir && Dir.exist?(outdir)
187
192
 
188
- # Detect asset pipeline type and delegate to appropriate manifest generator
189
- pipeline_type = detect_asset_pipeline
190
- manifest_class = Railpack::Manifest.const_get(pipeline_type.capitalize)
193
+ # Detect asset pipeline type and delegate to appropriate manifest generator
194
+ pipeline_type = detect_asset_pipeline
195
+ manifest_class = Railpack::Manifest.const_get(pipeline_type.capitalize)
191
196
 
192
- manifest_class.generate(config)
193
- rescue => error
194
- # Enhanced error logging with context
195
- asset_files = Dir.glob("#{outdir}/**/*.{js,css}").length rescue 0
196
- Railpack.logger.warn "⚠️ Failed to generate #{pipeline_type} asset manifest for #{outdir} (#{asset_files} assets): #{error.message}"
197
- end
197
+ manifest_class.generate(config)
198
+ rescue => error
199
+ # Enhanced error logging with context
200
+ asset_files = Dir.glob("#{outdir}/**/*.{js,css}").length rescue 0
201
+ Railpack.logger.warn "⚠️ Failed to generate #{pipeline_type} asset manifest for #{outdir} (#{asset_files} assets): #{error.message}"
202
+ end
198
203
 
199
- # Validate output directory exists and is writable before build
200
- def validate_output_directory(config)
201
- outdir = config['outdir']
202
- return unless outdir
204
+ # Validate output directory exists and is writable before build
205
+ def validate_output_directory(config)
206
+ outdir = config['outdir']
207
+ return unless outdir
203
208
 
204
- unless Dir.exist?(outdir)
205
- Railpack.logger.warn "⚠️ Output directory #{outdir} does not exist - assets will be created on first build"
209
+ unless Dir.exist?(outdir)
210
+ Railpack.logger.warn "⚠️ Output directory #{outdir} does not exist - assets will be created on first build"
211
+ # Ensure directory exists to prevent early build failures
212
+ FileUtils.mkdir_p(outdir)
213
+ end
206
214
  end
207
- end
208
215
 
209
- private
216
+ def detect_asset_pipeline
217
+ # Check Rails.application.config.assets class directly (more reliable)
218
+ if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
219
+ assets_config = Rails.application.config.assets
220
+ if assets_config.is_a?(Propshaft::Assembler) || defined?(Propshaft::Assembler)
221
+ :propshaft
222
+ elsif defined?(Sprockets::Manifest)
223
+ :sprockets
224
+ end
225
+ end
210
226
 
211
- def detect_asset_pipeline
212
- # Check Rails.application.config.assets class directly (more reliable)
213
- if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
214
- assets_config = Rails.application.config.assets
215
- if assets_config.is_a?(Propshaft::Assembler) || defined?(Propshaft::Assembler)
227
+ # Fallback to version-based detection
228
+ if defined?(Rails) && Rails.version.to_f >= 7.0
216
229
  :propshaft
217
- elsif defined?(Sprockets::Manifest)
230
+ elsif defined?(Rails) && Rails.version.to_f < 7.0 && defined?(Sprockets)
218
231
  :sprockets
232
+ else
233
+ # Safe default for modern Rails
234
+ :propshaft
219
235
  end
220
236
  end
221
-
222
- # Fallback to version-based detection
223
- if defined?(Rails) && Rails.version.to_f >= 7.0
224
- :propshaft
225
- elsif defined?(Rails) && Rails.version.to_f < 7.0 && defined?(Sprockets)
226
- :sprockets
227
- else
228
- # Safe default for modern Rails
229
- :propshaft
230
- end
231
- end
232
-
233
-
234
237
  end
235
- end
238
+ end
@@ -1,3 +1,3 @@
1
1
  module Railpack
2
- VERSION = "1.2.17"
2
+ VERSION = "1.3.1"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: railpack
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.2.17
4
+ version: 1.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - 21tycoons LLC