shakapacker 9.3.0.beta.2 → 9.3.0.beta.4

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: d196f7ff4fd6270c0a4739f62ed17ee202eef663222018dc0b031b7dfb8ec015
4
- data.tar.gz: cb645bae3cb677c7b7f7e35164d99b779dfb92d7d6a975204ac0fcae71b56f40
3
+ metadata.gz: 2f2fafddfa10d364b8e62946786c16d9206ecffadf82b719e3ffeb2100608da0
4
+ data.tar.gz: 0e0cdc08d53e64e8066d7232aba1022b6a7708067d56ff7ba738803bfbc49a78
5
5
  SHA512:
6
- metadata.gz: ee177b62cc41abd5fbd460d34f8f8dfe387385152cc683aed5034fe3fd67050043dd3cd2fe32f09f06735ff28a4e71ec6f1f4f945e6ea582dbba06e0883dbb1d
7
- data.tar.gz: dcb85543ec034ede52cf91ed5e36ab5a987e8fb485d54c857fd1762f133252a827d760ac92dd36b9973897e30c3ab1d89f64bacd5761ffe6815b75c990c2c2f9
6
+ metadata.gz: dcbb4604f956d78acaea611bb760ecea97a1afcc15bc390b9c5a64c5d9654f5d08e83c57ed24da55aca0a1a98da074a0047a754cadf5f74ee8ba832fb6e93b3e
7
+ data.tar.gz: 47b7d7887f770f5712b5f020afd260fcefcb4b09a0c707e1b2bc921c38d2944bf6045b6b9752295790806018d98cd95d43313f9079719bcb3c808ff9f6e6c2ad
@@ -6,7 +6,7 @@ This document tracks the ESLint errors currently suppressed in the codebase and
6
6
 
7
7
  **As of 2025-10-14**: All TypeScript files in `package/` directory are temporarily excluded from linting via the ignore pattern `package/**/*.ts` in `eslint.config.js`. This allows the project to adopt ESLint configuration without requiring immediate fixes to all existing issues.
8
8
 
9
- **Latest Update**: Fixed all `class-methods-use-this` violations by converting FileWriter methods to static methods (4 violations resolved).
9
+ **Latest Update**: Fixed all `no-param-reassign` violations by refactoring to create new objects instead of mutating parameters (7 violations resolved).
10
10
 
11
11
  ## Current Linting Status
12
12
 
@@ -19,12 +19,12 @@ This document tracks the ESLint errors currently suppressed in the codebase and
19
19
 
20
20
  **TypeScript files** (currently ignored via `package/**/*.ts`):
21
21
 
22
- - **Estimated suppressed errors: ~172** (from sample analysis)
23
- - TypeScript type-safety issues: ~114 (66%)
24
- - Style/convention issues: ~58 (34%)
22
+ - **Estimated suppressed errors: ~163** (from sample analysis)
23
+ - TypeScript type-safety issues: ~114 (70%)
24
+ - Style/convention issues: ~49 (30%)
25
25
 
26
26
  **Target**: Reduce suppressed errors by 50% within Q1 2025
27
- **Last Updated**: 2025-10-14
27
+ **Last Updated**: 2025-10-15
28
28
 
29
29
  ## Priority Matrix
30
30
 
@@ -33,9 +33,9 @@ This document tracks the ESLint errors currently suppressed in the codebase and
33
33
  | `@typescript-eslint/no-explicit-any` | High | High | P1 | 22 |
34
34
  | `@typescript-eslint/no-unsafe-*` | High | High | P1 | 85 |
35
35
  | `config.ts` type safety | High | Medium | P1 | 7 |
36
- | `no-param-reassign` | Medium | Low | P2 | 7 |
36
+ | `no-param-reassign` | Medium | Low | P2 | 0 |
37
37
  | `class-methods-use-this` | Low | Low | P3 | 0 |
38
- | `no-nested-ternary` | Low | Low | P3 | 3 |
38
+ | `no-nested-ternary` | Low | Low | P3 | 0 |
39
39
  | `import/prefer-default-export` | Low | Medium | P3 | 9 |
40
40
  | `global-require` | Medium | High | P2 | 3 |
41
41
  | Other style issues | Low | Low | P3 | 31 |
@@ -80,15 +80,13 @@ This document tracks the ESLint errors currently suppressed in the codebase and
80
80
 
81
81
  ✅ **FIXED** - All FileWriter methods that didn't use instance state have been converted to static methods
82
82
 
83
- #### `no-nested-ternary` (3 instances)
83
+ #### `no-nested-ternary` (0 instances)
84
84
 
85
- **Fix strategy:** Refactor to if-else statements
85
+ **FIXED** - All nested ternary expressions have been refactored to if-else statements for better readability
86
86
 
87
- #### `no-param-reassign` (7 instances)
87
+ #### `no-param-reassign` (0 instances)
88
88
 
89
- **Files affected:** `configExporter/cli.ts`
90
- **Why suppressed:** Common pattern for option objects
91
- **Fix strategy:** Create new objects instead of mutating parameters
89
+ **FIXED** - Refactored `applyDefaults` function to return new objects instead of mutating parameters
92
90
 
93
91
  #### `no-underscore-dangle` (2 instances)
94
92
 
@@ -115,10 +113,11 @@ This document tracks the ESLint errors currently suppressed in the codebase and
115
113
  - Added proper type annotations for `requireOrError` calls
116
114
  - Configured appropriate global rule disables (`no-console`, `no-restricted-syntax`)
117
115
  - ✅ **Fixed `class-methods-use-this`** - Converted FileWriter methods to static methods
116
+ - ✅ **Fixed `no-nested-ternary`** - Refactored to if-else statements for better readability
117
+ - ✅ **Fixed `no-param-reassign`** - Refactored `applyDefaults` to return new objects instead of mutating parameters
118
118
 
119
119
  🔧 Could still fix (low risk):
120
120
 
121
- - `no-nested-ternary` - Refactor conditionals
122
121
  - `no-useless-escape` - Remove unnecessary escapes
123
122
  - Unused variables - Remove or prefix with underscore
124
123
 
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- shakapacker (9.3.0.beta.2)
4
+ shakapacker (9.3.0.beta.4)
5
5
  activesupport (>= 5.2)
6
6
  package_json
7
7
  rack-proxy (>= 0.6.1)
data/README.md CHANGED
@@ -477,8 +477,8 @@ And the main layout has:
477
477
  is the same as using this in the main layout:
478
478
 
479
479
  ```erb
480
- <%= javascript_pack_tag 'calendar', 'map', application' %>
481
- <%= stylesheet_pack_tag 'calendar', 'map', application' %>
480
+ <%= javascript_pack_tag 'calendar', 'map', 'application' %>
481
+ <%= stylesheet_pack_tag 'calendar', 'map', 'application' %>
482
482
  ```
483
483
 
484
484
  However, you typically can't do that in the main layout, as the view and partial codes will depend on the route.
@@ -490,12 +490,13 @@ Thus, you can distribute the logic of what packs are needed for any route. All t
490
490
  The typical issue is that your layout might reference some partials that need to configure packs. A good way to solve this problem is to use `content_for` to ensure that the code to render your partial comes before the call to `javascript_pack_tag`.
491
491
 
492
492
  ```erb
493
- <% content_for :footer do
494
- render 'shared/footer' %>
493
+ <% content_for :footer do %>
494
+ <%= render 'shared/footer' %>
495
+ <% end %>
495
496
 
496
497
  <%= javascript_pack_tag %>
497
498
 
498
- <%= content_for :footer %>
499
+ <%= yield :footer %>
499
500
  ```
500
501
 
501
502
  There is also `prepend_javascript_pack_tag` that will put the entry at the front of the queue. This is handy when you want an entry in the main layout to go before the partial and main layout `append_javascript_pack_tag` entries.
@@ -522,11 +523,13 @@ And the main layout has:
522
523
  is the same as using this in the main layout:
523
524
 
524
525
  ```erb
525
- <%= javascript_pack_tag 'main', 'calendar', 'map', application' %>
526
+ <%= javascript_pack_tag 'main', 'calendar', 'map', 'application' %>
526
527
  ```
527
528
 
528
529
  For alternative options for setting the additional packs, [see this discussion](https://github.com/shakacode/shakapacker/issues/39).
529
530
 
531
+ **Important:** To prevent FOUC (Flash of Unstyled Content), always place `stylesheet_pack_tag` in the `<head>` section of your layout. When using `append_*` helpers with dynamic pack loading (e.g., React on Rails), use the `content_for` pattern to control execution order. See the [Preventing FOUC guide](./docs/preventing_fouc.md) for detailed examples.
532
+
530
533
  #### View Helper: `asset_pack_path`
531
534
 
532
535
  If you want to link a static asset for `<img />` tag, you can use the `asset_pack_path` helper:
@@ -12,6 +12,7 @@ This guide covers all configuration options available in `config/shakapacker.yml
12
12
  - [Compilation Options](#compilation-options)
13
13
  - [Advanced Options](#advanced-options)
14
14
  - [Environment-Specific Configuration](#environment-specific-configuration)
15
+ - [Build Configurations (config/shakapacker-builds.yml)](#build-configurations-configshakapacker-buildsyml)
15
16
 
16
17
  ## Basic Configuration
17
18
 
@@ -608,6 +609,106 @@ default: &default
608
609
  - packages/ui-components
609
610
  ```
610
611
 
612
+ ## Build Configurations (config/shakapacker-builds.yml)
613
+
614
+ Shakapacker supports defining reusable build configurations in `config/shakapacker-builds.yml`. This allows you to run predefined builds with a simple command, making it easy to switch between different build scenarios.
615
+
616
+ ### Creating a Build Configuration File
617
+
618
+ Generate `config/shakapacker-builds.yml` with example builds:
619
+
620
+ ```bash
621
+ bin/shakapacker --init # Creates config/shakapacker-builds.yml
622
+ ```
623
+
624
+ This generates a file with example builds for common scenarios (HMR development, standard development, and production).
625
+
626
+ ### Running Builds by Name
627
+
628
+ Once you have `config/shakapacker-builds.yml`, you can run builds by name:
629
+
630
+ ```bash
631
+ # List available builds
632
+ bin/shakapacker --list-builds
633
+
634
+ # Run a specific build
635
+ bin/shakapacker --build dev-hmr # Client bundle with HMR (automatically uses dev server)
636
+ bin/shakapacker --build prod # Client and server bundles for production
637
+ bin/shakapacker --build dev # Client bundle for development
638
+ ```
639
+
640
+ ### Build Configuration Format
641
+
642
+ Example `config/shakapacker-builds.yml`:
643
+
644
+ ```yaml
645
+ builds:
646
+ dev-hmr:
647
+ description: Client bundle with HMR (React Fast Refresh)
648
+ bundler: rspack # Optional: override assets_bundler from config/shakapacker.yml
649
+ environment:
650
+ NODE_ENV: development
651
+ RAILS_ENV: development
652
+ WEBPACK_SERVE: "true" # Automatically uses bin/shakapacker-dev-server
653
+ outputs:
654
+ - client
655
+ config: config/${BUNDLER}/custom.config.js # Optional: custom config file with variable substitution
656
+
657
+ prod:
658
+ description: Production client and server bundles
659
+ environment:
660
+ NODE_ENV: production
661
+ RAILS_ENV: production
662
+ outputs:
663
+ - client # Multiple outputs - builds both client and server bundles
664
+ - server
665
+ ```
666
+
667
+ ### Build Configuration Options
668
+
669
+ - **`description`** (optional): Human-readable description of the build
670
+ - **`bundler`** (optional): Override the default bundler from `config/shakapacker.yml` (`webpack` or `rspack`)
671
+ - **`dev_server`** (optional): Boolean flag to force routing to dev server (overrides environment variable detection)
672
+ - **`environment`**: Environment variables to set when running the build
673
+ - **`outputs`**: Array of output types - can include `client`, `server`, or both for multiple bundles in a single build
674
+ - **`config`** (optional): Custom config file path (supports `${BUNDLER}` variable substitution)
675
+ - **`bundler_env`** (optional): Key-value pairs passed as bundler `--env` flags (e.g., `{ analyze: true }` becomes `--env analyze=true`)
676
+
677
+ ### Automatic Dev Server Detection
678
+
679
+ Shakapacker automatically uses `bin/shakapacker-dev-server` instead of the regular build command when:
680
+
681
+ 1. The build has `dev_server: true` explicitly set (preferred method - takes precedence over environment variables), OR
682
+ 2. The build has `WEBPACK_SERVE=true` or `HMR=true` in its environment variables (fallback for backward compatibility)
683
+
684
+ Example:
685
+
686
+ ```bash
687
+ # These are equivalent:
688
+ bin/shakapacker --build dev-hmr
689
+ WEBPACK_SERVE=true bin/shakapacker-dev-server # (with dev-hmr environment vars)
690
+ ```
691
+
692
+ ### Variable Substitution
693
+
694
+ The `config` field supports `${BUNDLER}` substitution:
695
+
696
+ ```yaml
697
+ builds:
698
+ custom:
699
+ bundler: rspack
700
+ config: config/${BUNDLER}/custom.config.js # Becomes: config/rspack/custom.config.js
701
+ ```
702
+
703
+ ### When to Use Build Configurations
704
+
705
+ Build configurations are useful for:
706
+
707
+ - **Multiple build scenarios**: Use when you need different builds for HMR development, standard development, and production
708
+ - **CI/CD pipelines**: Use when you want predefined builds that can be referenced in deployment scripts
709
+ - **Team consistency**: Use to ensure all developers use the same build configurations
710
+ - **Complex setups**: Use to manage different bundler configs or environment variables for different scenarios
711
+
611
712
  ## Troubleshooting
612
713
 
613
714
  If you encounter configuration issues:
@@ -0,0 +1,132 @@
1
+ # Preventing FOUC (Flash of Unstyled Content)
2
+
3
+ ## Overview
4
+
5
+ FOUC (Flash of Unstyled Content) occurs when content is rendered before stylesheets load, causing a brief flash of unstyled content. This guide explains how to prevent FOUC when using Shakapacker's view helpers.
6
+
7
+ ## Basic Solution
8
+
9
+ Place `stylesheet_pack_tag` in the `<head>` section of your layout, not at the bottom of the `<body>`. This ensures styles load before content is rendered.
10
+
11
+ **Recommended layout structure:**
12
+
13
+ ```erb
14
+ <!DOCTYPE html>
15
+ <html>
16
+ <head>
17
+ <meta charset="UTF-8">
18
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
19
+ <title>My App</title>
20
+
21
+ <%= stylesheet_pack_tag 'application', media: 'all' %>
22
+ <%= javascript_pack_tag 'application', defer: true %>
23
+ </head>
24
+ <body>
25
+ <%= yield %>
26
+ </body>
27
+ </html>
28
+ ```
29
+
30
+ ## Advanced: Using `content_for` with Dynamic Pack Loading
31
+
32
+ If you're using libraries that dynamically append packs during rendering (like React on Rails with `auto_load_bundle`), or if you need to append packs from views/partials, you must ensure that all `append_*` helpers execute before the pack tags render.
33
+
34
+ Rails' `content_for` pattern solves this execution order problem.
35
+
36
+ ### The `content_for :body_content` Pattern
37
+
38
+ This pattern renders the body content first, allowing all append calls to register before the pack tags in the head are rendered:
39
+
40
+ ```erb
41
+ <% content_for :body_content do %>
42
+ <%= render 'shared/header' %>
43
+ <%= yield %>
44
+ <%= render 'shared/footer' %>
45
+ <% end %>
46
+
47
+ <!DOCTYPE html>
48
+ <html>
49
+ <head>
50
+ <meta charset="UTF-8">
51
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
52
+ <title>My App</title>
53
+
54
+ <%= stylesheet_pack_tag 'application', media: 'all' %>
55
+ <%= javascript_pack_tag 'application', defer: true %>
56
+ </head>
57
+ <body>
58
+ <%= yield :body_content %>
59
+ </body>
60
+ </html>
61
+ ```
62
+
63
+ **How this works:**
64
+
65
+ 1. The `content_for :body_content` block executes first during template rendering
66
+ 2. Any `append_stylesheet_pack_tag` or `append_javascript_pack_tag` calls in your views/partials register their packs
67
+ 3. Libraries like React on Rails can auto-append component-specific packs during rendering
68
+ 4. The pack tags in `<head>` then render with all registered appends
69
+ 5. Finally, `yield :body_content` outputs the pre-rendered content
70
+
71
+ **Result:**
72
+
73
+ - ✅ All appends (explicit + auto) happen before pack tags
74
+ - ✅ Stylesheets load in head, eliminating FOUC
75
+ - ✅ Works with `auto_load_bundle` and similar features
76
+
77
+ ### Alternative: Using `yield :head` for Explicit Appends
78
+
79
+ For simpler cases where you know which packs you need upfront and can explicitly specify them, you can use `content_for :head` in your views and yield it in your layout.
80
+
81
+ **Layout (app/views/layouts/application.html.erb):**
82
+
83
+ ```erb
84
+ <!DOCTYPE html>
85
+ <html>
86
+ <head>
87
+ <meta charset="UTF-8">
88
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
89
+ <title>My App</title>
90
+
91
+ <%= yield :head %>
92
+ <%= stylesheet_pack_tag 'application', media: 'all' %>
93
+ <%= javascript_pack_tag 'application', defer: true %>
94
+ </head>
95
+ <body>
96
+ <%= yield %>
97
+ </body>
98
+ </html>
99
+ ```
100
+
101
+ **View (app/views/pages/show.html.erb):**
102
+
103
+ ```erb
104
+ <% content_for :head do %>
105
+ <%= append_stylesheet_pack_tag 'my-component' %>
106
+ <%= append_javascript_pack_tag 'my-component' %>
107
+ <% end %>
108
+
109
+ <h1>My Page</h1>
110
+ <p>Content goes here...</p>
111
+ ```
112
+
113
+ This approach works when:
114
+
115
+ - You're not using auto-appending libraries
116
+ - You can explicitly list all required packs in each view
117
+ - You don't need dynamic pack determination
118
+
119
+ ## Key Takeaways
120
+
121
+ - Always place `stylesheet_pack_tag` in `<head>` to prevent FOUC
122
+ - Use `content_for` to control execution order when using `append_*` helpers
123
+ - Ensure `append_*` helpers execute before the main pack tags
124
+ - JavaScript can use `defer: true` (default) or be placed at end of `<body>`
125
+ - The `content_for :body_content` pattern is essential when using auto-appending libraries
126
+
127
+ ## Related
128
+
129
+ - [View Helpers Documentation](../README.md#view-helpers)
130
+ - [Troubleshooting Guide](./troubleshooting.md)
131
+ - Original issue: [#720](https://github.com/shakacode/shakapacker/issues/720)
132
+ - Working implementation: [react-webpack-rails-tutorial PR #686](https://github.com/shakacode/react-webpack-rails-tutorial/pull/686)
@@ -1,5 +1,9 @@
1
1
  # Troubleshooting
2
2
 
3
+ ## Flash of Unstyled Content (FOUC)
4
+
5
+ If you're experiencing FOUC where content briefly appears unstyled before CSS loads, see the [Preventing FOUC guide](./preventing_fouc.md) for solutions including proper `stylesheet_pack_tag` placement and `content_for` patterns for dynamic pack loading.
6
+
3
7
  ## Debugging your webpack config
4
8
 
5
9
  1. Read the error message carefully. The error message will tell you the precise key value
data/eslint.config.js CHANGED
@@ -177,8 +177,7 @@ module.exports = [
177
177
  "@typescript-eslint/no-unsafe-argument": "off",
178
178
  "@typescript-eslint/no-explicit-any": "off",
179
179
  "no-useless-escape": "off",
180
- "no-continue": "off",
181
- "no-nested-ternary": "off"
180
+ "no-continue": "off"
182
181
  }
183
182
  },
184
183
  {
@@ -193,9 +192,7 @@ module.exports = [
193
192
  "@typescript-eslint/no-unsafe-function-type": "off",
194
193
  "@typescript-eslint/no-unused-vars": "off",
195
194
  "@typescript-eslint/require-await": "off",
196
- "no-param-reassign": "off",
197
195
  "no-await-in-loop": "off",
198
- "no-nested-ternary": "off",
199
196
  "import/prefer-default-export": "off",
200
197
  "global-require": "off",
201
198
  "no-underscore-dangle": "off"
@@ -215,8 +212,7 @@ module.exports = [
215
212
  "@typescript-eslint/no-unsafe-argument": "off",
216
213
  "@typescript-eslint/no-explicit-any": "off",
217
214
  "no-useless-escape": "off",
218
- "no-continue": "off",
219
- "no-nested-ternary": "off"
215
+ "no-continue": "off"
220
216
  }
221
217
  },
222
218
  {
@@ -0,0 +1,147 @@
1
+ require "yaml"
2
+
3
+ module Shakapacker
4
+ class BuildConfigLoader
5
+ attr_reader :config_file_path
6
+
7
+ def initialize(config_file_path = nil)
8
+ @config_file_path = config_file_path || File.join(Dir.pwd, "config", "shakapacker-builds.yml")
9
+ end
10
+
11
+ def exists?
12
+ File.exist?(@config_file_path)
13
+ end
14
+
15
+ def load_build(build_name)
16
+ unless exists?
17
+ raise ArgumentError, "Config file not found: #{@config_file_path}\n" \
18
+ "Run 'bin/shakapacker --init' to generate a sample config file."
19
+ end
20
+
21
+ config = load_config
22
+ fetch_build_or_raise(config, build_name)
23
+ end
24
+
25
+ def resolve_build_config(build_name, default_bundler: "webpack")
26
+ config = load_config
27
+ build = fetch_build_or_raise(config, build_name)
28
+
29
+ # Resolve bundler with precedence: build.bundler > config.default_bundler > default_bundler
30
+ bundler = build["bundler"] || config["default_bundler"] || default_bundler
31
+
32
+ # Get environment variables
33
+ environment = build["environment"] || {}
34
+
35
+ # Get config file path if specified
36
+ config_file = build["config"]
37
+ if config_file
38
+ # Expand ${BUNDLER} variable
39
+ config_file = config_file.gsub("${BUNDLER}", bundler)
40
+ end
41
+
42
+ # Get bundler_env for --env flags
43
+ bundler_env = build["bundler_env"] || {}
44
+
45
+ # Get outputs
46
+ outputs = build["outputs"] || []
47
+
48
+ # Validate outputs
49
+ if outputs.empty?
50
+ raise ArgumentError, "Build '#{build_name}' has empty outputs array. " \
51
+ "Please specify at least one output type (client, server, or all)."
52
+ end
53
+
54
+ {
55
+ name: build_name,
56
+ description: build["description"],
57
+ bundler: bundler,
58
+ dev_server: build["dev_server"],
59
+ environment: environment,
60
+ bundler_env: bundler_env,
61
+ outputs: outputs,
62
+ config_file: config_file
63
+ }
64
+ end
65
+
66
+ def uses_dev_server?(build_config)
67
+ # Check explicit dev_server flag first (preferred)
68
+ # Only return early if the value is explicitly set (not nil)
69
+ return build_config[:dev_server] unless build_config[:dev_server].nil?
70
+
71
+ # Fallback: check environment variables for backward compatibility
72
+ env = build_config[:environment]
73
+ return false unless env
74
+
75
+ # Handle both string "true" and boolean true from YAML
76
+ %w[WEBPACK_SERVE HMR].any? do |key|
77
+ value = env[key]
78
+ value.to_s.strip.casecmp("true").zero?
79
+ end
80
+ end
81
+
82
+ def list_builds
83
+ config = load_config
84
+ builds = config["builds"]
85
+
86
+ puts "\nAvailable builds in #{@config_file_path}:\n\n"
87
+
88
+ builds.each do |name, build|
89
+ bundler = build["bundler"] || config["default_bundler"] || "webpack (default)"
90
+ outputs = build["outputs"] ? build["outputs"].join(", ") : "missing (invalid)"
91
+
92
+ puts " #{name}"
93
+ puts " Description: #{build["description"]}" if build["description"]
94
+ puts " Bundler: #{bundler}"
95
+ puts " Outputs: #{outputs}"
96
+ puts ""
97
+ end
98
+ end
99
+
100
+ private
101
+
102
+ def fetch_build_or_raise(config, build_name)
103
+ build = config["builds"][build_name]
104
+ unless build
105
+ available = config["builds"].keys.join(", ")
106
+ raise ArgumentError, "Build '#{build_name}' not found in config file.\n" \
107
+ "Available builds: #{available}\n" \
108
+ "Use 'bin/shakapacker --list-builds' to see all available builds."
109
+ end
110
+ build
111
+ end
112
+
113
+ # Load YAML config file safely with Ruby version compatibility
114
+ # Ruby 3.1+ supports safe_load_file with aliases, older versions need safe_load
115
+ def load_config
116
+ begin
117
+ config = if YAML.respond_to?(:safe_load_file)
118
+ # Ruby 3.1+: Use safe_load_file with aliases enabled
119
+ YAML.safe_load_file(@config_file_path, aliases: true)
120
+ else
121
+ # Ruby 2.7-3.0: Use safe_load with aliases enabled
122
+ YAML.safe_load(
123
+ File.read(@config_file_path),
124
+ permitted_classes: [],
125
+ permitted_symbols: [],
126
+ aliases: true
127
+ )
128
+ end
129
+ rescue ArgumentError
130
+ # Fallback for older Psych versions without aliases support
131
+ config = YAML.safe_load(
132
+ File.read(@config_file_path),
133
+ permitted_classes: [],
134
+ permitted_symbols: []
135
+ )
136
+ end
137
+
138
+ unless config["builds"]&.is_a?(Hash)
139
+ raise ArgumentError, "Config file must contain a 'builds' object"
140
+ end
141
+
142
+ config
143
+ rescue Psych::SyntaxError => e
144
+ raise ArgumentError, "Invalid YAML in config file: #{e.message}"
145
+ end
146
+ end
147
+ end
@@ -8,12 +8,13 @@ class Shakapacker::Configuration
8
8
  attr_accessor :installing
9
9
  end
10
10
 
11
- attr_reader :root_path, :config_path, :env
11
+ attr_reader :root_path, :config_path, :env, :bundler_override
12
12
 
13
- def initialize(root_path:, config_path:, env:)
13
+ def initialize(root_path:, config_path:, env:, bundler_override: nil)
14
14
  @root_path = root_path
15
15
  @env = env
16
16
  @config_path = config_path
17
+ @bundler_override = bundler_override
17
18
  end
18
19
 
19
20
  def dev_server
@@ -97,6 +98,9 @@ class Shakapacker::Configuration
97
98
  end
98
99
 
99
100
  def assets_bundler
101
+ # CLI --bundler flag takes highest precedence
102
+ return @bundler_override if @bundler_override
103
+
100
104
  # Show deprecation warning if using old 'bundler' key
101
105
  if data.has_key?(:bundler) && !data.has_key?(:assets_bundler)
102
106
  $stderr.puts "⚠️ DEPRECATION WARNING: The 'bundler' configuration option is deprecated. Please use 'assets_bundler' instead to avoid confusion with Ruby's Bundler gem manager."