shakapacker 8.4.0 → 9.0.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/.eslintignore +1 -0
  3. data/.eslintrc.fast.js +40 -0
  4. data/.eslintrc.js +48 -0
  5. data/.github/STATUS.md +1 -0
  6. data/.github/workflows/claude-code-review.yml +54 -0
  7. data/.github/workflows/claude.yml +50 -0
  8. data/.github/workflows/dummy.yml +8 -4
  9. data/.github/workflows/generator.yml +17 -14
  10. data/.github/workflows/node.yml +23 -1
  11. data/.github/workflows/ruby.yml +11 -0
  12. data/.github/workflows/test-bundlers.yml +170 -0
  13. data/.gitignore +17 -0
  14. data/.husky/pre-commit +2 -0
  15. data/.npmignore +56 -0
  16. data/.prettierignore +3 -0
  17. data/.rubocop.yml +1 -0
  18. data/.yalcignore +26 -0
  19. data/CHANGELOG.md +156 -18
  20. data/CLAUDE.md +29 -0
  21. data/CONTRIBUTING.md +138 -20
  22. data/Gemfile.lock +3 -3
  23. data/README.md +130 -5
  24. data/Rakefile +39 -4
  25. data/TODO.md +50 -0
  26. data/TODO_v9.md +87 -0
  27. data/conductor-setup.sh +70 -0
  28. data/conductor.json +7 -0
  29. data/docs/cdn_setup.md +379 -0
  30. data/docs/css-modules-export-mode.md +512 -0
  31. data/docs/deployment.md +10 -1
  32. data/docs/optional-peer-dependencies.md +198 -0
  33. data/docs/peer-dependencies.md +60 -0
  34. data/docs/rspack.md +190 -0
  35. data/docs/rspack_migration_guide.md +202 -0
  36. data/docs/transpiler-migration.md +188 -0
  37. data/docs/transpiler-performance.md +179 -0
  38. data/docs/troubleshooting.md +5 -0
  39. data/docs/typescript-migration.md +378 -0
  40. data/docs/typescript.md +99 -0
  41. data/docs/using_esbuild_loader.md +3 -3
  42. data/docs/using_swc_loader.md +5 -3
  43. data/docs/v6_upgrade.md +10 -0
  44. data/docs/v9_upgrade.md +413 -0
  45. data/lib/install/bin/shakapacker +3 -5
  46. data/lib/install/config/rspack/rspack.config.js +6 -0
  47. data/lib/install/config/rspack/rspack.config.ts +7 -0
  48. data/lib/install/config/shakapacker.yml +12 -2
  49. data/lib/install/config/webpack/webpack.config.ts +7 -0
  50. data/lib/install/package.json +38 -0
  51. data/lib/install/template.rb +194 -44
  52. data/lib/shakapacker/configuration.rb +141 -0
  53. data/lib/shakapacker/dev_server_runner.rb +25 -5
  54. data/lib/shakapacker/doctor.rb +844 -0
  55. data/lib/shakapacker/manifest.rb +4 -2
  56. data/lib/shakapacker/rspack_runner.rb +19 -0
  57. data/lib/shakapacker/runner.rb +144 -4
  58. data/lib/shakapacker/swc_migrator.rb +376 -0
  59. data/lib/shakapacker/utils/manager.rb +2 -0
  60. data/lib/shakapacker/version.rb +1 -1
  61. data/lib/shakapacker/version_checker.rb +1 -1
  62. data/lib/shakapacker/webpack_runner.rb +4 -42
  63. data/lib/shakapacker.rb +2 -1
  64. data/lib/tasks/shakapacker/doctor.rake +8 -0
  65. data/lib/tasks/shakapacker/install.rake +12 -2
  66. data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
  67. data/lib/tasks/shakapacker.rake +1 -0
  68. data/package/.npmignore +4 -0
  69. data/package/babel/preset.ts +56 -0
  70. data/package/config.ts +175 -0
  71. data/package/{dev_server.js → dev_server.ts} +8 -5
  72. data/package/env.ts +92 -0
  73. data/package/environments/base.ts +138 -0
  74. data/package/environments/development.ts +90 -0
  75. data/package/environments/production.ts +80 -0
  76. data/package/environments/test.ts +53 -0
  77. data/package/environments/types.ts +90 -0
  78. data/package/esbuild/index.ts +42 -0
  79. data/package/index.d.ts +3 -97
  80. data/package/index.ts +52 -0
  81. data/package/loaders.d.ts +28 -0
  82. data/package/optimization/rspack.ts +36 -0
  83. data/package/optimization/webpack.ts +57 -0
  84. data/package/plugins/rspack.ts +103 -0
  85. data/package/plugins/webpack.ts +62 -0
  86. data/package/rspack/index.ts +64 -0
  87. data/package/rules/{babel.js → babel.ts} +2 -2
  88. data/package/rules/{coffee.js → coffee.ts} +1 -1
  89. data/package/rules/css.ts +3 -0
  90. data/package/rules/{erb.js → erb.ts} +1 -1
  91. data/package/rules/esbuild.ts +10 -0
  92. data/package/rules/file.ts +40 -0
  93. data/package/rules/{jscommon.js → jscommon.ts} +4 -4
  94. data/package/rules/{less.js → less.ts} +4 -4
  95. data/package/rules/raw.ts +25 -0
  96. data/package/rules/rspack.ts +176 -0
  97. data/package/rules/{sass.js → sass.ts} +7 -3
  98. data/package/rules/{stylus.js → stylus.ts} +4 -8
  99. data/package/rules/swc.ts +10 -0
  100. data/package/rules/{index.js → webpack.ts} +1 -1
  101. data/package/swc/index.ts +54 -0
  102. data/package/types/README.md +87 -0
  103. data/package/types/index.ts +60 -0
  104. data/package/types.ts +108 -0
  105. data/package/utils/configPath.ts +6 -0
  106. data/package/utils/debug.ts +49 -0
  107. data/package/utils/defaultConfigPath.ts +4 -0
  108. data/package/utils/errorCodes.ts +219 -0
  109. data/package/utils/errorHelpers.ts +143 -0
  110. data/package/utils/getStyleRule.ts +64 -0
  111. data/package/utils/helpers.ts +85 -0
  112. data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
  113. data/package/utils/pathValidation.ts +139 -0
  114. data/package/utils/requireOrError.ts +15 -0
  115. data/package/utils/snakeToCamelCase.ts +5 -0
  116. data/package/utils/typeGuards.ts +342 -0
  117. data/package/utils/validateDependencies.ts +61 -0
  118. data/package/webpack-types.d.ts +33 -0
  119. data/package/webpackDevServerConfig.ts +117 -0
  120. data/package.json +134 -9
  121. data/scripts/remove-use-strict.js +45 -0
  122. data/scripts/type-check-no-emit.js +27 -0
  123. data/test/package/config.test.js +3 -0
  124. data/test/package/env.test.js +42 -7
  125. data/test/package/environments/base.test.js +5 -1
  126. data/test/package/rules/babel.test.js +16 -0
  127. data/test/package/rules/esbuild.test.js +1 -1
  128. data/test/package/rules/raw.test.js +40 -7
  129. data/test/package/rules/swc.test.js +1 -1
  130. data/test/package/rules/webpack.test.js +35 -0
  131. data/test/package/staging.test.js +4 -3
  132. data/test/package/transpiler-defaults.test.js +127 -0
  133. data/test/peer-dependencies.sh +85 -0
  134. data/test/scripts/remove-use-strict.test.js +125 -0
  135. data/test/typescript/build.test.js +118 -0
  136. data/test/typescript/environments.test.js +107 -0
  137. data/test/typescript/pathValidation.test.js +142 -0
  138. data/test/typescript/securityValidation.test.js +182 -0
  139. data/tools/README.md +124 -0
  140. data/tools/css-modules-v9-codemod.js +179 -0
  141. data/tsconfig.eslint.json +16 -0
  142. data/tsconfig.json +38 -0
  143. data/yarn.lock +2704 -767
  144. metadata +111 -41
  145. data/package/babel/preset.js +0 -48
  146. data/package/config.js +0 -56
  147. data/package/env.js +0 -48
  148. data/package/environments/base.js +0 -171
  149. data/package/environments/development.js +0 -13
  150. data/package/environments/production.js +0 -88
  151. data/package/environments/test.js +0 -3
  152. data/package/esbuild/index.js +0 -40
  153. data/package/index.js +0 -40
  154. data/package/rules/css.js +0 -3
  155. data/package/rules/esbuild.js +0 -10
  156. data/package/rules/file.js +0 -29
  157. data/package/rules/raw.js +0 -5
  158. data/package/rules/swc.js +0 -10
  159. data/package/swc/index.js +0 -50
  160. data/package/utils/configPath.js +0 -4
  161. data/package/utils/defaultConfigPath.js +0 -2
  162. data/package/utils/getStyleRule.js +0 -40
  163. data/package/utils/helpers.js +0 -62
  164. data/package/utils/snakeToCamelCase.js +0 -5
  165. data/package/webpackDevServerConfig.js +0 -71
  166. data/test/package/rules/index.test.js +0 -16
@@ -108,15 +108,17 @@ class Shakapacker::Manifest
108
108
  end
109
109
 
110
110
  def missing_file_from_manifest_error(bundle_name)
111
+ bundler_name = config.assets_bundler
111
112
  <<-MSG
112
113
  Shakapacker can't find #{bundle_name} in #{config.manifest_path}. Possible causes:
113
114
  1. You forgot to install javascript packages or are running an incompatible javascript runtime version
114
115
  2. Your app has code with a non-standard extension (like a `.jsx` file) but the extension is not in the `extensions` config in `config/shakapacker.yml`
115
116
  3. You have set compile: false (see `config/shakapacker.yml`) for this environment
116
117
  (unless you are using the `bin/shakapacker -w` or the `bin/shakapacker-dev-server`, in which case maybe you aren't running the dev server in the background?)
117
- 4. webpack has not yet FINISHED running to reflect updates.
118
+ 4. Your #{bundler_name} has not yet FINISHED running to reflect updates.
118
119
  5. You have misconfigured Shakapacker's `config/shakapacker.yml` file.
119
- 6. Your webpack configuration is not creating a manifest.
120
+ 6. Your #{bundler_name} configuration is not creating a manifest with the expected structure.
121
+ 7. Ensure the 'assets_bundler' in config/shakapacker.yml is set correctly (currently: #{bundler_name}).
120
122
 
121
123
  Your manifest contains:
122
124
  #{JSON.pretty_generate(@data)}
@@ -0,0 +1,19 @@
1
+ require "shellwords"
2
+
3
+ require_relative "runner"
4
+
5
+ module Shakapacker
6
+ class RspackRunner < Shakapacker::Runner
7
+ def self.run(argv)
8
+ $stdout.sync = true
9
+ ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
10
+ new(argv).run
11
+ end
12
+
13
+ private
14
+
15
+ def build_cmd
16
+ package_json.manager.native_exec_command("rspack")
17
+ end
18
+ end
19
+ end
@@ -1,14 +1,60 @@
1
1
  require_relative "utils/misc"
2
2
  require_relative "utils/manager"
3
+ require_relative "configuration"
3
4
 
4
5
  require "package_json"
6
+ require "pathname"
5
7
 
6
8
  module Shakapacker
7
9
  class Runner
10
+ attr_reader :config
11
+
12
+ # Common commands that don't work with --config option
13
+ BASE_COMMANDS = [
14
+ "help",
15
+ "h",
16
+ "--help",
17
+ "-h",
18
+ "version",
19
+ "v",
20
+ "--version",
21
+ "-v",
22
+ "info",
23
+ "i"
24
+ ].freeze
8
25
  def self.run(argv)
9
26
  $stdout.sync = true
10
27
  ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
11
- new(argv).run
28
+
29
+ # Create a single runner instance to avoid loading configuration twice.
30
+ # We extend it with the appropriate build command based on the bundler type.
31
+ runner = new(argv)
32
+
33
+ if runner.config.rspack?
34
+ require_relative "rspack_runner"
35
+ # Extend the runner instance with rspack-specific methods
36
+ # This avoids creating a new RspackRunner which would reload the configuration
37
+ runner.extend(Module.new do
38
+ def build_cmd
39
+ package_json.manager.native_exec_command("rspack")
40
+ end
41
+
42
+ def assets_bundler_commands
43
+ BASE_COMMANDS + %w[build watch]
44
+ end
45
+ end)
46
+ runner.run
47
+ else
48
+ require_relative "webpack_runner"
49
+ # Extend the runner instance with webpack-specific methods
50
+ # This avoids creating a new WebpackRunner which would reload the configuration
51
+ runner.extend(Module.new do
52
+ def build_cmd
53
+ package_json.manager.native_exec_command("webpack")
54
+ end
55
+ end)
56
+ runner.run
57
+ end
12
58
  end
13
59
 
14
60
  def initialize(argv)
@@ -16,7 +62,12 @@ module Shakapacker
16
62
 
17
63
  @app_path = File.expand_path(".", Dir.pwd)
18
64
  @shakapacker_config = ENV["SHAKAPACKER_CONFIG"] || File.join(@app_path, "config/shakapacker.yml")
19
- @webpack_config = find_webpack_config
65
+ @config = Configuration.new(
66
+ root_path: Pathname.new(@app_path),
67
+ config_path: Pathname.new(@shakapacker_config),
68
+ env: ENV["RAILS_ENV"] || ENV["NODE_ENV"] || "development"
69
+ )
70
+ @webpack_config = find_assets_bundler_config
20
71
 
21
72
  Shakapacker::Utils::Manager.error_unless_package_manager_is_obvious!
22
73
  end
@@ -25,16 +76,105 @@ module Shakapacker
25
76
  @package_json ||= PackageJson.read(@app_path)
26
77
  end
27
78
 
79
+ def run
80
+ puts "[Shakapacker] Preparing environment for assets bundler execution..."
81
+ env = Shakapacker::Compiler.env
82
+ env["SHAKAPACKER_CONFIG"] = @shakapacker_config
83
+ env["NODE_OPTIONS"] = ENV["NODE_OPTIONS"] || ""
84
+
85
+ cmd = build_cmd
86
+ puts "[Shakapacker] Base command: #{cmd.join(" ")}"
87
+
88
+ if @argv.delete("--debug-shakapacker")
89
+ puts "[Shakapacker] Debug mode enabled (--debug-shakapacker)"
90
+ env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --inspect-brk"
91
+ end
92
+
93
+ if @argv.delete "--trace-deprecation"
94
+ puts "[Shakapacker] Trace deprecation enabled (--trace-deprecation)"
95
+ env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --trace-deprecation"
96
+ end
97
+
98
+ if @argv.delete "--no-deprecation"
99
+ puts "[Shakapacker] Deprecation warnings disabled (--no-deprecation)"
100
+ env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --no-deprecation"
101
+ end
102
+
103
+ # Commands are not compatible with --config option.
104
+ if (@argv & assets_bundler_commands).empty?
105
+ puts "[Shakapacker] Adding config file: #{@webpack_config}"
106
+ cmd += ["--config", @webpack_config]
107
+ else
108
+ puts "[Shakapacker] Skipping config file (running assets bundler command: #{(@argv & assets_bundler_commands).join(", ")})"
109
+ end
110
+
111
+ cmd += @argv
112
+ puts "[Shakapacker] Final command: #{cmd.join(" ")}"
113
+ puts "[Shakapacker] Working directory: #{@app_path}"
114
+
115
+ Dir.chdir(@app_path) do
116
+ Kernel.exec env, *cmd
117
+ end
118
+ end
119
+
120
+ protected
121
+
122
+ def assets_bundler_commands
123
+ BASE_COMMANDS
124
+ end
125
+
28
126
  private
127
+ def find_assets_bundler_config
128
+ if @config.rspack?
129
+ find_rspack_config_with_fallback
130
+ else
131
+ find_webpack_config
132
+ end
133
+ end
134
+
135
+ def find_rspack_config_with_fallback
136
+ # First try rspack-specific paths
137
+ rspack_paths = %w[ts js].map do |ext|
138
+ File.join(@app_path, "config/rspack/rspack.config.#{ext}")
139
+ end
140
+
141
+ puts "[Shakapacker] Looking for Rspack config in: #{rspack_paths.join(", ")}"
142
+ rspack_path = rspack_paths.find { |f| File.exist?(f) }
143
+ if rspack_path
144
+ puts "[Shakapacker] Found Rspack config: #{rspack_path}"
145
+ return rspack_path
146
+ end
147
+
148
+ # Fallback to webpack config with deprecation warning
149
+ webpack_paths = %w[ts js].map do |ext|
150
+ File.join(@app_path, "config/webpack/webpack.config.#{ext}")
151
+ end
152
+
153
+ puts "[Shakapacker] Rspack config not found, checking for webpack config fallback..."
154
+ webpack_path = webpack_paths.find { |f| File.exist?(f) }
155
+ if webpack_path
156
+ $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."
158
+ $stderr.puts " Using: #{webpack_path}"
159
+ return webpack_path
160
+ end
161
+
162
+ # 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."
164
+ exit(1)
165
+ end
166
+
29
167
  def find_webpack_config
30
168
  possible_paths = %w[ts js].map do |ext|
31
169
  File.join(@app_path, "config/webpack/webpack.config.#{ext}")
32
170
  end
171
+ puts "[Shakapacker] Looking for Webpack config in: #{possible_paths.join(", ")}"
33
172
  path = possible_paths.find { |f| File.exist?(f) }
34
173
  unless path
35
- $stderr.puts "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."
36
- exit!
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."
175
+ exit(1)
37
176
  end
177
+ puts "[Shakapacker] Found Webpack config: #{path}"
38
178
  path
39
179
  end
40
180
  end
@@ -0,0 +1,376 @@
1
+ require "yaml"
2
+ require "json"
3
+ require "fileutils"
4
+ require "logger"
5
+ require "pathname"
6
+
7
+ module Shakapacker
8
+ class SwcMigrator
9
+ attr_reader :root_path, :logger
10
+
11
+ # Babel packages safe to remove when migrating to SWC
12
+ # Note: @babel/core and @babel/eslint-parser are excluded as they may be needed for ESLint
13
+ BABEL_PACKAGES = [
14
+ "@babel/plugin-proposal-class-properties",
15
+ "@babel/plugin-proposal-object-rest-spread",
16
+ "@babel/plugin-syntax-dynamic-import",
17
+ "@babel/plugin-transform-destructuring",
18
+ "@babel/plugin-transform-regenerator",
19
+ "@babel/plugin-transform-runtime",
20
+ "@babel/preset-env",
21
+ "@babel/preset-react",
22
+ "@babel/preset-typescript",
23
+ "@babel/runtime",
24
+ "babel-loader",
25
+ "babel-plugin-macros",
26
+ "babel-plugin-transform-react-remove-prop-types"
27
+ ].freeze
28
+
29
+ # Babel packages that may be needed for ESLint - only remove if user explicitly confirms
30
+ ESLINT_BABEL_PACKAGES = [
31
+ "@babel/core",
32
+ "@babel/eslint-parser"
33
+ ].freeze
34
+
35
+ SWC_PACKAGES = {
36
+ "@swc/core" => "^1.7.39",
37
+ "swc-loader" => "^0.2.6"
38
+ }.freeze
39
+
40
+ ESLINT_CONFIG_FILES = %w[
41
+ .eslintrc
42
+ .eslintrc.js
43
+ .eslintrc.cjs
44
+ .eslintrc.yaml
45
+ .eslintrc.yml
46
+ .eslintrc.json
47
+ ].freeze
48
+
49
+ DEFAULT_SWC_CONFIG = <<~JS.freeze
50
+ // config/swc.config.js
51
+ // This file is merged with Shakapacker's default SWC configuration
52
+ // See: https://swc.rs/docs/configuration/compilation
53
+
54
+ module.exports = {
55
+ jsc: {
56
+ transform: {
57
+ react: {
58
+ runtime: "automatic"
59
+ }
60
+ }
61
+ }
62
+ }
63
+ JS
64
+
65
+ def initialize(root_path, logger: nil)
66
+ @root_path = Pathname.new(root_path)
67
+ @logger = logger || Logger.new($stdout)
68
+ end
69
+
70
+ def migrate_to_swc(run_installer: true)
71
+ logger.info "🔄 Starting migration from Babel to SWC..."
72
+
73
+ results = {
74
+ config_updated: update_shakapacker_config,
75
+ packages_installed: install_swc_packages,
76
+ swc_config_created: create_swc_config,
77
+ babel_packages_found: find_babel_packages
78
+ }
79
+
80
+ logger.info "🎉 Migration to SWC complete!"
81
+ logger.info " Note: SWC is approximately 20x faster than Babel for transpilation."
82
+ logger.info " Please test your application thoroughly after migration."
83
+ logger.info "\n📝 Configuration Info:"
84
+ logger.info " - config/swc.config.js is merged with Shakapacker's default SWC configuration"
85
+ logger.info " - You can customize config/swc.config.js to add additional options"
86
+ logger.info " - Avoid using .swcrc as it overrides Shakapacker defaults completely"
87
+
88
+ # Show cleanup recommendations if babel packages found
89
+ if results[:babel_packages_found].any?
90
+ logger.info "\n🧹 Cleanup Recommendations:"
91
+ logger.info " Found the following Babel packages in your package.json:"
92
+ results[:babel_packages_found].each do |package|
93
+ logger.info " - #{package}"
94
+ end
95
+ logger.info "\n To remove them, run:"
96
+ logger.info " rails shakapacker:clean_babel_packages"
97
+ end
98
+
99
+ # Suggest running doctor to verify configuration
100
+ logger.info "\n🩺 Run 'rails shakapacker:doctor' to verify your configuration"
101
+
102
+ # Run package manager install if packages were added
103
+ if run_installer && results[:packages_installed].any?
104
+ run_package_manager_install
105
+ end
106
+
107
+ results
108
+ end
109
+
110
+ def clean_babel_packages(run_installer: true)
111
+ logger.info "🧹 Removing Babel packages..."
112
+
113
+ package_json_path = root_path.join("package.json")
114
+ unless package_json_path.exist?
115
+ logger.error "❌ No package.json found"
116
+ return { removed_packages: [], config_files_deleted: [], preserved_packages: [] }
117
+ end
118
+
119
+ # Check if ESLint uses Babel parser
120
+ preserved_for_eslint = []
121
+ if eslint_uses_babel?
122
+ logger.info "\n⚠️ ESLint configuration detected that uses Babel parser"
123
+ logger.info " Preserving @babel/core and @babel/eslint-parser for ESLint compatibility"
124
+ logger.info " To switch ESLint parser:"
125
+ logger.info " 1. For TypeScript: use @typescript-eslint/parser"
126
+ logger.info " 2. For JavaScript: use espree (ESLint's default parser)"
127
+ preserved_for_eslint = ESLINT_BABEL_PACKAGES
128
+ end
129
+
130
+ removed_packages = remove_babel_from_package_json(package_json_path, preserve: preserved_for_eslint)
131
+ deleted_files = delete_babel_config_files
132
+
133
+ if removed_packages.any?
134
+ logger.info "✅ Babel packages removed successfully!"
135
+ run_package_manager_install if run_installer
136
+ else
137
+ logger.info "ℹ️ No Babel packages found to remove"
138
+ end
139
+
140
+ { removed_packages: removed_packages, config_files_deleted: deleted_files, preserved_packages: preserved_for_eslint }
141
+ end
142
+
143
+ def find_babel_packages
144
+ package_json_path = root_path.join("package.json")
145
+ return [] unless package_json_path.exist?
146
+
147
+ begin
148
+ package_json = JSON.parse(File.read(package_json_path))
149
+ dependencies = package_json["dependencies"] || {}
150
+ dev_dependencies = package_json["devDependencies"] || {}
151
+ all_deps = dependencies.merge(dev_dependencies)
152
+
153
+ # Find all babel packages (including ESLint-related ones for display)
154
+ all_babel_packages = BABEL_PACKAGES + ESLINT_BABEL_PACKAGES
155
+ found_packages = all_babel_packages.select { |pkg| all_deps.key?(pkg) }
156
+ found_packages
157
+ rescue JSON::ParserError => e
158
+ logger.error "Failed to parse package.json: #{e.message}"
159
+ []
160
+ end
161
+ end
162
+
163
+ private
164
+
165
+ def eslint_uses_babel?
166
+ # Check for ESLint config files
167
+ # Note: This is a heuristic check that may have false positives (e.g., in comments),
168
+ # but false positives only result in an extra warning, which is safer than silently
169
+ # breaking ESLint configurations.
170
+ ESLINT_CONFIG_FILES.each do |config_file|
171
+ config_path = root_path.join(config_file)
172
+ next unless config_path.exist?
173
+
174
+ content = File.read(config_path)
175
+ # Check for Babel parser references
176
+ return true if content.match?(/@babel\/eslint-parser|babel-eslint/)
177
+ end
178
+
179
+ # Check package.json for eslintConfig
180
+ package_json_path = root_path.join("package.json")
181
+ if package_json_path.exist?
182
+ begin
183
+ package_json = JSON.parse(File.read(package_json_path))
184
+ if package_json["eslintConfig"]
185
+ # Check parser field explicitly
186
+ parser = package_json["eslintConfig"]["parser"]
187
+ return true if parser && parser.match?(/@babel\/eslint-parser|babel-eslint/)
188
+
189
+ # Also check entire config for babel parser references (catches nested configs)
190
+ return true if package_json["eslintConfig"].to_json.match?(/@babel\/eslint-parser|babel-eslint/)
191
+ end
192
+
193
+ # Check if Babel ESLint packages are installed
194
+ dependencies = package_json["dependencies"] || {}
195
+ dev_dependencies = package_json["devDependencies"] || {}
196
+ all_deps = dependencies.merge(dev_dependencies)
197
+ return true if all_deps.key?("@babel/eslint-parser") || all_deps.key?("babel-eslint")
198
+ rescue JSON::ParserError => e
199
+ logger.debug "Could not parse package.json for ESLint detection: #{e.message}"
200
+ end
201
+ end
202
+
203
+ false
204
+ end
205
+
206
+ def update_shakapacker_config
207
+ config_path = root_path.join("config/shakapacker.yml")
208
+ return false unless config_path.exist?
209
+
210
+ logger.info "📝 Updating shakapacker.yml..."
211
+ config = begin
212
+ YAML.load_file(config_path, aliases: true)
213
+ rescue ArgumentError
214
+ YAML.load_file(config_path)
215
+ end
216
+
217
+ config.each do |env, settings|
218
+ next unless settings.is_a?(Hash)
219
+
220
+ if settings["babel"]
221
+ logger.info " - Removing babel config from #{env} environment"
222
+ settings.delete("babel")
223
+ end
224
+
225
+ settings["swc"] = true
226
+ logger.info " - Enabled SWC for #{env} environment"
227
+ end
228
+
229
+ File.write(config_path, config.to_yaml)
230
+ logger.info "✅ shakapacker.yml updated"
231
+ true
232
+ rescue StandardError => e
233
+ logger.error "Failed to update config: #{e.message}"
234
+ false
235
+ end
236
+
237
+ def install_swc_packages
238
+ package_json_path = root_path.join("package.json")
239
+ return {} unless package_json_path.exist?
240
+
241
+ logger.info "📦 Installing SWC dependencies..."
242
+ package_json = JSON.parse(File.read(package_json_path))
243
+
244
+ dependencies = package_json["dependencies"] || {}
245
+ dev_dependencies = package_json["devDependencies"] || {}
246
+ installed = {}
247
+
248
+ SWC_PACKAGES.each do |package, version|
249
+ unless dependencies[package] || dev_dependencies[package]
250
+ logger.info " - Adding #{package}@#{version}"
251
+ dev_dependencies[package] = version
252
+ installed[package] = version
253
+ else
254
+ logger.info " - #{package} already installed"
255
+ end
256
+ end
257
+
258
+ if installed.any?
259
+ package_json["devDependencies"] = dev_dependencies
260
+ File.write(package_json_path, JSON.pretty_generate(package_json) + "\n")
261
+ logger.info "✅ package.json updated with SWC dependencies"
262
+ end
263
+
264
+ installed
265
+ rescue StandardError => e
266
+ logger.error "Failed to install packages: #{e.message}"
267
+ {}
268
+ end
269
+
270
+ def create_swc_config
271
+ config_dir = root_path.join("config")
272
+ swc_config_path = config_dir.join("swc.config.js")
273
+
274
+ if swc_config_path.exist?
275
+ logger.info "ℹ️ config/swc.config.js already exists"
276
+ return false
277
+ end
278
+
279
+ FileUtils.mkdir_p(config_dir) unless config_dir.exist?
280
+
281
+ logger.info "📄 Creating config/swc.config.js..."
282
+ File.write(swc_config_path, DEFAULT_SWC_CONFIG)
283
+ logger.info "✅ config/swc.config.js created"
284
+ true
285
+ rescue StandardError => e
286
+ logger.error "Failed to create config/swc.config.js: #{e.message}"
287
+ false
288
+ end
289
+
290
+ def remove_babel_from_package_json(package_json_path, preserve: [])
291
+ package_json = JSON.parse(File.read(package_json_path))
292
+ dependencies = package_json["dependencies"] || {}
293
+ dev_dependencies = package_json["devDependencies"] || {}
294
+ removed_packages = []
295
+
296
+ BABEL_PACKAGES.each do |package|
297
+ next if preserve.include?(package)
298
+
299
+ if dependencies.delete(package)
300
+ removed_packages << package
301
+ logger.info " - Removed #{package} from dependencies"
302
+ end
303
+ if dev_dependencies.delete(package)
304
+ removed_packages << package
305
+ logger.info " - Removed #{package} from devDependencies"
306
+ end
307
+ end
308
+
309
+ # Log preserved packages
310
+ preserve.each do |package|
311
+ if dependencies[package] || dev_dependencies[package]
312
+ logger.info " - Preserved #{package} (needed for ESLint)"
313
+ end
314
+ end
315
+
316
+ if removed_packages.any?
317
+ package_json["dependencies"] = dependencies
318
+ package_json["devDependencies"] = dev_dependencies
319
+ File.write(package_json_path, JSON.pretty_generate(package_json) + "\n")
320
+ logger.info "✅ package.json updated"
321
+ end
322
+
323
+ removed_packages.uniq
324
+ rescue StandardError => e
325
+ logger.error "Failed to remove packages: #{e.message}"
326
+ []
327
+ end
328
+
329
+ def delete_babel_config_files
330
+ deleted_files = []
331
+ babel_config_files = [".babelrc", "babel.config.js", ".babelrc.js", "babel.config.json"]
332
+
333
+ babel_config_files.each do |file|
334
+ file_path = root_path.join(file)
335
+ if file_path.exist?
336
+ logger.info " - Removing #{file}"
337
+ File.delete(file_path)
338
+ deleted_files << file
339
+ end
340
+ end
341
+
342
+ deleted_files
343
+ rescue StandardError => e
344
+ logger.error "Failed to delete config files: #{e.message}"
345
+ []
346
+ end
347
+
348
+ def run_package_manager_install
349
+ logger.info "\n🔧 Running npm/yarn install..."
350
+
351
+ yarn_lock = root_path.join("yarn.lock")
352
+ pnpm_lock = root_path.join("pnpm-lock.yaml")
353
+
354
+ if yarn_lock.exist?
355
+ system("yarn install")
356
+ elsif pnpm_lock.exist?
357
+ system("pnpm install")
358
+ else
359
+ system("npm install")
360
+ end
361
+ end
362
+
363
+ def package_manager
364
+ yarn_lock = root_path.join("yarn.lock")
365
+ pnpm_lock = root_path.join("pnpm-lock.yaml")
366
+
367
+ if yarn_lock.exist?
368
+ "yarn"
369
+ elsif pnpm_lock.exist?
370
+ "pnpm"
371
+ else
372
+ "npm"
373
+ end
374
+ end
375
+ end
376
+ end
@@ -58,6 +58,8 @@ module Shakapacker
58
58
  def self.rails_root
59
59
  if defined?(APP_ROOT)
60
60
  Pathname.new(APP_ROOT)
61
+ elsif ENV["APP_ROOT"]
62
+ Pathname.new(ENV["APP_ROOT"])
61
63
  elsif defined?(Rails)
62
64
  Rails.root
63
65
  else
@@ -1,4 +1,4 @@
1
1
  module Shakapacker
2
2
  # Change the version in package.json too, please!
3
- VERSION = "8.4.0".freeze
3
+ VERSION = "9.0.0".freeze
4
4
  end
@@ -126,7 +126,7 @@ module Shakapacker
126
126
  end
127
127
 
128
128
  def relative_path?
129
- raw.match(%r{(\.\.|\Afile:///)}).present?
129
+ raw.match(%r{(\.\.|\Afile:)}).present?
130
130
  end
131
131
 
132
132
  def git_url?
@@ -4,48 +4,10 @@ require_relative "runner"
4
4
 
5
5
  module Shakapacker
6
6
  class WebpackRunner < Shakapacker::Runner
7
- WEBPACK_COMMANDS = [
8
- "help",
9
- "h",
10
- "--help",
11
- "-h",
12
- "version",
13
- "v",
14
- "--version",
15
- "-v",
16
- "info",
17
- "i"
18
- ].freeze
19
-
20
- def run
21
- env = Shakapacker::Compiler.env
22
- env["SHAKAPACKER_CONFIG"] = @shakapacker_config
23
- env["NODE_OPTIONS"] = ENV["NODE_OPTIONS"] || ""
24
-
25
- cmd = build_cmd
26
-
27
- if @argv.delete("--debug-shakapacker")
28
- env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --inspect-brk"
29
- end
30
-
31
- if @argv.delete "--trace-deprecation"
32
- env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --trace-deprecation"
33
- end
34
-
35
- if @argv.delete "--no-deprecation"
36
- env["NODE_OPTIONS"] = "#{env["NODE_OPTIONS"]} --no-deprecation"
37
- end
38
-
39
- # Webpack commands are not compatible with --config option.
40
- if (@argv & WEBPACK_COMMANDS).empty?
41
- cmd += ["--config", @webpack_config]
42
- end
43
-
44
- cmd += @argv
45
-
46
- Dir.chdir(@app_path) do
47
- Kernel.exec env, *cmd
48
- end
7
+ def self.run(argv)
8
+ $stdout.sync = true
9
+ ENV["NODE_ENV"] ||= (ENV["RAILS_ENV"] == "production") ? "production" : "development"
10
+ new(argv).run
49
11
  end
50
12
 
51
13
  private