shakapacker 9.2.0 → 9.3.0.beta.0

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.
Files changed (115) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
  3. data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
  4. data/.github/workflows/claude-code-review.yml +4 -5
  5. data/.github/workflows/claude.yml +1 -2
  6. data/.github/workflows/dummy.yml +4 -4
  7. data/.github/workflows/generator.yml +9 -9
  8. data/.github/workflows/node.yml +11 -2
  9. data/.github/workflows/ruby.yml +16 -16
  10. data/.github/workflows/test-bundlers.yml +9 -9
  11. data/.gitignore +4 -0
  12. data/CHANGELOG.md +19 -4
  13. data/CLAUDE.md +6 -1
  14. data/CONTRIBUTING.md +0 -1
  15. data/Gemfile.lock +1 -1
  16. data/README.md +14 -14
  17. data/TODO.md +10 -2
  18. data/TODO_v9.md +13 -3
  19. data/bin/export-bundler-config +1 -1
  20. data/conductor-setup.sh +1 -1
  21. data/conductor.json +1 -1
  22. data/docs/cdn_setup.md +13 -8
  23. data/docs/common-upgrades.md +2 -1
  24. data/docs/configuration.md +630 -0
  25. data/docs/css-modules-export-mode.md +120 -100
  26. data/docs/customizing_babel_config.md +16 -16
  27. data/docs/deployment.md +18 -0
  28. data/docs/developing_shakapacker.md +6 -0
  29. data/docs/optional-peer-dependencies.md +9 -4
  30. data/docs/peer-dependencies.md +17 -6
  31. data/docs/precompile_hook.md +342 -0
  32. data/docs/react.md +57 -47
  33. data/docs/releasing.md +0 -2
  34. data/docs/rspack.md +25 -21
  35. data/docs/rspack_migration_guide.md +335 -8
  36. data/docs/sprockets.md +1 -0
  37. data/docs/style_loader_vs_mini_css.md +12 -12
  38. data/docs/subresource_integrity.md +13 -7
  39. data/docs/transpiler-performance.md +40 -19
  40. data/docs/troubleshooting.md +0 -2
  41. data/docs/typescript-migration.md +48 -39
  42. data/docs/typescript.md +12 -8
  43. data/docs/using_esbuild_loader.md +10 -10
  44. data/docs/v6_upgrade.md +33 -20
  45. data/docs/v7_upgrade.md +8 -6
  46. data/docs/v8_upgrade.md +13 -12
  47. data/docs/v9_upgrade.md +2 -1
  48. data/eslint.config.fast.js +134 -0
  49. data/eslint.config.js +140 -0
  50. data/knip.ts +54 -0
  51. data/lib/install/bin/export-bundler-config +1 -1
  52. data/lib/install/config/shakapacker.yml +16 -5
  53. data/lib/shakapacker/compiler.rb +80 -0
  54. data/lib/shakapacker/configuration.rb +33 -5
  55. data/lib/shakapacker/dev_server_runner.rb +140 -1
  56. data/lib/shakapacker/doctor.rb +294 -65
  57. data/lib/shakapacker/instance.rb +8 -3
  58. data/lib/shakapacker/runner.rb +244 -8
  59. data/lib/shakapacker/version.rb +1 -1
  60. data/lib/tasks/shakapacker/doctor.rake +42 -2
  61. data/package/babel/preset.ts +7 -4
  62. data/package/config.ts +42 -30
  63. data/package/configExporter/cli.ts +799 -208
  64. data/package/configExporter/configFile.ts +520 -0
  65. data/package/configExporter/fileWriter.ts +12 -8
  66. data/package/configExporter/index.ts +9 -1
  67. data/package/configExporter/types.ts +36 -2
  68. data/package/configExporter/yamlSerializer.ts +22 -8
  69. data/package/dev_server.ts +1 -1
  70. data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +11 -5
  71. data/package/environments/base.ts +18 -13
  72. data/package/environments/development.ts +1 -1
  73. data/package/environments/production.ts +4 -1
  74. data/package/index.d.ts +50 -3
  75. data/package/index.d.ts.template +50 -0
  76. data/package/index.ts +7 -7
  77. data/package/loaders.d.ts +2 -2
  78. data/package/optimization/rspack.ts +1 -1
  79. data/package/plugins/rspack.ts +15 -4
  80. data/package/plugins/webpack.ts +7 -3
  81. data/package/rspack/index.ts +10 -2
  82. data/package/rules/raw.ts +3 -2
  83. data/package/rules/sass.ts +1 -1
  84. data/package/types/README.md +15 -13
  85. data/package/types/index.ts +5 -5
  86. data/package/types.ts +0 -1
  87. data/package/utils/defaultConfigPath.ts +4 -1
  88. data/package/utils/errorCodes.ts +129 -100
  89. data/package/utils/errorHelpers.ts +34 -29
  90. data/package/utils/getStyleRule.ts +5 -2
  91. data/package/utils/helpers.ts +21 -11
  92. data/package/utils/pathValidation.ts +43 -35
  93. data/package/utils/requireOrError.ts +1 -1
  94. data/package/utils/snakeToCamelCase.ts +1 -1
  95. data/package/utils/typeGuards.ts +132 -83
  96. data/package/utils/validateDependencies.ts +1 -1
  97. data/package/webpack-types.d.ts +3 -3
  98. data/package/webpackDevServerConfig.ts +22 -10
  99. data/package-lock.json +2 -2
  100. data/package.json +36 -28
  101. data/scripts/type-check-no-emit.js +1 -1
  102. data/test/configExporter/configFile.test.js +392 -0
  103. data/test/configExporter/integration.test.js +275 -0
  104. data/test/helpers.js +1 -1
  105. data/test/package/configExporter.test.js +154 -0
  106. data/test/package/helpers.test.js +2 -2
  107. data/test/package/rules/sass-version-parsing.test.js +71 -0
  108. data/test/package/rules/sass.test.js +2 -4
  109. data/test/package/rules/sass1.test.js +1 -3
  110. data/test/package/rules/sass16.test.js +23 -0
  111. data/tools/README.md +15 -5
  112. data/tsconfig.eslint.json +2 -9
  113. data/yarn.lock +1894 -1492
  114. metadata +19 -3
  115. data/.eslintignore +0 -5
@@ -1,9 +1,11 @@
1
1
  require_relative "utils/misc"
2
2
  require_relative "utils/manager"
3
3
  require_relative "configuration"
4
+ require_relative "version"
4
5
 
5
6
  require "package_json"
6
7
  require "pathname"
8
+ require "stringio"
7
9
 
8
10
  module Shakapacker
9
11
  class Runner
@@ -24,6 +26,16 @@ module Shakapacker
24
26
  ].freeze
25
27
  def self.run(argv)
26
28
  $stdout.sync = true
29
+
30
+ # Show Shakapacker help and exit (don't call bundler)
31
+ if argv.include?("--help") || argv.include?("-h")
32
+ print_help
33
+ exit(0)
34
+ elsif argv.include?("--version") || argv.include?("-v")
35
+ print_version
36
+ exit(0)
37
+ end
38
+
27
39
  Shakapacker.ensure_node_env!
28
40
 
29
41
  # Create a single runner instance to avoid loading configuration twice.
@@ -112,9 +124,22 @@ module Shakapacker
112
124
  puts "[Shakapacker] Final command: #{cmd.join(" ")}"
113
125
  puts "[Shakapacker] Working directory: #{@app_path}"
114
126
 
127
+ watch_mode = @argv.include?("--watch") || @argv.include?("-w")
128
+ start_time = Time.now unless watch_mode
129
+
115
130
  Dir.chdir(@app_path) do
116
- Kernel.exec env, *cmd
131
+ system(env, *cmd)
117
132
  end
133
+
134
+ if !watch_mode && start_time
135
+ bundler_name = @config.rspack? ? "rspack" : "webpack"
136
+ elapsed_time = Time.now - start_time
137
+ minutes = (elapsed_time / 60).floor
138
+ seconds = (elapsed_time % 60).round(2)
139
+ time_display = minutes > 0 ? "#{minutes}:#{format('%05.2f', seconds)}s" : "#{elapsed_time.round(2)}s"
140
+ puts "[Shakapacker] Completed #{bundler_name} build in #{time_display} (#{elapsed_time.round(2)}s)"
141
+ end
142
+ exit($?.exitstatus || 1) unless $?.success?
118
143
  end
119
144
 
120
145
  protected
@@ -123,6 +148,213 @@ module Shakapacker
123
148
  BASE_COMMANDS
124
149
  end
125
150
 
151
+ def print_config_not_found_error(bundler_type, config_path, config_dir)
152
+ $stderr.puts "[Shakapacker] ERROR: #{bundler_type} config #{config_path} not found."
153
+ $stderr.puts ""
154
+ $stderr.puts "Please run 'bundle exec rails shakapacker:install' to install Shakapacker with default configs,"
155
+ $stderr.puts "or create the missing config file."
156
+ $stderr.puts ""
157
+ $stderr.puts "If your config file is in a different location, you can configure it in config/shakapacker.yml:"
158
+ $stderr.puts ""
159
+ $stderr.puts " assets_bundler_config_path: your/custom/path"
160
+ $stderr.puts ""
161
+ $stderr.puts "Current configured path: #{config_dir}"
162
+ end
163
+
164
+ def self.print_help
165
+ puts <<~HELP
166
+ ================================================================================
167
+ SHAKAPACKER - Rails Webpack/Rspack Integration
168
+ ================================================================================
169
+
170
+ Usage: bin/shakapacker [options]
171
+
172
+ Shakapacker-specific options:
173
+ -h, --help Show this help message
174
+ -v, --version Show Shakapacker version
175
+ --debug-shakapacker Enable Node.js debugging (--inspect-brk)
176
+ --trace-deprecation Show stack traces for deprecations
177
+ --no-deprecation Silence deprecation warnings
178
+
179
+ Examples:
180
+ bin/shakapacker # Build for production
181
+ bin/shakapacker --mode development # Build for development
182
+ bin/shakapacker --watch # Watch mode
183
+ bin/shakapacker --mode development --analyze # Development build with analysis
184
+ bin/shakapacker --debug-shakapacker # Debug with Node inspector
185
+
186
+ HELP
187
+
188
+ print_bundler_help
189
+
190
+ puts <<~HELP
191
+
192
+ Options managed by Shakapacker (configured via config files):
193
+ --config Set automatically based on assets_bundler_config_path
194
+ (defaults to config/webpack or config/rspack)
195
+ --node-env Set from RAILS_ENV or NODE_ENV
196
+ HELP
197
+ end
198
+
199
+ def self.print_bundler_help
200
+ bundler_type, bundler_help = get_bundler_help
201
+
202
+ if bundler_help
203
+ bundler_name = bundler_type == :rspack ? "RSPACK" : "WEBPACK"
204
+ puts "=" * 80
205
+ puts "AVAILABLE #{bundler_name} OPTIONS (Passed directly to #{bundler_name.downcase})"
206
+ puts "=" * 80
207
+ puts
208
+ puts filter_managed_options(bundler_help)
209
+ puts
210
+ puts "For complete documentation:"
211
+ if bundler_type == :rspack
212
+ puts " https://rspack.dev/api/cli"
213
+ else
214
+ puts " https://webpack.js.org/api/cli/"
215
+ end
216
+ else
217
+ puts "For complete documentation:"
218
+ puts " Webpack: https://webpack.js.org/api/cli/"
219
+ puts " Rspack: https://rspack.dev/api/cli"
220
+ end
221
+ end
222
+
223
+ def self.get_bundler_help
224
+ execute_bundler_command("--help") { |stdout| stdout }
225
+ end
226
+
227
+ # Filter bundler help output to remove Shakapacker-managed options
228
+ #
229
+ # This method processes the raw help output from webpack/rspack and removes:
230
+ # 1. Command sections (e.g., "Commands: webpack build")
231
+ # 2. Options that Shakapacker manages automatically (--config, --nodeEnv, etc.)
232
+ # 3. Help/version flags (shown separately in Shakapacker's help)
233
+ #
234
+ # The filtering uses stateful line-by-line processing:
235
+ # - in_commands_section: tracks when we're inside a Commands: block
236
+ # - skip_until_blank: tracks multi-line option descriptions to skip entirely
237
+ #
238
+ # Note: This relies on bundler help format conventions. If webpack/rspack
239
+ # significantly changes their help output format, this may need adjustment.
240
+ def self.filter_managed_options(help_text)
241
+ lines = help_text.lines
242
+ filtered_lines = []
243
+ skip_until_blank = false
244
+ in_commands_section = false
245
+
246
+ lines.each do |line|
247
+ # Skip the [options] line and Commands section headers
248
+ # These appear in formats like "[options]" or "Commands:"
249
+ if line.match?(/^\[options\]/) || line.match?(/^Commands:/)
250
+ in_commands_section = true
251
+ next
252
+ end
253
+
254
+ # Continue skipping until we exit the commands section
255
+ # Exit when we hit "Options:" header or double blank lines
256
+ if in_commands_section
257
+ if line.match?(/^Options:/) || (line.strip.empty? && filtered_lines.last&.strip&.empty?)
258
+ in_commands_section = false
259
+ else
260
+ next
261
+ end
262
+ end
263
+
264
+ # Skip options that Shakapacker manages and their descriptions
265
+ # These options are shown in the "Options managed by Shakapacker" section
266
+ if line.match?(/^\s*(-c,\s*)?--config\b/) ||
267
+ line.match?(/^\s*--configName\b/) ||
268
+ line.match?(/^\s*--configLoader\b/) ||
269
+ line.match?(/^\s*--nodeEnv\b/) ||
270
+ line.match?(/^\s*(-h,\s*)?--help\b/) ||
271
+ line.match?(/^\s*(-v,\s*)?--version\b/)
272
+ skip_until_blank = true
273
+ next
274
+ end
275
+
276
+ # Continue skipping lines that are part of a filtered option's description
277
+ # Reset when we hit a blank line or the start of a new option (starts with -)
278
+ if skip_until_blank
279
+ if line.strip.empty? || line.match?(/^\s*-/)
280
+ skip_until_blank = false
281
+ else
282
+ next
283
+ end
284
+ end
285
+
286
+ filtered_lines << line
287
+ end
288
+
289
+ filtered_lines.join
290
+ end
291
+
292
+ def self.print_version
293
+ puts "Shakapacker #{Shakapacker::VERSION}"
294
+ puts "Framework: Rails #{Rails.version}" if defined?(Rails)
295
+
296
+ # Try to get bundler version
297
+ bundler_type, bundler_version = get_bundler_version
298
+ if bundler_version
299
+ bundler_name = bundler_type == :rspack ? "Rspack" : "Webpack"
300
+ puts "Bundler: #{bundler_name} #{bundler_version}"
301
+ end
302
+ end
303
+
304
+ def self.get_bundler_version
305
+ execute_bundler_command("--version") { |stdout| stdout.strip }
306
+ end
307
+
308
+ # Shared helper to execute bundler commands with output suppression
309
+ # Returns [bundler_type, processed_output] or [nil, nil] on error
310
+ #
311
+ # @param bundler_args [String, Array<String>] Arguments to pass to bundler command
312
+ # @yield [stdout] Block to process the command output
313
+ # @yieldparam stdout [String] The raw stdout from the bundler command
314
+ # @yieldreturn [Object] The processed output to return
315
+ def self.execute_bundler_command(*bundler_args)
316
+ # Check if we're in a Rails project with necessary files
317
+ app_path = File.expand_path(".", Dir.pwd)
318
+ config_path = ENV["SHAKAPACKER_CONFIG"] || File.join(app_path, "config/shakapacker.yml")
319
+ return [nil, nil] unless File.exist?(config_path)
320
+
321
+ original_stdout = $stdout
322
+ original_stderr = $stderr
323
+
324
+ begin
325
+ # Suppress any output during config loading
326
+ $stdout = StringIO.new
327
+ $stderr = StringIO.new
328
+
329
+ # Try to detect bundler type
330
+ runner = new([])
331
+ return [nil, nil] unless runner.config
332
+
333
+ bundler_type = runner.config.rspack? ? :rspack : :webpack
334
+ bundler_name = bundler_type == :rspack ? "rspack" : "webpack"
335
+ cmd = runner.package_json.manager.native_exec_command(bundler_name, bundler_args.flatten)
336
+
337
+ # Restore output before running command
338
+ $stdout = original_stdout
339
+ $stderr = original_stderr
340
+
341
+ # Capture command output
342
+ require "open3"
343
+ stdout, _stderr, status = Open3.capture3(*cmd)
344
+ return [nil, nil] unless status.success?
345
+
346
+ # Process output using the provided block
347
+ processed_output = yield(stdout)
348
+ [bundler_type, processed_output]
349
+ rescue StandardError => e
350
+ [nil, nil]
351
+ ensure
352
+ # Always restore output streams
353
+ $stdout = original_stdout
354
+ $stderr = original_stderr
355
+ end
356
+ end
357
+
126
358
  private
127
359
  def find_assets_bundler_config
128
360
  if @config.rspack?
@@ -133,9 +365,11 @@ module Shakapacker
133
365
  end
134
366
 
135
367
  def find_rspack_config_with_fallback
136
- # First try rspack-specific paths
368
+ config_dir = @config.assets_bundler_config_path
369
+
370
+ # First try rspack-specific paths in the configured directory
137
371
  rspack_paths = %w[ts js].map do |ext|
138
- File.join(@app_path, "config/rspack/rspack.config.#{ext}")
372
+ File.join(@app_path, config_dir, "rspack.config.#{ext}")
139
373
  end
140
374
 
141
375
  puts "[Shakapacker] Looking for Rspack config in: #{rspack_paths.join(", ")}"
@@ -147,31 +381,33 @@ module Shakapacker
147
381
 
148
382
  # Fallback to webpack config with deprecation warning
149
383
  webpack_paths = %w[ts js].map do |ext|
150
- File.join(@app_path, "config/webpack/webpack.config.#{ext}")
384
+ File.join(@app_path, config_dir, "webpack.config.#{ext}")
151
385
  end
152
386
 
153
387
  puts "[Shakapacker] Rspack config not found, checking for webpack config fallback..."
154
388
  webpack_path = webpack_paths.find { |f| File.exist?(f) }
155
389
  if webpack_path
156
390
  $stderr.puts "⚠️ DEPRECATION WARNING: Using webpack config file for Rspack assets bundler."
157
- $stderr.puts " Please create config/rspack/rspack.config.js and migrate your configuration."
391
+ $stderr.puts " Please create #{config_dir}/rspack.config.js and migrate your configuration."
158
392
  $stderr.puts " Using: #{webpack_path}"
159
393
  return webpack_path
160
394
  end
161
395
 
162
396
  # No config found
163
- $stderr.puts "[Shakapacker] ERROR: rspack config #{rspack_paths.last} not found, please run 'bundle exec rails shakapacker:install' to install Shakapacker with default configs or create the missing config file."
397
+ print_config_not_found_error("rspack", rspack_paths.last, config_dir)
164
398
  exit(1)
165
399
  end
166
400
 
167
401
  def find_webpack_config
402
+ config_dir = @config.assets_bundler_config_path
403
+
168
404
  possible_paths = %w[ts js].map do |ext|
169
- File.join(@app_path, "config/webpack/webpack.config.#{ext}")
405
+ File.join(@app_path, config_dir, "webpack.config.#{ext}")
170
406
  end
171
407
  puts "[Shakapacker] Looking for Webpack config in: #{possible_paths.join(", ")}"
172
408
  path = possible_paths.find { |f| File.exist?(f) }
173
409
  unless path
174
- $stderr.puts "[Shakapacker] ERROR: webpack config #{possible_paths.last} not found, please run 'bundle exec rails shakapacker:install' to install Shakapacker with default configs or add the missing config file for your custom environment."
410
+ print_config_not_found_error("webpack", possible_paths.last, config_dir)
175
411
  exit(1)
176
412
  end
177
413
  puts "[Shakapacker] Found Webpack config: #{path}"
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.2.0".freeze
3
+ VERSION = "9.3.0.beta.0".freeze
4
4
  end
@@ -1,8 +1,48 @@
1
1
  require "shakapacker/doctor"
2
2
 
3
3
  namespace :shakapacker do
4
- desc "Checks for common Shakapacker configuration issues and missing dependencies"
4
+ desc <<~DESC
5
+ Checks for common Shakapacker configuration issues and missing dependencies
6
+
7
+ Performs comprehensive diagnostics including:
8
+ • Configuration file validity and deprecated settings
9
+ • Entry points, output paths, and asset compilation status
10
+ • Node.js and package manager installation
11
+ • Required and optional npm dependencies
12
+ • JavaScript transpiler (Babel, SWC, esbuild) configuration
13
+ • CSS, CSS Modules, and stylesheet preprocessor setup
14
+ • Binstubs presence (shakapacker, shakapacker-dev-server, export-bundler-config)
15
+ • Version consistency between gem and npm package
16
+ • Legacy Webpacker file detection
17
+
18
+ Options:
19
+ --help Show detailed help and usage information
20
+ --verbose Display additional diagnostic details (paths, versions, environment)
21
+
22
+ Examples:
23
+ bin/rails shakapacker:doctor
24
+ bin/rails shakapacker:doctor -- --verbose
25
+ bin/rails shakapacker:doctor -- --help
26
+
27
+ Exit codes:
28
+ 0 - No issues found
29
+ 1 - Issues or warnings detected (see output for details)
30
+ DESC
5
31
  task doctor: :environment do
6
- Shakapacker::Doctor.new.run
32
+ # Parse command-line options
33
+ options = {}
34
+ ARGV.each do |arg|
35
+ case arg
36
+ when "--help", "-h"
37
+ options[:help] = true
38
+ when "--verbose", "-v"
39
+ options[:verbose] = true
40
+ end
41
+ end
42
+
43
+ Shakapacker::Doctor.new(nil, nil, options).run
44
+
45
+ # Prevent rake from treating options as task names
46
+ ARGV.each { |arg| task arg.to_sym do; end if arg.start_with?("--", "-") }
7
47
  end
8
48
  end
@@ -17,7 +17,10 @@ const coreJsVersion = (): string => {
17
17
  }
18
18
  }
19
19
 
20
- export = function config(api: ConfigAPI): { presets: PluginItem[]; plugins: PluginItem[] } {
20
+ export = function config(api: ConfigAPI): {
21
+ presets: PluginItem[]
22
+ plugins: PluginItem[]
23
+ } {
21
24
  const validEnv = ["development", "test", "production"]
22
25
  const currentEnv = api.env()
23
26
  const isDevelopmentEnv = api.env("development")
@@ -45,9 +48,9 @@ export = function config(api: ConfigAPI): { presets: PluginItem[]; plugins: Plug
45
48
  moduleExists("@babel/preset-typescript") && "@babel/preset-typescript"
46
49
  ].filter(Boolean) as PluginItem[]
47
50
 
48
- const plugins: PluginItem[] = [["@babel/plugin-transform-runtime", { helpers: false }]].filter(
49
- Boolean
50
- ) as PluginItem[]
51
+ const plugins: PluginItem[] = [
52
+ ["@babel/plugin-transform-runtime", { helpers: false }]
53
+ ].filter(Boolean) as PluginItem[]
51
54
 
52
55
  return {
53
56
  presets,
data/package/config.ts CHANGED
@@ -7,17 +7,25 @@ const { railsEnv } = require("./env")
7
7
  const configPath = require("./utils/configPath")
8
8
  const defaultConfigPath = require("./utils/defaultConfigPath")
9
9
  import { Config, YamlConfig, LegacyConfig } from "./types"
10
- const { isValidYamlConfig, createConfigValidationError, isPartialConfig, isValidConfig } = require("./utils/typeGuards")
11
- const { isFileNotFoundError, createFileOperationError } = require("./utils/errorHelpers")
10
+ const {
11
+ isValidYamlConfig,
12
+ createConfigValidationError,
13
+ isPartialConfig,
14
+ isValidConfig
15
+ } = require("./utils/typeGuards")
16
+ const {
17
+ isFileNotFoundError,
18
+ createFileOperationError
19
+ } = require("./utils/errorHelpers")
12
20
 
13
21
  const loadAndValidateYaml = (path: string): YamlConfig => {
14
22
  const fileContent = readFileSync(path, "utf8")
15
23
  const yamlContent = load(fileContent)
16
-
24
+
17
25
  if (!isValidYamlConfig(yamlContent)) {
18
26
  throw createConfigValidationError(path, railsEnv, "Invalid YAML structure")
19
27
  }
20
-
28
+
21
29
  return yamlContent as YamlConfig
22
30
  }
23
31
 
@@ -28,8 +36,8 @@ const getDefaultConfig = (): Partial<Config> => {
28
36
  } catch (error) {
29
37
  if (isFileNotFoundError(error)) {
30
38
  throw createFileOperationError(
31
- 'read',
32
- defaultConfigPath,
39
+ "read",
40
+ defaultConfigPath,
33
41
  `Default configuration not found at ${defaultConfigPath}. Please ensure Shakapacker is properly installed. You may need to run 'yarn add shakapacker' or 'npm install shakapacker'.`
34
42
  )
35
43
  }
@@ -43,34 +51,34 @@ let config: Config
43
51
  if (existsSync(configPath)) {
44
52
  try {
45
53
  const appYmlObject = loadAndValidateYaml(configPath)
46
-
54
+
47
55
  const envAppConfig = appYmlObject[railsEnv]
48
56
 
49
57
  if (!envAppConfig) {
50
58
  /* eslint no-console:0 */
51
59
  console.warn(
52
60
  `[SHAKAPACKER WARNING] Environment '${railsEnv}' not found in ${configPath}\n` +
53
- `Available environments: ${Object.keys(appYmlObject).join(', ')}\n` +
54
- `Using 'production' configuration as fallback.\n\n` +
55
- `To fix this, either:\n` +
56
- ` - Add a '${railsEnv}' section to your shakapacker.yml\n` +
57
- ` - Set RAILS_ENV to one of the available environments\n` +
58
- ` - Copy settings from another environment as a starting point`
61
+ `Available environments: ${Object.keys(appYmlObject).join(", ")}\n` +
62
+ `Using 'production' configuration as fallback.\n\n` +
63
+ `To fix this, either:\n` +
64
+ ` - Add a '${railsEnv}' section to your shakapacker.yml\n` +
65
+ ` - Set RAILS_ENV to one of the available environments\n` +
66
+ ` - Copy settings from another environment as a starting point`
59
67
  )
60
68
  }
61
69
 
62
70
  // Merge returns the merged type
63
71
  const mergedConfig = merge(defaults, envAppConfig || {})
64
-
72
+
65
73
  // Validate merged config before type assertion
66
74
  if (!isPartialConfig(mergedConfig)) {
67
75
  throw createConfigValidationError(
68
- configPath,
69
- railsEnv,
76
+ configPath,
77
+ railsEnv,
70
78
  `Invalid configuration structure in ${configPath}. Please check your shakapacker.yml syntax and ensure all required fields are properly defined.`
71
79
  )
72
80
  }
73
-
81
+
74
82
  // After merging with defaults, config should be complete
75
83
  // Use type assertion only after validation
76
84
  config = mergedConfig as Config
@@ -79,8 +87,8 @@ if (existsSync(configPath)) {
79
87
  // File not found is OK, use defaults
80
88
  if (!isPartialConfig(defaults)) {
81
89
  throw createConfigValidationError(
82
- defaultConfigPath,
83
- railsEnv,
90
+ defaultConfigPath,
91
+ railsEnv,
84
92
  `Invalid default configuration. This may indicate a corrupted Shakapacker installation. Try reinstalling with 'yarn add shakapacker --force'.`
85
93
  )
86
94
  }
@@ -94,8 +102,8 @@ if (existsSync(configPath)) {
94
102
  // No user config, use defaults
95
103
  if (!isPartialConfig(defaults)) {
96
104
  throw createConfigValidationError(
97
- defaultConfigPath,
98
- railsEnv,
105
+ defaultConfigPath,
106
+ railsEnv,
99
107
  `Invalid default configuration. This may indicate a corrupted Shakapacker installation. Try reinstalling with 'yarn add shakapacker --force'.`
100
108
  )
101
109
  }
@@ -122,7 +130,9 @@ if (config.manifest_path) {
122
130
  }
123
131
  // Ensure no duplicate hash functions exist in the returned config object
124
132
  if (config.integrity?.hash_functions) {
125
- config.integrity.hash_functions = [...new Set(config.integrity.hash_functions)]
133
+ config.integrity.hash_functions = [
134
+ ...new Set(config.integrity.hash_functions)
135
+ ]
126
136
  }
127
137
 
128
138
  // Ensure assets_bundler has a default value
@@ -130,7 +140,7 @@ if (!config.assets_bundler) {
130
140
  config.assets_bundler = "webpack"
131
141
  }
132
142
 
133
- // Allow ENV variable to override assets_bundler
143
+ // Allow ENV variable to override assets_bundler
134
144
  if (process.env.SHAKAPACKER_ASSETS_BUNDLER) {
135
145
  config.assets_bundler = process.env.SHAKAPACKER_ASSETS_BUNDLER
136
146
  }
@@ -138,16 +148,18 @@ if (process.env.SHAKAPACKER_ASSETS_BUNDLER) {
138
148
  // Define clear defaults
139
149
  // Keep Babel as default for webpack to maintain backward compatibility
140
150
  // Use SWC for rspack as it's a newer bundler where we can set modern defaults
141
- const DEFAULT_JAVASCRIPT_TRANSPILER =
151
+ const DEFAULT_JAVASCRIPT_TRANSPILER =
142
152
  config.assets_bundler === "rspack" ? "swc" : "babel"
143
153
 
144
154
  // Backward compatibility: Check for webpack_loader using proper type guard
145
- function hasWebpackLoader(obj: unknown): obj is Config & { webpack_loader: string } {
155
+ function hasWebpackLoader(
156
+ obj: unknown
157
+ ): obj is Config & { webpack_loader: string } {
146
158
  return (
147
- typeof obj === 'object' &&
159
+ typeof obj === "object" &&
148
160
  obj !== null &&
149
- 'webpack_loader' in obj &&
150
- typeof (obj as Record<string, unknown>).webpack_loader === 'string'
161
+ "webpack_loader" in obj &&
162
+ typeof (obj as Record<string, unknown>).webpack_loader === "string"
151
163
  )
152
164
  }
153
165
 
@@ -157,7 +169,7 @@ if (process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER) {
157
169
  } else if (hasWebpackLoader(config) && !config.javascript_transpiler) {
158
170
  console.warn(
159
171
  "[SHAKAPACKER DEPRECATION] The 'webpack_loader' configuration option is deprecated.\n" +
160
- "Please use 'javascript_transpiler' instead as it better reflects its purpose of configuring JavaScript transpilation regardless of the bundler used."
172
+ "Please use 'javascript_transpiler' instead as it better reflects its purpose of configuring JavaScript transpilation regardless of the bundler used."
161
173
  )
162
174
  config.javascript_transpiler = config.webpack_loader
163
175
  } else if (!config.javascript_transpiler) {
@@ -165,7 +177,7 @@ if (process.env.SHAKAPACKER_JAVASCRIPT_TRANSPILER) {
165
177
  }
166
178
 
167
179
  // Ensure webpack_loader is always available for backward compatibility
168
- Object.defineProperty(config, 'webpack_loader', {
180
+ Object.defineProperty(config, "webpack_loader", {
169
181
  value: config.javascript_transpiler,
170
182
  writable: true,
171
183
  enumerable: true,