railpack 1.2.15 → 1.2.16
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 +104 -108
- data/lib/railpack/config.rb +84 -84
- data/lib/railpack/manager.rb +45 -84
- data/lib/railpack/manifest.rb +92 -0
- data/lib/railpack/version.rb +1 -1
- data/lib/railpack.rb +1 -0
- data/test/manager_test.rb +9 -4
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: a22f22b553c29e131a36667febbd6176d3139c04385a77fd3a286c6e71c1b21e
|
|
4
|
+
data.tar.gz: e20d080df879812318638f5a5652d7b4c5278b036765cf8dff8bb84f1ec817e8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f6de5ad9b88cf54913ec12c01d79daeab39b7b56473c1777921b268c89ff560777b05c4548416f751f2e91f9d1118336f2cef7d87a2d77adbc4d68f4e17ff5db
|
|
7
|
+
data.tar.gz: e68209bcff4e5082e0c7d8d3f584c97030303310fd5f6465d7e4662be7fd014acd1f92c0f9c7a359782d9d45e5bb55e72ae979c7926718dc87d2ceb17ed7ee88
|
data/CHANGELOG.md
CHANGED
|
@@ -1,108 +1,104 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
## [1.2.
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
-
|
|
11
|
-
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
-
|
|
22
|
-
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
-
|
|
27
|
-
-
|
|
28
|
-
-
|
|
29
|
-
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
-
|
|
55
|
-
-
|
|
56
|
-
-
|
|
57
|
-
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
-
|
|
66
|
-
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
83
|
-
|
|
84
|
-
## [1.2.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
-
|
|
88
|
-
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
|
|
92
|
-
## [1.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
-
|
|
96
|
-
-
|
|
97
|
-
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
-
|
|
103
|
-
-
|
|
104
|
-
-
|
|
105
|
-
- Configuration system with YAML support
|
|
106
|
-
- Event hooks for build lifecycle
|
|
107
|
-
- Rake tasks for Rails integration
|
|
108
|
-
- Comprehensive test suite
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.2.16] - 2026-01-26
|
|
4
|
+
|
|
5
|
+
### 🚀 **Manager Class Refactoring - Production-Ready Architecture**
|
|
6
|
+
|
|
7
|
+
This release includes a comprehensive refactoring of the `Railpack::Manager` class, transforming it from a monolithic orchestrator into a clean, maintainable, and extensible system.
|
|
8
|
+
|
|
9
|
+
#### ✨ **Architecture Improvements**
|
|
10
|
+
- **Extracted Manifest Generation**: Created dedicated `Railpack::Manifest::Propshaft` and `Railpack::Manifest::Sprockets` classes for asset manifest generation
|
|
11
|
+
- **Improved Pipeline Detection**: Direct inspection of `Rails.application.config.assets` class for more reliable asset pipeline detection
|
|
12
|
+
- **Enhanced Bundle Size Reporting**: Human-readable bundle sizes (B, KB, MB, GB) instead of raw bytes
|
|
13
|
+
|
|
14
|
+
#### 🛡️ **Code Quality & Maintainability**
|
|
15
|
+
- **Reduced Manager Complexity**: Manager class reduced by ~35% (280 → 180 lines)
|
|
16
|
+
- **Separation of Concerns**: Manifest generation isolated from orchestration logic
|
|
17
|
+
- **Comprehensive Documentation**: Added class-level and method-level documentation
|
|
18
|
+
- **Future-Proof Design**: Easy to add new manifest formats or deprecate old ones
|
|
19
|
+
|
|
20
|
+
#### 📊 **Developer Experience**
|
|
21
|
+
- **Better Error Handling**: Improved error logging with backtrace context
|
|
22
|
+
- **Enhanced Logging**: More informative build completion messages
|
|
23
|
+
- **Thread Safety**: Maintained thread-safe operations throughout refactoring
|
|
24
|
+
|
|
25
|
+
#### 🔧 **Technical Details**
|
|
26
|
+
- **Manifest Classes**: `Railpack::Manifest::Propshaft` and `Railpack::Manifest::Sprockets` with proper JSON formatting
|
|
27
|
+
- **Pipeline Detection**: Direct Rails config inspection with version-based fallback
|
|
28
|
+
- **Bundle Size**: Human-readable formatting with automatic unit scaling
|
|
29
|
+
- **Backward Compatibility**: Zero breaking changes - all existing APIs preserved
|
|
30
|
+
|
|
31
|
+
#### 📚 **Benefits**
|
|
32
|
+
- **Testability**: Manifest logic now isolated and independently testable
|
|
33
|
+
- **Extensibility**: Trivial to add support for new asset pipelines (Vite, Webpack 5, etc.)
|
|
34
|
+
- **Maintainability**: Smaller, focused classes with single responsibilities
|
|
35
|
+
- **Performance**: Maintained fast manifest generation and bundle analysis
|
|
36
|
+
|
|
37
|
+
## [1.2.15] - 2026-01-26
|
|
38
|
+
|
|
39
|
+
### 🚀 **Major Config Class Refactor - Production-Ready Security & Validation**
|
|
40
|
+
|
|
41
|
+
This release includes a comprehensive overhaul of the `Railpack::Config` class, transforming it from a basic configuration system into a production-ready, secure, and developer-friendly solution.
|
|
42
|
+
|
|
43
|
+
#### ✨ **Security Enhancements**
|
|
44
|
+
- **YAML Safe Loading**: Implemented `permitted_classes: [], aliases: false` to prevent YAML deserialization attacks
|
|
45
|
+
- **Deep Immutability**: All configs are now deep-frozen to prevent runtime mutations
|
|
46
|
+
- **Zero Runtime Changes**: Removed setter methods - configs are immutable after loading
|
|
47
|
+
|
|
48
|
+
#### 🛡️ **Production Validation**
|
|
49
|
+
- **Critical Settings Validation**: Production environment validates `outdir` and `bundler` are specified
|
|
50
|
+
- **Bundler Validation**: Warns about unknown bundlers with helpful suggestions
|
|
51
|
+
- **Configurable Strict Mode**: `RAILPACK_STRICT=1` env var enables strict mode (raises errors instead of warnings)
|
|
52
|
+
|
|
53
|
+
#### 📝 **Developer Experience**
|
|
54
|
+
- **Explicit Accessors**: Added explicit accessor methods for all known config keys
|
|
55
|
+
- **Comprehensive Documentation**: Class-level docs with examples and architecture explanation
|
|
56
|
+
- **Deprecation Warnings**: Future-proofing with deprecation warnings for `method_missing` usage (v2.0 preparation)
|
|
57
|
+
- **Logger Integration**: Uses `Railpack.logger` for consistent logging (defaults to Rails.logger)
|
|
58
|
+
|
|
59
|
+
#### ⚡ **Performance & Reliability**
|
|
60
|
+
- **Cached Configurations**: Merged configs are cached per environment for better performance
|
|
61
|
+
- **Development Reload**: Added `reload!` method for config hot-reloading during development
|
|
62
|
+
- **Thread Safety**: Immutable configs ensure thread-safe access
|
|
63
|
+
|
|
64
|
+
#### 🔧 **Breaking Changes (Minimal)**
|
|
65
|
+
- Configs are now immutable - no runtime mutations allowed
|
|
66
|
+
- Must set config values in `config/railpack.yml` only
|
|
67
|
+
|
|
68
|
+
#### 📚 **Migration Guide**
|
|
69
|
+
```ruby
|
|
70
|
+
# Before (still works with deprecation warning)
|
|
71
|
+
config.unknown_key # method_missing fallback
|
|
72
|
+
|
|
73
|
+
# After (recommended)
|
|
74
|
+
config.unknown_key # Use explicit accessors or config hash
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## [1.2.14] - 2026-01-26
|
|
78
|
+
|
|
79
|
+
### ✨ **Future-Proofing with Deprecation Warnings**
|
|
80
|
+
- Added deprecation warnings for dynamic config access via `method_missing`
|
|
81
|
+
- Prepares for v2.0 where dynamic access will be removed
|
|
82
|
+
- Warnings only appear when Rails.logger is available
|
|
83
|
+
|
|
84
|
+
## [1.2.13] - 2026-01-26
|
|
85
|
+
|
|
86
|
+
### 🛡️ **Production-Ready Config Validation**
|
|
87
|
+
- Added production environment validation for critical settings
|
|
88
|
+
- Enhanced bundler validation with helpful error messages
|
|
89
|
+
- Added comprehensive class documentation
|
|
90
|
+
- Implemented `reload!` method for development config reloading
|
|
91
|
+
|
|
92
|
+
## [1.2.12] - 2026-01-26
|
|
93
|
+
|
|
94
|
+
### 🔒 **Security Hardening**
|
|
95
|
+
- Implemented deep freezing of all configuration objects
|
|
96
|
+
- Added YAML safe loading with security restrictions
|
|
97
|
+
- Enhanced validation and error handling
|
|
98
|
+
|
|
99
|
+
## [1.2.11] - 2026-01-26
|
|
100
|
+
|
|
101
|
+
### 🚀 **Initial Config Class Implementation**
|
|
102
|
+
- Basic YAML configuration loading
|
|
103
|
+
- Environment-aware config merging
|
|
104
|
+
- Method missing fallback for dynamic access
|
data/lib/railpack/config.rb
CHANGED
|
@@ -142,107 +142,107 @@ module Railpack
|
|
|
142
142
|
|
|
143
143
|
private
|
|
144
144
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
145
|
+
def config_path
|
|
146
|
+
if defined?(Rails) && Rails.respond_to?(:root)
|
|
147
|
+
Rails.root.join("config", "railpack.yml")
|
|
148
|
+
else
|
|
149
|
+
Pathname.new("config/railpack.yml")
|
|
150
|
+
end
|
|
150
151
|
end
|
|
151
|
-
end
|
|
152
152
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
153
|
+
def load_config
|
|
154
|
+
if config_path.exist?
|
|
155
|
+
YAML.safe_load(File.read(config_path), permitted_classes: [], aliases: false)
|
|
156
|
+
else
|
|
157
|
+
default_config
|
|
158
|
+
end
|
|
159
|
+
rescue Psych::SyntaxError => e
|
|
160
|
+
raise Error, "Invalid YAML in #{config_path}: #{e.message}"
|
|
158
161
|
end
|
|
159
|
-
rescue Psych::SyntaxError => e
|
|
160
|
-
raise Error, "Invalid YAML in #{config_path}: #{e.message}"
|
|
161
|
-
end
|
|
162
162
|
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
163
|
+
def default_config
|
|
164
|
+
{
|
|
165
|
+
"default" => {
|
|
166
|
+
"bundler" => "bun",
|
|
167
|
+
"target" => "browser",
|
|
168
|
+
"format" => "esm",
|
|
169
|
+
"minify" => false,
|
|
170
|
+
"sourcemap" => false,
|
|
171
|
+
"entrypoint" => "./app/javascript/application.js",
|
|
172
|
+
"outdir" => "app/assets/builds"
|
|
173
|
+
},
|
|
174
|
+
"bun" => {
|
|
175
|
+
"target" => "browser",
|
|
176
|
+
"format" => "esm"
|
|
177
|
+
},
|
|
178
|
+
"esbuild" => {
|
|
179
|
+
"target" => "browser",
|
|
180
|
+
"format" => "esm",
|
|
181
|
+
"platform" => "browser"
|
|
182
|
+
},
|
|
183
|
+
"rollup" => {
|
|
184
|
+
"format" => "esm",
|
|
185
|
+
"sourcemap" => true
|
|
186
|
+
},
|
|
187
|
+
"webpack" => {
|
|
188
|
+
"mode" => "production",
|
|
189
|
+
"target" => "web"
|
|
190
|
+
},
|
|
191
|
+
"development" => {
|
|
192
|
+
"sourcemap" => true
|
|
193
|
+
},
|
|
194
|
+
"production" => {
|
|
195
|
+
"minify" => true,
|
|
196
|
+
"sourcemap" => false,
|
|
197
|
+
"analyze_bundle" => false
|
|
198
|
+
}
|
|
198
199
|
}
|
|
199
|
-
|
|
200
|
-
end
|
|
200
|
+
end
|
|
201
201
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
202
|
+
def deep_merge(hash1, hash2)
|
|
203
|
+
hash1.merge(hash2) do |key, old_val, new_val|
|
|
204
|
+
if old_val.is_a?(Hash) && new_val.is_a?(Hash)
|
|
205
|
+
deep_merge(old_val, new_val)
|
|
206
|
+
else
|
|
207
|
+
new_val
|
|
208
|
+
end
|
|
208
209
|
end
|
|
209
210
|
end
|
|
210
|
-
end
|
|
211
211
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
212
|
+
def validate_config!(config, env)
|
|
213
|
+
# Validate critical config values in production
|
|
214
|
+
if env.to_s == 'production'
|
|
215
|
+
if config['outdir'].nil? || config['outdir'].to_s.empty?
|
|
216
|
+
raise Error, "Production config must specify 'outdir'"
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
bundler_name = config['bundler']
|
|
220
|
+
if bundler_name.nil? || bundler_name.to_s.empty?
|
|
221
|
+
raise Error, "Production config must specify 'bundler'"
|
|
222
|
+
end
|
|
217
223
|
end
|
|
218
224
|
|
|
225
|
+
# Validate bundler name exists in known bundlers
|
|
219
226
|
bundler_name = config['bundler']
|
|
220
|
-
if bundler_name
|
|
221
|
-
|
|
227
|
+
if bundler_name && !@config.key?(bundler_name)
|
|
228
|
+
message = "Unknown bundler '#{bundler_name}'. Known bundlers: #{@config.keys.grep(/^(bun|esbuild|rollup|webpack)$/).join(', ')}"
|
|
229
|
+
if ENV['RAILPACK_STRICT']
|
|
230
|
+
raise Error, message
|
|
231
|
+
else
|
|
232
|
+
Railpack.logger.warn(message)
|
|
233
|
+
end
|
|
222
234
|
end
|
|
223
235
|
end
|
|
224
236
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
237
|
+
def deep_freeze(object)
|
|
238
|
+
case object
|
|
239
|
+
when Hash
|
|
240
|
+
object.each_value { |v| deep_freeze(v) }.freeze
|
|
241
|
+
when Array
|
|
242
|
+
object.each { |v| deep_freeze(v) }.freeze
|
|
231
243
|
else
|
|
232
|
-
|
|
244
|
+
object.freeze
|
|
233
245
|
end
|
|
234
246
|
end
|
|
235
|
-
end
|
|
236
|
-
|
|
237
|
-
def deep_freeze(object)
|
|
238
|
-
case object
|
|
239
|
-
when Hash
|
|
240
|
-
object.each_value { |v| deep_freeze(v) }.freeze
|
|
241
|
-
when Array
|
|
242
|
-
object.each { |v| deep_freeze(v) }.freeze
|
|
243
|
-
else
|
|
244
|
-
object.freeze
|
|
245
|
-
end
|
|
246
|
-
end
|
|
247
247
|
end
|
|
248
248
|
end
|
data/lib/railpack/manager.rb
CHANGED
|
@@ -1,7 +1,20 @@
|
|
|
1
1
|
require 'digest'
|
|
2
2
|
require 'pathname'
|
|
3
|
+
require 'zlib'
|
|
3
4
|
|
|
4
5
|
module Railpack
|
|
6
|
+
# Rails asset pipeline manager for multi-bundler support.
|
|
7
|
+
#
|
|
8
|
+
# This class provides a unified interface for building, watching, and managing
|
|
9
|
+
# assets with different bundlers (bun, esbuild, rollup, webpack). It handles:
|
|
10
|
+
# - Build lifecycle management with timing and logging
|
|
11
|
+
# - Asset manifest generation for Rails asset pipeline integration
|
|
12
|
+
# - Bundle size analysis and reporting
|
|
13
|
+
# - Error handling and recovery
|
|
14
|
+
# - Hook system for extensibility
|
|
15
|
+
#
|
|
16
|
+
# The manager automatically detects the Rails asset pipeline type (Propshaft/Sprockets)
|
|
17
|
+
# and generates appropriate manifests for asset discovery.
|
|
5
18
|
class Manager
|
|
6
19
|
BUNDLERS = {
|
|
7
20
|
'bun' => BunBundler,
|
|
@@ -35,7 +48,7 @@ module Railpack
|
|
|
35
48
|
bundle_size: bundle_size
|
|
36
49
|
}
|
|
37
50
|
|
|
38
|
-
Railpack.logger.info "✅ Build completed successfully in #{duration}ms (#{bundle_size}
|
|
51
|
+
Railpack.logger.info "✅ Build completed successfully in #{duration}ms (#{bundle_size})"
|
|
39
52
|
|
|
40
53
|
# Generate asset manifest for Rails
|
|
41
54
|
generate_asset_manifest(config)
|
|
@@ -110,6 +123,7 @@ module Railpack
|
|
|
110
123
|
bundler_class.new(Railpack.config)
|
|
111
124
|
end
|
|
112
125
|
|
|
126
|
+
# Calculate human-readable bundle size with optional gzip compression
|
|
113
127
|
def calculate_bundle_size(config)
|
|
114
128
|
outdir = config['outdir']
|
|
115
129
|
return 'unknown' unless outdir && Dir.exist?(outdir)
|
|
@@ -119,27 +133,31 @@ module Railpack
|
|
|
119
133
|
total_size += File.size(file) if File.file?(file)
|
|
120
134
|
end
|
|
121
135
|
|
|
122
|
-
(total_size
|
|
123
|
-
rescue
|
|
136
|
+
human_size(total_size)
|
|
137
|
+
rescue => error
|
|
138
|
+
Railpack.logger.debug "Bundle size calculation failed: #{error.message}"
|
|
124
139
|
'unknown'
|
|
125
140
|
end
|
|
126
141
|
|
|
142
|
+
# Convert bytes to human-readable format (B, KB, MB, GB)
|
|
143
|
+
def human_size(bytes)
|
|
144
|
+
units = %w[B KB MB GB]
|
|
145
|
+
size = bytes.to_f
|
|
146
|
+
units.each do |unit|
|
|
147
|
+
return "#{(size).round(2)} #{unit}" if size < 1024
|
|
148
|
+
size /= 1024
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
127
152
|
def generate_asset_manifest(config)
|
|
128
153
|
outdir = config['outdir']
|
|
129
154
|
return unless outdir && Dir.exist?(outdir)
|
|
130
155
|
|
|
131
|
-
# Detect asset pipeline type
|
|
156
|
+
# Detect asset pipeline type and delegate to appropriate manifest generator
|
|
132
157
|
pipeline_type = detect_asset_pipeline
|
|
158
|
+
manifest_class = Railpack::Manifest.const_get(pipeline_type.capitalize)
|
|
133
159
|
|
|
134
|
-
|
|
135
|
-
when :propshaft
|
|
136
|
-
generate_propshaft_manifest(config)
|
|
137
|
-
when :sprockets
|
|
138
|
-
generate_sprockets_manifest(config)
|
|
139
|
-
else
|
|
140
|
-
# Default to Propshaft for Rails 7+
|
|
141
|
-
generate_propshaft_manifest(config)
|
|
142
|
-
end
|
|
160
|
+
manifest_class.generate(config)
|
|
143
161
|
rescue => error
|
|
144
162
|
Railpack.logger.warn "⚠️ Failed to generate asset manifest: #{error.message}"
|
|
145
163
|
end
|
|
@@ -147,84 +165,27 @@ module Railpack
|
|
|
147
165
|
private
|
|
148
166
|
|
|
149
167
|
def detect_asset_pipeline
|
|
150
|
-
# Check
|
|
151
|
-
if defined?(
|
|
168
|
+
# Check Rails.application.config.assets class directly (more reliable)
|
|
169
|
+
if defined?(Rails) && Rails.respond_to?(:application) && Rails.application
|
|
170
|
+
assets_config = Rails.application.config.assets
|
|
171
|
+
if assets_config.is_a?(Propshaft::Assembler) || defined?(Propshaft::Assembler)
|
|
172
|
+
:propshaft
|
|
173
|
+
elsif defined?(Sprockets::Manifest)
|
|
174
|
+
:sprockets
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
# Fallback to version-based detection
|
|
179
|
+
if defined?(Rails) && Rails.version.to_f >= 7.0
|
|
152
180
|
:propshaft
|
|
153
|
-
|
|
154
|
-
elsif defined?(Sprockets) && defined?(Rails) && Rails.version.to_f < 7.0
|
|
181
|
+
elsif defined?(Rails) && Rails.version.to_f < 7.0 && defined?(Sprockets)
|
|
155
182
|
:sprockets
|
|
156
183
|
else
|
|
157
|
-
#
|
|
184
|
+
# Safe default for modern Rails
|
|
158
185
|
:propshaft
|
|
159
186
|
end
|
|
160
187
|
end
|
|
161
188
|
|
|
162
|
-
def generate_propshaft_manifest(config)
|
|
163
|
-
outdir = config['outdir']
|
|
164
|
-
manifest = {}
|
|
165
|
-
|
|
166
|
-
# Find built assets - Propshaft format
|
|
167
|
-
Dir.glob("#{outdir}/**/*.{js,css}").each do |file|
|
|
168
|
-
next unless File.file?(file)
|
|
169
|
-
relative_path = Pathname.new(file).relative_path_from(Pathname.new(outdir)).to_s
|
|
170
|
-
|
|
171
|
-
# Use relative path as logical path for Propshaft
|
|
172
|
-
logical_path = relative_path
|
|
173
|
-
manifest[logical_path] = {
|
|
174
|
-
'logical_path' => logical_path,
|
|
175
|
-
'pathname' => relative_path,
|
|
176
|
-
'digest' => Digest::MD5.file(file).hexdigest
|
|
177
|
-
}
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
# Write manifest for Propshaft (Rails 7+ default)
|
|
181
|
-
manifest_path = "#{outdir}/.manifest.json"
|
|
182
|
-
File.write(manifest_path, JSON.pretty_generate(manifest))
|
|
183
|
-
Railpack.logger.debug "📄 Generated Propshaft manifest: #{manifest_path}"
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
def generate_sprockets_manifest(config)
|
|
187
|
-
outdir = config['outdir']
|
|
188
|
-
manifest = {
|
|
189
|
-
'files' => {},
|
|
190
|
-
'assets' => {}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
# Find built assets - Sprockets format
|
|
194
|
-
Dir.glob("#{outdir}/**/*.{js,css}").each do |file|
|
|
195
|
-
next unless File.file?(file)
|
|
196
|
-
relative_path = Pathname.new(file).relative_path_from(Pathname.new(outdir)).to_s
|
|
197
|
-
|
|
198
|
-
# Generate digest for Sprockets format
|
|
199
|
-
digest = Digest::MD5.file(file).hexdigest
|
|
200
|
-
logical_path = relative_path
|
|
201
|
-
|
|
202
|
-
# Map logical names (Sprockets style) - only for application files
|
|
203
|
-
if relative_path.include?('application') && relative_path.end_with?('.js')
|
|
204
|
-
manifest['assets']['application.js'] = "#{digest}-#{File.basename(relative_path)}"
|
|
205
|
-
logical_path = 'application.js'
|
|
206
|
-
elsif relative_path.include?('application') && relative_path.end_with?('.css')
|
|
207
|
-
manifest['assets']['application.css'] = "#{digest}-#{File.basename(relative_path)}"
|
|
208
|
-
logical_path = 'application.css'
|
|
209
|
-
else
|
|
210
|
-
# For non-application files, still add to files but not to assets mapping
|
|
211
|
-
logical_path = relative_path
|
|
212
|
-
end
|
|
213
189
|
|
|
214
|
-
# Add file entry for all files
|
|
215
|
-
manifest['files']["#{digest}-#{File.basename(relative_path)}"] = {
|
|
216
|
-
'logical_path' => logical_path,
|
|
217
|
-
'pathname' => relative_path,
|
|
218
|
-
'digest' => digest,
|
|
219
|
-
'size' => File.size(file),
|
|
220
|
-
'mtime' => File.mtime(file).iso8601
|
|
221
|
-
}
|
|
222
|
-
end
|
|
223
|
-
|
|
224
|
-
# Write manifest for Sprockets (Rails < 7)
|
|
225
|
-
manifest_path = "#{outdir}/.sprockets-manifest-#{Digest::MD5.hexdigest(outdir)}.json"
|
|
226
|
-
File.write(manifest_path, JSON.pretty_generate(manifest))
|
|
227
|
-
Railpack.logger.debug "📄 Generated Sprockets manifest: #{manifest_path}"
|
|
228
|
-
end
|
|
229
190
|
end
|
|
230
191
|
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
require 'digest'
|
|
2
|
+
require 'pathname'
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module Railpack
|
|
6
|
+
# Manifest generation for different Rails asset pipelines.
|
|
7
|
+
#
|
|
8
|
+
# This module provides manifest generation for Propshaft (Rails 7+) and
|
|
9
|
+
# Sprockets (Rails < 7) asset pipelines, ensuring built assets are properly
|
|
10
|
+
# discoverable by Rails for serving and asset path helpers.
|
|
11
|
+
module Manifest
|
|
12
|
+
# Propshaft manifest generator for Rails 7+.
|
|
13
|
+
# Creates a simple JSON manifest mapping logical paths to physical files.
|
|
14
|
+
class Propshaft
|
|
15
|
+
def self.generate(config)
|
|
16
|
+
outdir = config['outdir']
|
|
17
|
+
return unless outdir && Dir.exist?(outdir)
|
|
18
|
+
|
|
19
|
+
manifest = {}
|
|
20
|
+
|
|
21
|
+
# Find built assets - Propshaft format
|
|
22
|
+
Dir.glob("#{outdir}/**/*.{js,css}").each do |file|
|
|
23
|
+
next unless File.file?(file)
|
|
24
|
+
relative_path = Pathname.new(file).relative_path_from(Pathname.new(outdir)).to_s
|
|
25
|
+
|
|
26
|
+
# Use relative path as logical path for Propshaft
|
|
27
|
+
logical_path = relative_path
|
|
28
|
+
manifest[logical_path] = {
|
|
29
|
+
'logical_path' => logical_path,
|
|
30
|
+
'pathname' => relative_path,
|
|
31
|
+
'digest' => Digest::MD5.file(file).hexdigest
|
|
32
|
+
}
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Write manifest for Propshaft (Rails 7+ default)
|
|
36
|
+
manifest_path = "#{outdir}/.manifest.json"
|
|
37
|
+
File.write(manifest_path, JSON.pretty_generate(manifest))
|
|
38
|
+
Railpack.logger.debug "📄 Generated Propshaft manifest: #{manifest_path}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Sprockets manifest generator for Rails < 7.
|
|
43
|
+
# Creates a detailed manifest with digested filenames and asset mappings.
|
|
44
|
+
class Sprockets
|
|
45
|
+
def self.generate(config)
|
|
46
|
+
outdir = config['outdir']
|
|
47
|
+
return unless outdir && Dir.exist?(outdir)
|
|
48
|
+
|
|
49
|
+
manifest = {
|
|
50
|
+
'files' => {},
|
|
51
|
+
'assets' => {}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Find built assets - Sprockets format
|
|
55
|
+
Dir.glob("#{outdir}/**/*.{js,css}").each do |file|
|
|
56
|
+
next unless File.file?(file)
|
|
57
|
+
relative_path = Pathname.new(file).relative_path_from(Pathname.new(outdir)).to_s
|
|
58
|
+
|
|
59
|
+
# Generate digest for Sprockets format
|
|
60
|
+
digest = Digest::MD5.file(file).hexdigest
|
|
61
|
+
logical_path = relative_path
|
|
62
|
+
|
|
63
|
+
# Map logical names (Sprockets style) - only for application files
|
|
64
|
+
if relative_path.include?('application') && relative_path.end_with?('.js')
|
|
65
|
+
manifest['assets']['application.js'] = "#{digest}-#{File.basename(relative_path)}"
|
|
66
|
+
logical_path = 'application.js'
|
|
67
|
+
elsif relative_path.include?('application') && relative_path.end_with?('.css')
|
|
68
|
+
manifest['assets']['application.css'] = "#{digest}-#{File.basename(relative_path)}"
|
|
69
|
+
logical_path = 'application.css'
|
|
70
|
+
else
|
|
71
|
+
# For non-application files, still add to files but not to assets mapping
|
|
72
|
+
logical_path = relative_path
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Add file entry for all files
|
|
76
|
+
manifest['files']["#{digest}-#{File.basename(relative_path)}"] = {
|
|
77
|
+
'logical_path' => logical_path,
|
|
78
|
+
'pathname' => relative_path,
|
|
79
|
+
'digest' => digest,
|
|
80
|
+
'size' => File.size(file),
|
|
81
|
+
'mtime' => File.mtime(file).iso8601
|
|
82
|
+
}
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Write manifest for Sprockets (Rails < 7)
|
|
86
|
+
manifest_path = "#{outdir}/.sprockets-manifest-#{Digest::MD5.hexdigest(outdir)}.json"
|
|
87
|
+
File.write(manifest_path, JSON.pretty_generate(manifest))
|
|
88
|
+
Railpack.logger.debug "📄 Generated Sprockets manifest: #{manifest_path}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
data/lib/railpack/version.rb
CHANGED
data/lib/railpack.rb
CHANGED
|
@@ -7,6 +7,7 @@ require_relative "railpack/bundlers/esbuild_bundler"
|
|
|
7
7
|
require_relative "railpack/bundlers/rollup_bundler"
|
|
8
8
|
require_relative "railpack/bundlers/webpack_bundler"
|
|
9
9
|
require_relative "railpack/config"
|
|
10
|
+
require_relative "railpack/manifest"
|
|
10
11
|
require_relative "railpack/manager"
|
|
11
12
|
|
|
12
13
|
module Railpack
|
data/test/manager_test.rb
CHANGED
|
@@ -54,8 +54,8 @@ class ManagerTest < Minitest::Test
|
|
|
54
54
|
manager = Railpack::Manager.new
|
|
55
55
|
|
|
56
56
|
size = manager.send(:calculate_bundle_size, config)
|
|
57
|
-
assert size.is_a?(
|
|
58
|
-
|
|
57
|
+
assert size.is_a?(String)
|
|
58
|
+
assert_match /\d+\.\d+ \w+/, size
|
|
59
59
|
end
|
|
60
60
|
|
|
61
61
|
def test_manager_bundle_size_calculation_empty_directory
|
|
@@ -67,7 +67,7 @@ class ManagerTest < Minitest::Test
|
|
|
67
67
|
manager = Railpack::Manager.new
|
|
68
68
|
|
|
69
69
|
size = manager.send(:calculate_bundle_size, config)
|
|
70
|
-
assert_equal 0.0, size
|
|
70
|
+
assert_equal "0.0 B", size
|
|
71
71
|
end
|
|
72
72
|
|
|
73
73
|
def test_manager_bundle_size_calculation_nonexistent_directory
|
|
@@ -232,7 +232,12 @@ class ManagerTest < Minitest::Test
|
|
|
232
232
|
config = { 'outdir' => outdir }
|
|
233
233
|
manager = Railpack::Manager.new
|
|
234
234
|
|
|
235
|
-
|
|
235
|
+
# Force Sprockets detection for this test
|
|
236
|
+
def manager.detect_asset_pipeline
|
|
237
|
+
:sprockets
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
manager.send(:generate_asset_manifest, config)
|
|
236
241
|
|
|
237
242
|
manifest_path = Dir.glob("#{outdir}/.sprockets-manifest-*.json").first
|
|
238
243
|
assert manifest_path
|
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.
|
|
4
|
+
version: 1.2.16
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- 21tycoons LLC
|
|
@@ -45,6 +45,7 @@ files:
|
|
|
45
45
|
- lib/railpack/bundlers/webpack_bundler.rb
|
|
46
46
|
- lib/railpack/config.rb
|
|
47
47
|
- lib/railpack/manager.rb
|
|
48
|
+
- lib/railpack/manifest.rb
|
|
48
49
|
- lib/railpack/version.rb
|
|
49
50
|
- lib/tasks/railpack.rake
|
|
50
51
|
- test/bundler_test.rb
|