shakapacker 9.3.0.beta.6 → 9.3.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 (62) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +46 -105
  3. data/ESLINT_TECHNICAL_DEBT.md +8 -2
  4. data/Gemfile.lock +1 -1
  5. data/README.md +53 -2
  6. data/docs/configuration.md +28 -0
  7. data/docs/rspack_migration_guide.md +238 -2
  8. data/docs/troubleshooting.md +21 -21
  9. data/eslint.config.fast.js +8 -0
  10. data/eslint.config.js +47 -10
  11. data/knip.ts +8 -1
  12. data/lib/install/config/shakapacker.yml +6 -6
  13. data/lib/shakapacker/configuration.rb +227 -4
  14. data/lib/shakapacker/dev_server.rb +88 -1
  15. data/lib/shakapacker/doctor.rb +129 -72
  16. data/lib/shakapacker/instance.rb +85 -1
  17. data/lib/shakapacker/manifest.rb +85 -11
  18. data/lib/shakapacker/runner.rb +12 -8
  19. data/lib/shakapacker/swc_migrator.rb +7 -7
  20. data/lib/shakapacker/version.rb +1 -1
  21. data/lib/shakapacker.rb +143 -3
  22. data/lib/tasks/shakapacker/doctor.rake +1 -1
  23. data/lib/tasks/shakapacker/export_bundler_config.rake +4 -4
  24. data/package/config.ts +0 -1
  25. data/package/configExporter/buildValidator.ts +53 -29
  26. data/package/configExporter/cli.ts +152 -118
  27. data/package/configExporter/configFile.ts +33 -26
  28. data/package/configExporter/fileWriter.ts +3 -3
  29. data/package/configExporter/types.ts +64 -0
  30. data/package/configExporter/yamlSerializer.ts +147 -36
  31. data/package/dev_server.ts +2 -1
  32. data/package/env.ts +1 -1
  33. data/package/environments/base.ts +4 -4
  34. data/package/environments/development.ts +7 -6
  35. data/package/environments/production.ts +6 -7
  36. data/package/environments/test.ts +2 -1
  37. data/package/index.ts +28 -4
  38. data/package/loaders.d.ts +2 -2
  39. data/package/optimization/webpack.ts +29 -31
  40. data/package/plugins/webpack.ts +2 -1
  41. data/package/rspack/index.ts +2 -1
  42. data/package/rules/file.ts +1 -0
  43. data/package/rules/jscommon.ts +1 -0
  44. data/package/utils/helpers.ts +0 -1
  45. data/package/utils/pathValidation.ts +68 -7
  46. data/package/utils/requireOrError.ts +10 -2
  47. data/package/utils/typeGuards.ts +43 -46
  48. data/package/webpack-types.d.ts +2 -2
  49. data/package/webpackDevServerConfig.ts +1 -0
  50. data/package.json +2 -3
  51. data/test/configExporter/integration.test.js +8 -8
  52. data/test/package/configExporter/cli.test.js +440 -0
  53. data/test/package/configExporter/types.test.js +163 -0
  54. data/test/package/configExporter.test.js +271 -7
  55. data/test/package/yamlSerializer.test.js +204 -0
  56. data/test/typescript/pathValidation.test.js +44 -0
  57. data/test/typescript/requireOrError.test.js +49 -0
  58. data/yarn.lock +0 -32
  59. metadata +11 -6
  60. data/.eslintrc.fast.js +0 -40
  61. data/.eslintrc.js +0 -84
  62. data/package-lock.json +0 -13047
@@ -391,7 +391,7 @@ module Shakapacker
391
391
  expected_binstubs = {
392
392
  "bin/shakapacker" => "Main Shakapacker binstub",
393
393
  "bin/shakapacker-dev-server" => "Development server binstub",
394
- "bin/export-bundler-config" => "Config export binstub"
394
+ "bin/shakapacker-config" => "Config export binstub"
395
395
  }
396
396
 
397
397
  expected_binstubs.each do |path, description|
@@ -879,49 +879,81 @@ module Shakapacker
879
879
 
880
880
  def print_verbose_checks
881
881
  puts "\nVerbose diagnostics:"
882
+ print_environment_info
883
+ print_version_info
884
+ print_path_info
885
+ print_config_values
886
+ end
882
887
 
883
- # Show environment info
888
+ def print_environment_info
884
889
  rails_env = defined?(Rails) ? Rails.env : ENV["RAILS_ENV"]
885
890
  node_env = ENV["NODE_ENV"]
886
891
  puts " • Rails environment: #{rails_env || 'not set'}"
887
892
  puts " • Node environment: #{node_env || 'not set'}"
893
+ end
888
894
 
889
- # Show gem/npm versions
890
- if doctor.send(:package_json_exists?)
891
- package_json = doctor.send(:read_package_json)
892
- npm_version = package_json.dig("dependencies", "shakapacker") ||
893
- package_json.dig("devDependencies", "shakapacker")
894
- puts " • Shakapacker gem version: #{Shakapacker::VERSION}"
895
- puts " • Shakapacker npm version: #{npm_version || 'not installed'}"
896
- end
895
+ def print_version_info
896
+ return unless doctor.send(:package_json_exists?)
897
897
 
898
- # Show paths
898
+ package_json = doctor.send(:read_package_json)
899
+ npm_version = package_json.dig("dependencies", "shakapacker") ||
900
+ package_json.dig("devDependencies", "shakapacker")
901
+ puts " • Shakapacker gem version: #{Shakapacker::VERSION}"
902
+ puts " • Shakapacker npm version: #{npm_version || 'not installed'}"
903
+ end
904
+
905
+ def print_path_info
899
906
  puts " • Root path: #{doctor.root_path}"
900
- if doctor.config.config_path.exist?
901
- puts " • Cache path: #{doctor.config.cache_path}"
902
- puts " • Manifest path: #{doctor.config.manifest_path}"
907
+ return unless doctor.config.config_path.exist?
908
+
909
+ puts " • Cache path: #{doctor.config.cache_path}"
910
+ puts " • Manifest path: #{doctor.config.manifest_path}"
911
+ end
912
+
913
+ def print_config_values
914
+ return unless doctor.config.config_path.exist?
915
+
916
+ puts "\nConfiguration values for '#{doctor.config.env}' environment:"
917
+ config_data = doctor.config.send(:data)
918
+
919
+ if config_data.any?
920
+ print_config_data(config_data)
921
+ else
922
+ puts " (using bundled defaults - no environment-specific config found)"
903
923
  end
924
+ end
904
925
 
905
- # Show environment-specific shakapacker.yml configuration values
906
- if doctor.config.config_path.exist?
907
- puts "\nConfiguration values for '#{doctor.config.env}' environment:"
908
- config_data = doctor.config.send(:data)
909
- if config_data.any?
910
- config_data.each do |key, value|
911
- # Format the value nicely - truncate long arrays/hashes
912
- formatted_value = case value
913
- when Array
914
- value.length > 3 ? "#{value.first(3).inspect}... (#{value.length} items)" : value.inspect
915
- when Hash
916
- value.length > 3 ? "{...} (#{value.length} keys)" : value.inspect
917
- else
918
- value.inspect
919
- end
920
- puts " • #{key}: #{formatted_value}"
921
- end
922
- else
923
- puts " (using bundled defaults - no environment-specific config found)"
924
- end
926
+ def print_config_data(config_data)
927
+ config_data.each do |key, value|
928
+ formatted_value = format_config_value(value)
929
+ puts " • #{key}: #{formatted_value}"
930
+ end
931
+ end
932
+
933
+ def format_config_value(value)
934
+ case value
935
+ when Array
936
+ format_array_value(value)
937
+ when Hash
938
+ format_hash_value(value)
939
+ else
940
+ value.inspect
941
+ end
942
+ end
943
+
944
+ def format_array_value(array)
945
+ if array.length > 3
946
+ "#{array.first(3).inspect}... (#{array.length} items)"
947
+ else
948
+ array.inspect
949
+ end
950
+ end
951
+
952
+ def format_hash_value(hash)
953
+ if hash.length > 3
954
+ "{...} (#{hash.length} keys)"
955
+ else
956
+ hash.inspect
925
957
  end
926
958
  end
927
959
 
@@ -981,7 +1013,7 @@ module Shakapacker
981
1013
  binstubs = [
982
1014
  "bin/shakapacker",
983
1015
  "bin/shakapacker-dev-server",
984
- "bin/export-bundler-config"
1016
+ "bin/shakapacker-config"
985
1017
  ]
986
1018
 
987
1019
  existing_binstubs = binstubs.select { |b| doctor.root_path.join(b).exist? }
@@ -1033,49 +1065,61 @@ module Shakapacker
1033
1065
 
1034
1066
  item_number = 0
1035
1067
  doctor.warnings.each do |warning|
1036
- category_prefix = case warning[:category]
1037
- when :action_required then "[REQUIRED]"
1038
- when :info then "[INFO]"
1039
- when :recommended then "[RECOMMENDED]"
1040
- else ""
1041
- end
1042
-
1043
- # Sub-items start with whitespace (indented fix instructions)
1044
- is_subitem = warning[:message].start_with?(" ")
1045
-
1046
- if is_subitem
1047
- # Fix instructions align at column 16 (length of "N. [RECOMMENDED] ")
1048
- # This ensures all Fix lines align vertically regardless of category
1049
- subitem_prefix = " " * 15
1050
- wrapped = wrap_text(warning[:message], 100, subitem_prefix)
1051
- puts wrapped
1068
+ if subitem?(warning[:message])
1069
+ print_subitem(warning[:message])
1052
1070
  else
1053
1071
  item_number += 1
1054
- # Format: N. [CATEGORY] Message
1055
- prefix = "#{item_number}. #{category_prefix} "
1056
- wrapped = wrap_text(warning[:message], 100, prefix)
1057
- puts wrapped
1072
+ print_main_item(item_number, warning)
1058
1073
  end
1059
1074
  end
1060
1075
  puts ""
1061
1076
  end
1062
1077
 
1078
+ def subitem?(message)
1079
+ message.start_with?(" ")
1080
+ end
1081
+
1082
+ def print_subitem(message)
1083
+ # Fix instructions align at column 16 (length of "N. [RECOMMENDED] ")
1084
+ # This ensures all Fix lines align vertically regardless of category
1085
+ subitem_prefix = " " * 15
1086
+ wrapped = wrap_text(message, 100, subitem_prefix)
1087
+ puts wrapped
1088
+ end
1089
+
1090
+ def print_main_item(item_number, warning)
1091
+ category_prefix = case warning[:category]
1092
+ when :action_required then "[REQUIRED]"
1093
+ when :info then "[INFO]"
1094
+ when :recommended then "[RECOMMENDED]"
1095
+ else
1096
+ ""
1097
+ end
1098
+
1099
+ # Format: N. [CATEGORY] Message
1100
+ prefix = "#{item_number}. #{category_prefix} "
1101
+ wrapped = wrap_text(warning[:message], 100, prefix)
1102
+ puts wrapped
1103
+ end
1104
+
1063
1105
  def wrap_text(text, max_width, prefix)
1064
- # Strip leading whitespace from sub-items
1065
1106
  text = text.strip
1066
-
1067
- # Calculate available width for text
1068
1107
  available_width = max_width - prefix.length
1108
+
1069
1109
  return prefix + text if text.length <= available_width
1070
1110
 
1071
- # Wrap long lines
1111
+ lines = build_wrapped_lines(text, available_width)
1112
+ format_wrapped_output(lines, prefix)
1113
+ end
1114
+
1115
+ def build_wrapped_lines(text, available_width)
1072
1116
  words = text.split(" ")
1073
1117
  lines = []
1074
1118
  current_line = []
1075
1119
  current_length = 0
1076
1120
 
1077
1121
  words.each do |word|
1078
- word_length = word.length + (current_line.empty? ? 0 : 1) # +1 for space
1122
+ word_length = word.length + (current_line.empty? ? 0 : 1)
1079
1123
 
1080
1124
  if current_length + word_length <= available_width
1081
1125
  current_line << word
@@ -1086,26 +1130,39 @@ module Shakapacker
1086
1130
  current_length = word.length
1087
1131
  end
1088
1132
  end
1133
+
1089
1134
  lines << current_line.join(" ") unless current_line.empty?
1135
+ lines
1136
+ end
1090
1137
 
1091
- # Format output
1138
+ def format_wrapped_output(lines, prefix)
1092
1139
  result = prefix + lines[0]
1140
+ indent = " " * prefix.length
1141
+
1093
1142
  lines[1..].each do |line|
1094
- result += "\n" + (" " * prefix.length) + line
1143
+ result += "\n#{indent}#{line}"
1095
1144
  end
1145
+
1096
1146
  result
1097
1147
  end
1098
1148
 
1099
1149
  def has_dependency_issues?
1100
- # Check if any issues or warnings are about missing npm/package dependencies
1101
- # Exclude optional dependencies - only show install instructions for required dependencies
1102
1150
  all_messages = doctor.issues + doctor.warnings.map { |w| w[:message] }
1103
- all_messages.any? do |msg|
1104
- next if msg.include?("Optional")
1105
- (msg.include?("Missing") && msg.include?("dependency")) ||
1106
- msg.include?("not installed") ||
1107
- msg.include?("is not installed")
1108
- end
1151
+ all_messages.any? { |msg| dependency_issue?(msg) }
1152
+ end
1153
+
1154
+ def dependency_issue?(message)
1155
+ return false if message.include?("Optional")
1156
+
1157
+ missing_dependency?(message) || not_installed?(message)
1158
+ end
1159
+
1160
+ def missing_dependency?(message)
1161
+ message.include?("Missing") && message.include?("dependency")
1162
+ end
1163
+
1164
+ def not_installed?(message)
1165
+ message.include?("not installed") || message.include?("is not installed")
1109
1166
  end
1110
1167
 
1111
1168
  def print_fix_instructions
@@ -1114,10 +1171,10 @@ module Shakapacker
1114
1171
  puts " #{package_manager_install_command(package_manager)}"
1115
1172
  puts ""
1116
1173
  puts "For debugging configuration issues, export your webpack/rspack config:"
1117
- puts " bin/export-bundler-config --doctor"
1174
+ puts " bin/shakapacker-config --doctor"
1118
1175
  puts " (Exports annotated YAML configs for dev and production - best for troubleshooting)"
1119
1176
  puts ""
1120
- puts " See 'bin/export-bundler-config --help' for more options"
1177
+ puts " See 'bin/shakapacker-config --help' for more options"
1121
1178
  end
1122
1179
 
1123
1180
  def package_manager_install_command(manager)
@@ -1,10 +1,43 @@
1
1
  require "pathname"
2
2
 
3
+ # Represents a single instance of Shakapacker configuration and state
4
+ #
5
+ # An instance encapsulates all the configuration, compilation, and manifest
6
+ # lookup functionality for a specific Rails application. Most applications
7
+ # will use the shared instance accessible via {Shakapacker.instance}, but
8
+ # multiple instances can be created for testing or advanced scenarios.
9
+ #
10
+ # @example Using the default instance
11
+ # instance = Shakapacker::Instance.new
12
+ # instance.config.source_path
13
+ # instance.manifest.lookup("application.js")
14
+ #
15
+ # @example Creating an instance with custom paths
16
+ # instance = Shakapacker::Instance.new(
17
+ # root_path: "/path/to/app",
18
+ # config_path: "/custom/config.yml"
19
+ # )
3
20
  class Shakapacker::Instance
21
+ # The shared logger used by all Shakapacker instances
22
+ # @return [ActiveSupport::TaggedLogging] the logger
4
23
  cattr_accessor(:logger) { ActiveSupport::TaggedLogging.new(ActiveSupport::Logger.new(STDOUT)) }
5
24
 
6
- attr_reader :root_path, :config_path
25
+ # The root path of the application
26
+ # @return [Pathname] the application root path
27
+ attr_reader :root_path
7
28
 
29
+ # The path to the Shakapacker configuration file
30
+ # @return [Pathname] the config file path
31
+ attr_reader :config_path
32
+
33
+ # Creates a new Shakapacker instance
34
+ #
35
+ # @param root_path [String, Pathname, nil] the application root path.
36
+ # Defaults to Rails.root if Rails is defined, otherwise uses current directory
37
+ # @param config_path [String, Pathname, nil] the path to shakapacker.yml.
38
+ # Can also be set via SHAKAPACKER_CONFIG environment variable.
39
+ # Defaults to +config/shakapacker.yml+ within the root_path
40
+ # @return [Shakapacker::Instance] the new instance
8
41
  def initialize(root_path: nil, config_path: nil)
9
42
  # Use Rails.root if Rails is defined and no root_path is provided
10
43
  @root_path = root_path || (defined?(Rails) && Rails&.root) || Pathname.new(Dir.pwd)
@@ -15,10 +48,23 @@ class Shakapacker::Instance
15
48
  @config_path = Pathname.new(ENV["SHAKAPACKER_CONFIG"] || config_path || default_config_path)
16
49
  end
17
50
 
51
+ # Returns the current Rails environment as a StringInquirer
52
+ #
53
+ # This allows for convenient environment checking:
54
+ # env.development? # => true/false
55
+ # env.production? # => true/false
56
+ #
57
+ # @return [ActiveSupport::StringInquirer] the environment
18
58
  def env
19
59
  @env ||= Shakapacker::Env.inquire self
20
60
  end
21
61
 
62
+ # Returns the configuration object for this instance
63
+ #
64
+ # The configuration is loaded from the shakapacker.yml file and provides
65
+ # access to all settings like source paths, output paths, and compilation options.
66
+ #
67
+ # @return [Shakapacker::Configuration] the configuration
22
68
  def config
23
69
  @config ||= Shakapacker::Configuration.new(
24
70
  root_path: root_path,
@@ -27,26 +73,64 @@ class Shakapacker::Instance
27
73
  )
28
74
  end
29
75
 
76
+ # Returns the compiler strategy for determining staleness
77
+ #
78
+ # The strategy (mtime or digest) determines how Shakapacker decides whether
79
+ # assets need recompilation.
80
+ #
81
+ # @return [Shakapacker::CompilerStrategy] the compiler strategy
82
+ # @api private
30
83
  def strategy
31
84
  @strategy ||= Shakapacker::CompilerStrategy.from_config
32
85
  end
33
86
 
87
+ # Returns the compiler for this instance
88
+ #
89
+ # The compiler is responsible for executing webpack/rspack to compile assets.
90
+ #
91
+ # @return [Shakapacker::Compiler] the compiler
34
92
  def compiler
35
93
  @compiler ||= Shakapacker::Compiler.new self
36
94
  end
37
95
 
96
+ # Returns the development server instance
97
+ #
98
+ # The dev server instance can query the status of the webpack-dev-server,
99
+ # including whether it's running, its host/port, and configuration.
100
+ #
101
+ # @return [Shakapacker::DevServer] the dev server
38
102
  def dev_server
39
103
  @dev_server ||= Shakapacker::DevServer.new config
40
104
  end
41
105
 
106
+ # Returns the manifest for looking up compiled assets
107
+ #
108
+ # The manifest reads the manifest.json file produced by webpack/rspack
109
+ # and provides methods to look up the compiled paths for source files.
110
+ #
111
+ # @return [Shakapacker::Manifest] the manifest
42
112
  def manifest
43
113
  @manifest ||= Shakapacker::Manifest.new self
44
114
  end
45
115
 
116
+ # Returns the commands instance for build operations
117
+ #
118
+ # The commands object provides methods for bootstrapping, cleaning,
119
+ # clobbering, and compiling assets.
120
+ #
121
+ # @return [Shakapacker::Commands] the commands
46
122
  def commands
47
123
  @commands ||= Shakapacker::Commands.new self
48
124
  end
49
125
 
126
+ # Returns whether CSS should be inlined by the dev server
127
+ #
128
+ # CSS inlining is enabled when:
129
+ # - The dev server has inline_css enabled
130
+ # - Hot Module Replacement (HMR) is enabled
131
+ # - The dev server is currently running
132
+ #
133
+ # @return [Boolean] true if CSS should be inlined
50
134
  def inlining_css?
51
135
  dev_server.inline_css? && dev_server.hmr? && dev_server.running?
52
136
  end
@@ -1,23 +1,70 @@
1
- # Singleton registry for accessing the packs path using a generated manifest.
2
- # This allows javascript_pack_tag, stylesheet_pack_tag, asset_pack_path to take a reference to,
3
- # say, "calendar.js" or "calendar.css" and turn it into "/packs/calendar-1016838bab065ae1e314.js" or
4
- # "/packs/calendar-1016838bab065ae1e314.css".
1
+ # Manifest for looking up compiled asset paths
5
2
  #
6
- # When the configuration is set to on-demand compilation, with the `compile: true` option in
7
- # the shakapacker.yml file, any lookups will be preceded by a compilation if one is needed.
3
+ # The manifest reads the +manifest.json+ file produced by webpack/rspack during
4
+ # compilation and provides methods to look up the compiled (digested) paths for
5
+ # source files.
6
+ #
7
+ # This allows view helpers like +javascript_pack_tag+, +stylesheet_pack_tag+, and
8
+ # +asset_pack_path+ to take a reference to a source file (e.g., "calendar.js")
9
+ # and turn it into the compiled path with digest (e.g., "/packs/calendar-1016838bab065ae1e314.js").
10
+ #
11
+ # == Automatic Compilation
12
+ #
13
+ # When the configuration has +compile: true+ in shakapacker.yml, any lookups will
14
+ # automatically trigger compilation if the assets are stale. This is typically
15
+ # enabled in development and disabled in production.
16
+ #
17
+ # == Caching
18
+ #
19
+ # The manifest can cache the loaded data in memory when +cache_manifest: true+ is
20
+ # set in the configuration. This improves performance in production by avoiding
21
+ # repeated file reads.
22
+ #
23
+ # @example Looking up assets
24
+ # manifest = Shakapacker.manifest
25
+ # manifest.lookup("application.js")
26
+ # #=> "/packs/application-abc123.js"
27
+ #
28
+ # manifest.lookup!("missing.js")
29
+ # #=> raises Shakapacker::Manifest::MissingEntryError
30
+ #
31
+ # @see Shakapacker::Helper
8
32
  class Shakapacker::Manifest
33
+ # Raised when an asset cannot be found in the manifest
9
34
  class MissingEntryError < StandardError; end
10
35
 
11
36
  delegate :config, :compiler, :dev_server, to: :@instance
12
37
 
38
+ # Creates a new manifest instance
39
+ #
40
+ # @param instance [Shakapacker::Instance] the Shakapacker instance
41
+ # @return [Shakapacker::Manifest] the new manifest
13
42
  def initialize(instance)
14
43
  @instance = instance
15
44
  end
16
45
 
46
+ # Reloads the manifest data from disk
47
+ #
48
+ # Forces a fresh read of the manifest.json file, bypassing any cache.
49
+ # This is useful when you know the manifest has been updated.
50
+ #
51
+ # @return [Hash] the loaded manifest data
17
52
  def refresh
18
53
  @data = load
19
54
  end
20
55
 
56
+ # Looks up an entry point with all its chunks (split code)
57
+ #
58
+ # This method is used when you need to load all chunks for a pack that has
59
+ # been split via code splitting. It returns an array of asset paths for the
60
+ # main entry and all its dynamic imports.
61
+ #
62
+ # @param name [String] the entry point name (e.g., "application")
63
+ # @param pack_type [Hash] options hash with :type key (:javascript, :stylesheet, etc.)
64
+ # @return [Array<String>, nil] array of asset paths, or nil if not found
65
+ # @example
66
+ # manifest.lookup_pack_with_chunks("application", type: :javascript)
67
+ # #=> ["/packs/runtime-abc123.js", "/packs/application-def456.js"]
21
68
  def lookup_pack_with_chunks(name, pack_type = {})
22
69
  compile if compiling?
23
70
 
@@ -28,23 +75,50 @@ class Shakapacker::Manifest
28
75
  nil
29
76
  end
30
77
 
78
+ # Like {#lookup_pack_with_chunks}, but raises an error if not found
79
+ #
80
+ # @param name [String] the entry point name
81
+ # @param pack_type [Hash] options hash with :type key
82
+ # @return [Array<String>] array of asset paths
83
+ # @raise [MissingEntryError] if the entry point is not found in the manifest
31
84
  def lookup_pack_with_chunks!(name, pack_type = {})
32
85
  lookup_pack_with_chunks(name, pack_type) || handle_missing_entry(name, pack_type)
33
86
  end
34
87
 
35
- # Computes the relative path for a given Shakapacker asset using manifest.json.
36
- # If no asset is found, returns nil.
88
+ # Looks up the compiled path for a given asset
89
+ #
90
+ # Computes the relative path for a Shakapacker asset using the manifest.json file.
91
+ # If automatic compilation is enabled and the assets are stale, triggers a
92
+ # compilation before looking up the path.
37
93
  #
38
- # Example:
94
+ # @param name [String] the source file name (e.g., "calendar.js" or "calendar")
95
+ # @param pack_type [Hash] options hash with :type key (:javascript, :stylesheet, etc.).
96
+ # If not specified, the extension from the name is used.
97
+ # @return [String, nil] the compiled asset path, or nil if not found
98
+ # @example
99
+ # Shakapacker.manifest.lookup('calendar.js')
100
+ # #=> "/packs/calendar-1016838bab065ae1e122.js"
39
101
  #
40
- # Shakapacker.manifest.lookup('calendar.js') # => "/packs/calendar-1016838bab065ae1e122.js"
102
+ # Shakapacker.manifest.lookup('calendar', type: :javascript)
103
+ # #=> "/packs/calendar-1016838bab065ae1e122.js"
41
104
  def lookup(name, pack_type = {})
42
105
  compile if compiling?
43
106
 
44
107
  find(full_pack_name(name, pack_type[:type]))
45
108
  end
46
109
 
47
- # Like lookup, except that if no asset is found, raises a Shakapacker::Manifest::MissingEntryError.
110
+ # Like {#lookup}, but raises an error if the asset is not found
111
+ #
112
+ # @param name [String] the source file name
113
+ # @param pack_type [Hash] options hash with :type key
114
+ # @return [String] the compiled asset path
115
+ # @raise [MissingEntryError] if the asset is not found in the manifest
116
+ # @example
117
+ # Shakapacker.manifest.lookup!('calendar.js')
118
+ # #=> "/packs/calendar-1016838bab065ae1e122.js"
119
+ #
120
+ # Shakapacker.manifest.lookup!('missing.js')
121
+ # #=> raises MissingEntryError
48
122
  def lookup!(name, pack_type = {})
49
123
  lookup(name, pack_type) || handle_missing_entry(name, pack_type)
50
124
  end
@@ -29,8 +29,10 @@ module Shakapacker
29
29
  $stdout.sync = true
30
30
 
31
31
  # Show Shakapacker help and exit (don't call bundler)
32
- if argv.include?("--help") || argv.include?("-h")
33
- print_help
32
+ # Support --help, -h, and --help=verbose formats
33
+ help_verbose = argv.any? { |arg| arg == "--help=verbose" }
34
+ if argv.include?("--help") || argv.include?("-h") || help_verbose
35
+ print_help(verbose: help_verbose)
34
36
  exit(0)
35
37
  elsif argv.include?("--version") || argv.include?("-v")
36
38
  print_version
@@ -310,7 +312,7 @@ module Shakapacker
310
312
  $stderr.puts "Current configured path: #{config_dir}"
311
313
  end
312
314
 
313
- def self.print_help
315
+ def self.print_help(verbose: false)
314
316
  puts <<~HELP
315
317
  ================================================================================
316
318
  SHAKAPACKER - Rails Webpack/Rspack Integration
@@ -320,6 +322,7 @@ module Shakapacker
320
322
 
321
323
  Shakapacker-specific options:
322
324
  -h, --help Show this help message
325
+ --help=verbose Show verbose help including all bundler options
323
326
  -v, --version Show Shakapacker version
324
327
  --debug-shakapacker Enable Node.js debugging (--inspect-brk)
325
328
  --trace-deprecation Show stack traces for deprecations
@@ -347,7 +350,7 @@ module Shakapacker
347
350
 
348
351
  HELP
349
352
 
350
- print_bundler_help
353
+ print_bundler_help(verbose: verbose)
351
354
 
352
355
  puts <<~HELP
353
356
 
@@ -366,8 +369,9 @@ module Shakapacker
366
369
  HELP
367
370
  end
368
371
 
369
- def self.print_bundler_help
370
- bundler_type, bundler_help = get_bundler_help
372
+ def self.print_bundler_help(verbose: false)
373
+ help_flag = verbose ? "--help=verbose" : "--help"
374
+ bundler_type, bundler_help = get_bundler_help(help_flag)
371
375
 
372
376
  if bundler_help
373
377
  bundler_name = bundler_type == :rspack ? "RSPACK" : "WEBPACK"
@@ -390,8 +394,8 @@ module Shakapacker
390
394
  end
391
395
  end
392
396
 
393
- def self.get_bundler_help
394
- execute_bundler_command("--help") { |stdout| stdout }
397
+ def self.get_bundler_help(help_flag = "--help")
398
+ execute_bundler_command(help_flag) { |stdout| stdout }
395
399
  end
396
400
 
397
401
  # Filter bundler help output to remove Shakapacker-managed options
@@ -61,13 +61,13 @@ module Shakapacker
61
61
  keepClassNames: true,
62
62
  transform: {
63
63
  react: {
64
- runtime: "automatic",
65
- refresh: env.isDevelopment && env.runningWebpackDevServer
66
- }
67
- }
68
- }
69
- }
70
- }
64
+ runtime: 'automatic',
65
+ refresh: env.isDevelopment && env.runningWebpackDevServer,
66
+ },
67
+ },
68
+ },
69
+ },
70
+ };
71
71
  JS
72
72
 
73
73
  def initialize(root_path, logger: nil)
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "9.3.0.beta.6".freeze
3
+ VERSION = "9.3.0".freeze
4
4
  end