shakapacker 9.3.0.beta.2 → 9.3.0.beta.5

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.
@@ -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."
@@ -231,6 +235,10 @@ class Shakapacker::Configuration
231
235
  fetch(:integrity)
232
236
  end
233
237
 
238
+ def early_hints
239
+ fetch(:early_hints)
240
+ end
241
+
234
242
  private
235
243
  def validate_output_paths!
236
244
  # Skip validation if already validated to avoid redundant checks
@@ -18,9 +18,71 @@ module Shakapacker
18
18
  exit(0)
19
19
  end
20
20
 
21
+ # Check for --build flag
22
+ build_index = argv.index("--build")
23
+ if build_index
24
+ build_name = argv[build_index + 1]
25
+
26
+ unless build_name
27
+ $stderr.puts "[Shakapacker] Error: --build requires a build name"
28
+ $stderr.puts "Usage: bin/shakapacker-dev-server --build <name>"
29
+ exit(1)
30
+ end
31
+
32
+ loader = BuildConfigLoader.new
33
+
34
+ unless loader.exists?
35
+ $stderr.puts "[Shakapacker] Config file not found: #{loader.config_file_path}"
36
+ $stderr.puts "Run 'bin/shakapacker --init' to create one"
37
+ exit(1)
38
+ end
39
+
40
+ begin
41
+ build_config = loader.resolve_build_config(build_name)
42
+
43
+ # Check if this build is meant for dev server
44
+ unless loader.uses_dev_server?(build_config)
45
+ $stderr.puts "[Shakapacker] Error: Build '#{build_name}' is not configured for dev server (dev_server: false)"
46
+ $stderr.puts "[Shakapacker] Use this command instead:"
47
+ $stderr.puts " bin/shakapacker --build #{build_name}"
48
+ exit(1)
49
+ end
50
+
51
+ # Remove --build and build name from argv
52
+ remaining_argv = argv.dup
53
+ remaining_argv.delete_at(build_index + 1)
54
+ remaining_argv.delete_at(build_index)
55
+
56
+ run_with_build_config(remaining_argv, build_config)
57
+ return
58
+ rescue ArgumentError => e
59
+ $stderr.puts "[Shakapacker] #{e.message}"
60
+ exit(1)
61
+ end
62
+ end
63
+
21
64
  new(argv).run
22
65
  end
23
66
 
67
+ def self.run_with_build_config(argv, build_config)
68
+ # Apply build config environment variables
69
+ build_config[:environment].each do |key, value|
70
+ ENV[key] = value.to_s
71
+ end
72
+
73
+ # Set SHAKAPACKER_ASSETS_BUNDLER so JS/TS config files use the correct bundler
74
+ # This ensures the bundler override (from --bundler or build config) is respected
75
+ ENV["SHAKAPACKER_ASSETS_BUNDLER"] = build_config[:bundler]
76
+
77
+ puts "[Shakapacker] Running dev server for build: #{build_config[:name]}"
78
+ puts "[Shakapacker] Description: #{build_config[:description]}" if build_config[:description]
79
+ puts "[Shakapacker] Bundler: #{build_config[:bundler]}"
80
+ puts "[Shakapacker] Config file: #{build_config[:config_file]}" if build_config[:config_file]
81
+
82
+ # Pass bundler override so Configuration.assets_bundler reflects the build
83
+ new(argv, build_config, build_config[:bundler]).run
84
+ end
85
+
24
86
  def self.print_help
25
87
  puts <<~HELP
26
88
  ================================================================================
@@ -33,6 +95,17 @@ module Shakapacker
33
95
  -h, --help Show this help message
34
96
  -v, --version Show Shakapacker version
35
97
  --debug-shakapacker Enable Node.js debugging (--inspect-brk)
98
+ --build <name> Run a specific build configuration
99
+
100
+ Build configurations (config/shakapacker-builds.yml):
101
+ bin/shakapacker-dev-server --build dev-hmr # Run the 'dev-hmr' build
102
+
103
+ To manage builds:
104
+ bin/shakapacker --init # Create config file
105
+ bin/shakapacker --list-builds # List available builds
106
+
107
+ Note: You can also use bin/shakapacker --build with a build that has
108
+ WEBPACK_SERVE=true, and it will automatically use the dev server.
36
109
 
37
110
  Examples:
38
111
  bin/shakapacker-dev-server # Start dev server