shakapacker 8.0.2 → 9.2.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.
- checksums.yaml +4 -4
- data/.eslintignore +1 -0
- data/.eslintrc.fast.js +40 -0
- data/.eslintrc.js +48 -0
- data/.github/STATUS.md +1 -0
- data/.github/workflows/claude-code-review.yml +54 -0
- data/.github/workflows/claude.yml +50 -0
- data/.github/workflows/dummy.yml +9 -4
- data/.github/workflows/generator.yml +32 -10
- data/.github/workflows/node.yml +23 -1
- data/.github/workflows/ruby.yml +33 -2
- data/.github/workflows/test-bundlers.yml +170 -0
- data/.gitignore +20 -0
- data/.husky/pre-commit +2 -0
- data/.npmignore +56 -0
- data/.prettierignore +3 -0
- data/.rubocop.yml +1 -0
- data/.yalcignore +26 -0
- data/CHANGELOG.md +302 -16
- data/CLAUDE.md +29 -0
- data/CONTRIBUTING.md +138 -20
- data/Gemfile.lock +83 -89
- data/README.md +343 -105
- data/Rakefile +39 -4
- data/TODO.md +50 -0
- data/TODO_v9.md +87 -0
- data/bin/export-bundler-config +11 -0
- data/conductor-setup.sh +70 -0
- data/conductor.json +7 -0
- data/docs/cdn_setup.md +379 -0
- data/docs/common-upgrades.md +615 -0
- data/docs/css-modules-export-mode.md +512 -0
- data/docs/deployment.md +62 -9
- data/docs/optional-peer-dependencies.md +198 -0
- data/docs/peer-dependencies.md +60 -0
- data/docs/react.md +6 -14
- data/docs/releasing.md +197 -0
- data/docs/rspack.md +190 -0
- data/docs/rspack_migration_guide.md +305 -0
- data/docs/subresource_integrity.md +54 -0
- data/docs/transpiler-migration.md +209 -0
- data/docs/transpiler-performance.md +179 -0
- data/docs/troubleshooting.md +157 -22
- data/docs/typescript-migration.md +379 -0
- data/docs/typescript.md +99 -0
- data/docs/using_esbuild_loader.md +3 -3
- data/docs/using_swc_loader.md +112 -10
- data/docs/v6_upgrade.md +10 -0
- data/docs/v8_upgrade.md +3 -5
- data/docs/v9_upgrade.md +458 -0
- data/gemfiles/Gemfile-rails.6.0.x +2 -1
- data/gemfiles/Gemfile-rails.6.1.x +1 -1
- data/gemfiles/Gemfile-rails.7.0.x +2 -2
- data/gemfiles/Gemfile-rails.7.1.x +1 -2
- data/gemfiles/Gemfile-rails.7.2.x +11 -0
- data/gemfiles/Gemfile-rails.8.0.x +11 -0
- data/lib/install/bin/export-bundler-config +11 -0
- data/lib/install/bin/shakapacker +4 -6
- data/lib/install/bin/shakapacker-dev-server +1 -1
- data/lib/install/config/rspack/rspack.config.js +6 -0
- data/lib/install/config/rspack/rspack.config.ts +7 -0
- data/lib/install/config/shakapacker.yml +25 -5
- data/lib/install/config/webpack/webpack.config.ts +7 -0
- data/lib/install/package.json +38 -0
- data/lib/install/template.rb +194 -44
- data/lib/shakapacker/bundler_switcher.rb +329 -0
- data/lib/shakapacker/compiler.rb +2 -1
- data/lib/shakapacker/compiler_strategy.rb +2 -2
- data/lib/shakapacker/configuration.rb +173 -2
- data/lib/shakapacker/dev_server_runner.rb +29 -8
- data/lib/shakapacker/digest_strategy.rb +2 -1
- data/lib/shakapacker/doctor.rb +905 -0
- data/lib/shakapacker/helper.rb +64 -16
- data/lib/shakapacker/manifest.rb +10 -3
- data/lib/shakapacker/mtime_strategy.rb +1 -1
- data/lib/shakapacker/railtie.rb +4 -4
- data/lib/shakapacker/rspack_runner.rb +19 -0
- data/lib/shakapacker/runner.rb +159 -10
- data/lib/shakapacker/swc_migrator.rb +384 -0
- data/lib/shakapacker/utils/manager.rb +15 -2
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/version_checker.rb +2 -2
- data/lib/shakapacker/webpack_runner.rb +6 -43
- data/lib/shakapacker.rb +22 -11
- data/lib/tasks/shakapacker/doctor.rake +8 -0
- data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
- data/lib/tasks/shakapacker/install.rake +12 -2
- data/lib/tasks/shakapacker/migrate_to_swc.rake +13 -0
- data/lib/tasks/shakapacker/switch_bundler.rake +82 -0
- data/lib/tasks/shakapacker.rake +2 -0
- data/package/.npmignore +4 -0
- data/package/babel/preset.ts +56 -0
- data/package/config.ts +175 -0
- data/package/configExporter/cli.ts +683 -0
- data/package/configExporter/configDocs.ts +102 -0
- data/package/configExporter/fileWriter.ts +92 -0
- data/package/configExporter/index.ts +5 -0
- data/package/configExporter/types.ts +36 -0
- data/package/configExporter/yamlSerializer.ts +266 -0
- data/package/{dev_server.js → dev_server.ts} +8 -5
- data/package/env.ts +92 -0
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +30 -0
- data/package/environments/{base.js → base.ts} +56 -60
- data/package/environments/development.ts +90 -0
- data/package/environments/production.ts +80 -0
- data/package/environments/test.ts +53 -0
- data/package/environments/types.ts +98 -0
- data/package/esbuild/index.ts +42 -0
- data/package/index.d.ts +3 -60
- data/package/index.ts +55 -0
- data/package/loaders.d.ts +28 -0
- data/package/optimization/rspack.ts +36 -0
- data/package/optimization/webpack.ts +57 -0
- data/package/plugins/rspack.ts +103 -0
- data/package/plugins/webpack.ts +62 -0
- data/package/rspack/index.ts +64 -0
- data/package/rules/{babel.js → babel.ts} +2 -2
- data/package/rules/{coffee.js → coffee.ts} +1 -1
- data/package/rules/css.ts +3 -0
- data/package/rules/{erb.js → erb.ts} +1 -1
- data/package/rules/esbuild.ts +10 -0
- data/package/rules/file.ts +40 -0
- data/package/rules/{jscommon.js → jscommon.ts} +4 -4
- data/package/rules/{less.js → less.ts} +4 -4
- data/package/rules/raw.ts +25 -0
- data/package/rules/rspack.ts +176 -0
- data/package/rules/{sass.js → sass.ts} +7 -3
- data/package/rules/{stylus.js → stylus.ts} +4 -8
- data/package/rules/swc.ts +10 -0
- data/package/rules/webpack.ts +16 -0
- data/package/swc/index.ts +56 -0
- data/package/types/README.md +88 -0
- data/package/types/index.ts +61 -0
- data/package/types.ts +108 -0
- data/package/utils/configPath.ts +6 -0
- data/package/utils/debug.ts +49 -0
- data/package/utils/defaultConfigPath.ts +4 -0
- data/package/utils/errorCodes.ts +219 -0
- data/package/utils/errorHelpers.ts +143 -0
- data/package/utils/getStyleRule.ts +64 -0
- data/package/utils/helpers.ts +85 -0
- data/package/utils/{inliningCss.js → inliningCss.ts} +3 -3
- data/package/utils/pathValidation.ts +139 -0
- data/package/utils/requireOrError.ts +15 -0
- data/package/utils/snakeToCamelCase.ts +5 -0
- data/package/utils/typeGuards.ts +342 -0
- data/package/utils/validateDependencies.ts +61 -0
- data/package/webpack-types.d.ts +33 -0
- data/package/webpackDevServerConfig.ts +117 -0
- data/package-lock.json +13047 -0
- data/package.json +154 -18
- data/scripts/remove-use-strict.js +45 -0
- data/scripts/type-check-no-emit.js +27 -0
- data/test/helpers.js +1 -1
- data/test/package/config.test.js +43 -0
- data/test/package/env.test.js +42 -7
- data/test/package/environments/base.test.js +5 -1
- data/test/package/rules/babel.test.js +16 -0
- data/test/package/rules/esbuild.test.js +1 -1
- data/test/package/rules/raw.test.js +40 -7
- data/test/package/rules/swc.test.js +1 -1
- data/test/package/rules/webpack.test.js +35 -0
- data/test/package/staging.test.js +4 -3
- data/test/package/transpiler-defaults.test.js +127 -0
- data/test/peer-dependencies.sh +85 -0
- data/test/scripts/remove-use-strict.test.js +125 -0
- data/test/typescript/build.test.js +118 -0
- data/test/typescript/environments.test.js +107 -0
- data/test/typescript/pathValidation.test.js +142 -0
- data/test/typescript/securityValidation.test.js +182 -0
- data/tools/README.md +124 -0
- data/tools/css-modules-v9-codemod.js +179 -0
- data/tsconfig.eslint.json +16 -0
- data/tsconfig.json +38 -0
- data/yarn.lock +4165 -2706
- metadata +129 -41
- data/package/babel/preset.js +0 -37
- data/package/config.js +0 -54
- data/package/env.js +0 -48
- data/package/environments/development.js +0 -13
- data/package/environments/production.js +0 -88
- data/package/environments/test.js +0 -3
- data/package/esbuild/index.js +0 -40
- data/package/index.js +0 -40
- data/package/rules/css.js +0 -3
- data/package/rules/esbuild.js +0 -10
- data/package/rules/file.js +0 -29
- data/package/rules/index.js +0 -20
- data/package/rules/raw.js +0 -5
- data/package/rules/swc.js +0 -10
- data/package/swc/index.js +0 -50
- data/package/utils/configPath.js +0 -4
- data/package/utils/defaultConfigPath.js +0 -2
- data/package/utils/getStyleRule.js +0 -40
- data/package/utils/helpers.js +0 -58
- data/package/utils/snakeToCamelCase.js +0 -5
- data/package/webpackDevServerConfig.js +0 -71
- data/test/package/rules/index.test.js +0 -16
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// See the shakacode/shakapacker README and docs directory for advice on customizing your webpackConfig.
|
|
2
|
+
import { generateWebpackConfig } from 'shakapacker'
|
|
3
|
+
import type { Configuration } from 'webpack'
|
|
4
|
+
|
|
5
|
+
const webpackConfig: Configuration = generateWebpackConfig()
|
|
6
|
+
|
|
7
|
+
export default webpackConfig
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"rspack": {
|
|
3
|
+
"@rspack/cli": "^1.0.0",
|
|
4
|
+
"@rspack/core": "^1.0.0",
|
|
5
|
+
"rspack-manifest-plugin": "^5.0.0"
|
|
6
|
+
},
|
|
7
|
+
"webpack": {
|
|
8
|
+
"mini-css-extract-plugin": "^2.0.0",
|
|
9
|
+
"terser-webpack-plugin": "^5.3.1",
|
|
10
|
+
"webpack": "^5.76.0",
|
|
11
|
+
"webpack-assets-manifest": "^5.0.6 || ^6.0.0",
|
|
12
|
+
"webpack-cli": "^4.9.2 || ^5.0.0 || ^6.0.0",
|
|
13
|
+
"webpack-dev-server": "^4.15.2 || ^5.2.2",
|
|
14
|
+
"webpack-merge": "^5.8.0 || ^6.0.0",
|
|
15
|
+
"webpack-subresource-integrity": "^5.1.0"
|
|
16
|
+
},
|
|
17
|
+
"common": {
|
|
18
|
+
"compression-webpack-plugin": "^9.0.0 || ^10.0.0|| ^11.0.0",
|
|
19
|
+
"css-loader": "^6.0.0 || ^7.0.0",
|
|
20
|
+
"sass-loader": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
21
|
+
"style-loader": "^3.0.0 || ^4.0.0"
|
|
22
|
+
},
|
|
23
|
+
"babel": {
|
|
24
|
+
"@babel/core": "^7.17.9",
|
|
25
|
+
"@babel/plugin-transform-runtime": "^7.17.0",
|
|
26
|
+
"@babel/preset-env": "^7.16.11",
|
|
27
|
+
"@babel/runtime": "^7.17.9",
|
|
28
|
+
"babel-loader": "^8.2.4 || ^9.0.0 || ^10.0.0"
|
|
29
|
+
},
|
|
30
|
+
"swc": {
|
|
31
|
+
"@swc/core": "^1.3.0",
|
|
32
|
+
"swc-loader": "^0.2.0"
|
|
33
|
+
},
|
|
34
|
+
"esbuild": {
|
|
35
|
+
"esbuild": "^0.24.0",
|
|
36
|
+
"esbuild-loader": "^4.0.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
data/lib/install/template.rb
CHANGED
|
@@ -2,26 +2,72 @@ require "shakapacker/utils/misc"
|
|
|
2
2
|
require "shakapacker/utils/manager"
|
|
3
3
|
require "shakapacker/utils/version_syntax_converter"
|
|
4
4
|
require "package_json"
|
|
5
|
+
require "yaml"
|
|
6
|
+
require "json"
|
|
5
7
|
|
|
6
8
|
# Install Shakapacker
|
|
7
9
|
|
|
8
10
|
force_option = ENV["FORCE"] ? { force: true } : {}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
+
# Initialize variables for use throughout the template
|
|
13
|
+
# Using instance variable to avoid method definition issues in Rails templates
|
|
14
|
+
@package_json ||= PackageJson.new
|
|
15
|
+
install_dir = File.expand_path(File.dirname(__FILE__))
|
|
12
16
|
|
|
13
|
-
|
|
14
|
-
|
|
17
|
+
# Installation strategy:
|
|
18
|
+
# - USE_BABEL_PACKAGES installs both babel AND swc for compatibility
|
|
19
|
+
# - Otherwise install only the specified transpiler
|
|
20
|
+
if ENV["USE_BABEL_PACKAGES"] == "true" || ENV["USE_BABEL_PACKAGES"] == "1"
|
|
21
|
+
@transpiler_to_install = "babel"
|
|
22
|
+
say "📦 Installing Babel packages (USE_BABEL_PACKAGES is set)", :yellow
|
|
23
|
+
say "✨ Also installing SWC packages for default config compatibility", :green
|
|
24
|
+
elsif ENV["JAVASCRIPT_TRANSPILER"]
|
|
25
|
+
@transpiler_to_install = ENV["JAVASCRIPT_TRANSPILER"]
|
|
26
|
+
say "📦 Installing #{@transpiler_to_install} packages", :blue
|
|
27
|
+
else
|
|
28
|
+
# Default to swc (matches the default in shakapacker.yml)
|
|
29
|
+
@transpiler_to_install = "swc"
|
|
30
|
+
say "✨ Installing SWC packages (20x faster than Babel)", :green
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Copy config file
|
|
34
|
+
copy_file "#{install_dir}/config/shakapacker.yml", "config/shakapacker.yml", force_option
|
|
35
|
+
|
|
36
|
+
# Update config if USE_BABEL_PACKAGES is set to ensure babel is used at runtime
|
|
37
|
+
if @transpiler_to_install == "babel" && !ENV["JAVASCRIPT_TRANSPILER"]
|
|
38
|
+
# When USE_BABEL_PACKAGES is set, update the config to use babel
|
|
39
|
+
gsub_file "config/shakapacker.yml", "javascript_transpiler: 'swc'", "javascript_transpiler: 'babel'"
|
|
40
|
+
say " 📝 Updated config/shakapacker.yml to use Babel transpiler", :green
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Detect TypeScript usage
|
|
44
|
+
# Auto-detect from tsconfig.json or explicit via SHAKAPACKER_USE_TYPESCRIPT env var
|
|
45
|
+
@use_typescript = File.exist?(Rails.root.join("tsconfig.json")) ||
|
|
46
|
+
ENV["SHAKAPACKER_USE_TYPESCRIPT"] == "true"
|
|
47
|
+
assets_bundler = ENV["SHAKAPACKER_ASSETS_BUNDLER"] || "webpack"
|
|
48
|
+
config_extension = @use_typescript ? "ts" : "js"
|
|
49
|
+
|
|
50
|
+
say "Copying #{assets_bundler} core config (#{config_extension.upcase})"
|
|
51
|
+
config_file = "#{assets_bundler}.config.#{config_extension}"
|
|
52
|
+
source_config = "#{install_dir}/config/#{assets_bundler}/#{config_file}"
|
|
53
|
+
dest_config = "config/#{assets_bundler}/#{config_file}"
|
|
54
|
+
|
|
55
|
+
empty_directory "config/#{assets_bundler}"
|
|
56
|
+
copy_file source_config, dest_config, force_option
|
|
57
|
+
|
|
58
|
+
if @use_typescript
|
|
59
|
+
say " ✨ Using TypeScript config for enhanced type safety", :green
|
|
60
|
+
end
|
|
15
61
|
|
|
16
62
|
if Dir.exist?(Shakapacker.config.source_path)
|
|
17
63
|
say "The packs app source directory already exists"
|
|
18
64
|
else
|
|
19
65
|
say "Creating packs app source directory"
|
|
20
66
|
empty_directory "app/javascript/packs"
|
|
21
|
-
copy_file "#{
|
|
67
|
+
copy_file "#{install_dir}/application.js", "app/javascript/packs/application.js"
|
|
22
68
|
end
|
|
23
69
|
|
|
24
|
-
apply "#{
|
|
70
|
+
apply "#{install_dir}/binstubs.rb"
|
|
25
71
|
|
|
26
72
|
git_ignore_path = Rails.root.join(".gitignore")
|
|
27
73
|
if File.exist?(git_ignore_path)
|
|
@@ -44,17 +90,8 @@ else
|
|
|
44
90
|
say %( Add <%= javascript_pack_tag "application" %> within the <head> tag in your custom layout.)
|
|
45
91
|
end
|
|
46
92
|
|
|
47
|
-
def package_json
|
|
48
|
-
@package_json ||= PackageJson.new
|
|
49
|
-
end
|
|
50
|
-
|
|
51
93
|
# setup the package manager with default values
|
|
52
|
-
package_json.merge! do |pj|
|
|
53
|
-
babel = pj.fetch("babel", {})
|
|
54
|
-
|
|
55
|
-
babel["presets"] ||= []
|
|
56
|
-
babel["presets"].push("./node_modules/shakapacker/package/babel/preset.js")
|
|
57
|
-
|
|
94
|
+
@package_json.merge! do |pj|
|
|
58
95
|
package_manager = pj.fetch("packageManager") do
|
|
59
96
|
"#{Shakapacker::Utils::Manager.guess_binary}@#{Shakapacker::Utils::Manager.guess_version}"
|
|
60
97
|
end
|
|
@@ -63,7 +100,6 @@ package_json.merge! do |pj|
|
|
|
63
100
|
"name" => "app",
|
|
64
101
|
"private" => true,
|
|
65
102
|
"version" => "0.1.0",
|
|
66
|
-
"babel" => babel,
|
|
67
103
|
"browserslist" => [
|
|
68
104
|
"defaults"
|
|
69
105
|
],
|
|
@@ -75,7 +111,7 @@ Shakapacker::Utils::Manager.error_unless_package_manager_is_obvious!
|
|
|
75
111
|
|
|
76
112
|
# Ensure there is `system!("bin/yarn")` command in `./bin/setup` file
|
|
77
113
|
if (setup_path = Rails.root.join("bin/setup")).exist?
|
|
78
|
-
native_install_command = package_json.manager.native_install_command.join(" ")
|
|
114
|
+
native_install_command = @package_json.manager.native_install_command.join(" ")
|
|
79
115
|
|
|
80
116
|
say "Run #{native_install_command} during bin/setup"
|
|
81
117
|
|
|
@@ -104,40 +140,126 @@ if (setup_path = Rails.root.join("bin/setup")).exist?
|
|
|
104
140
|
end
|
|
105
141
|
end
|
|
106
142
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
exit 1
|
|
112
|
-
end
|
|
143
|
+
Dir.chdir(Rails.root) do
|
|
144
|
+
# In CI, use the pre-packed tarball if available
|
|
145
|
+
if ENV["SHAKAPACKER_NPM_PACKAGE"]
|
|
146
|
+
package_path = ENV["SHAKAPACKER_NPM_PACKAGE"]
|
|
113
147
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
148
|
+
# Validate package path to prevent directory traversal and invalid file types
|
|
149
|
+
begin
|
|
150
|
+
# Resolve to absolute path
|
|
151
|
+
absolute_path = File.expand_path(package_path)
|
|
117
152
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
"
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
153
|
+
# Reject paths containing directory traversal
|
|
154
|
+
if package_path.include?("..") || absolute_path.include?("..")
|
|
155
|
+
say "❌ Security Error: Package path contains directory traversal: #{package_path}", :red
|
|
156
|
+
exit 1
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
# Ensure filename ends with .tgz or .tar.gz
|
|
160
|
+
unless absolute_path.end_with?(".tgz", ".tar.gz")
|
|
161
|
+
say "❌ Security Error: Package must be a .tgz or .tar.gz file: #{package_path}", :red
|
|
162
|
+
exit 1
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Check existence only after validation
|
|
166
|
+
if File.exist?(absolute_path)
|
|
167
|
+
say "📦 Installing shakapacker from local package: #{absolute_path}", :cyan
|
|
168
|
+
begin
|
|
169
|
+
@package_json.manager.add!([absolute_path], type: :production)
|
|
170
|
+
rescue PackageJson::Error
|
|
171
|
+
say "Shakapacker installation failed 😭 See above for details.", :red
|
|
172
|
+
exit 1
|
|
173
|
+
end
|
|
174
|
+
else
|
|
175
|
+
say "⚠️ SHAKAPACKER_NPM_PACKAGE set but file not found: #{absolute_path}", :yellow
|
|
176
|
+
say "Falling back to npm registry...", :yellow
|
|
177
|
+
npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
|
|
178
|
+
begin
|
|
179
|
+
@package_json.manager.add!(["shakapacker@#{npm_version}"], type: :production)
|
|
180
|
+
rescue PackageJson::Error
|
|
181
|
+
say "Shakapacker installation failed 😭 See above for details.", :red
|
|
182
|
+
exit 1
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
rescue => e
|
|
186
|
+
say "❌ Error validating package path: #{e.message}", :red
|
|
187
|
+
exit 1
|
|
188
|
+
end
|
|
189
|
+
else
|
|
190
|
+
npm_version = Shakapacker::Utils::VersionSyntaxConverter.new.rubygem_to_npm(Shakapacker::VERSION)
|
|
191
|
+
say "Installing shakapacker@#{npm_version}"
|
|
192
|
+
begin
|
|
193
|
+
@package_json.manager.add!(["shakapacker@#{npm_version}"], type: :production)
|
|
194
|
+
rescue PackageJson::Error
|
|
195
|
+
say "Shakapacker installation failed 😭 See above for details.", :red
|
|
196
|
+
exit 1
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
@package_json.merge! do |pj|
|
|
201
|
+
if pj["dependencies"] && pj["dependencies"]["shakapacker"]
|
|
202
|
+
{
|
|
203
|
+
"dependencies" => pj["dependencies"].merge({
|
|
204
|
+
# TODO: workaround for test suite - long-run need to actually account for diff pkg manager behaviour
|
|
205
|
+
"shakapacker" => pj["dependencies"]["shakapacker"].delete_prefix("^")
|
|
206
|
+
})
|
|
207
|
+
}
|
|
208
|
+
else
|
|
209
|
+
pj
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Inline fetch_peer_dependencies and fetch_common_dependencies
|
|
214
|
+
peers = PackageJson.read(install_dir).fetch(ENV["SHAKAPACKER_ASSETS_BUNDLER"] || "webpack")
|
|
215
|
+
common_deps = ENV["SKIP_COMMON_LOADERS"] ? {} : PackageJson.read(install_dir).fetch("common")
|
|
216
|
+
peers = peers.merge(common_deps)
|
|
217
|
+
|
|
218
|
+
# Add transpiler-specific dependencies based on detected/configured transpiler
|
|
219
|
+
# Inline the logic here since methods can't be called before they're defined in Rails templates
|
|
220
|
+
|
|
221
|
+
# Install transpiler-specific dependencies
|
|
222
|
+
# When USE_BABEL_PACKAGES is set, install both babel AND swc
|
|
223
|
+
# This ensures backward compatibility while supporting the default config
|
|
224
|
+
if @transpiler_to_install == "babel"
|
|
225
|
+
# Install babel packages
|
|
226
|
+
babel_deps = PackageJson.read(install_dir).fetch("babel")
|
|
227
|
+
peers = peers.merge(babel_deps)
|
|
228
|
+
|
|
229
|
+
# Also install SWC since that's what the default config uses
|
|
230
|
+
# This ensures the runtime works regardless of config
|
|
231
|
+
swc_deps = PackageJson.read(install_dir).fetch("swc")
|
|
232
|
+
peers = peers.merge(swc_deps)
|
|
233
|
+
|
|
234
|
+
say "ℹ️ Installing both Babel and SWC packages for compatibility:", :blue
|
|
235
|
+
say " - Babel packages are installed as requested via USE_BABEL_PACKAGES", :blue
|
|
236
|
+
say " - SWC packages are also installed to ensure the default config works", :blue
|
|
237
|
+
say " - Your actual transpiler will be determined by your shakapacker.yml configuration", :blue
|
|
238
|
+
elsif @transpiler_to_install == "swc"
|
|
239
|
+
swc_deps = PackageJson.read(install_dir).fetch("swc")
|
|
240
|
+
peers = peers.merge(swc_deps)
|
|
241
|
+
elsif @transpiler_to_install == "esbuild"
|
|
242
|
+
esbuild_deps = PackageJson.read(install_dir).fetch("esbuild")
|
|
243
|
+
peers = peers.merge(esbuild_deps)
|
|
130
244
|
end
|
|
131
245
|
|
|
132
|
-
peers = fetch_peer_dependencies
|
|
133
246
|
dev_dependency_packages = ["webpack-dev-server"]
|
|
134
247
|
|
|
135
248
|
dependencies_to_add = []
|
|
136
249
|
dev_dependencies_to_add = []
|
|
137
250
|
|
|
138
251
|
peers.each do |(package, version)|
|
|
139
|
-
|
|
140
|
-
|
|
252
|
+
# Handle versions like "^1.3.0" or ">= 4 || 5"
|
|
253
|
+
if version.start_with?("^", "~") || version.match?(/^\d+\.\d+/)
|
|
254
|
+
# Already has proper version format, use as-is
|
|
255
|
+
entry = "#{package}@#{version}"
|
|
256
|
+
else
|
|
257
|
+
# Extract major version from complex version strings like ">= 4 || 5"
|
|
258
|
+
# Fallback to "latest" if no version number found
|
|
259
|
+
version_match = version.split("||").last.match(/(\d+)/)
|
|
260
|
+
major_version = version_match ? version_match[1] : "latest"
|
|
261
|
+
entry = "#{package}@#{major_version}"
|
|
262
|
+
end
|
|
141
263
|
|
|
142
264
|
if dev_dependency_packages.include? package
|
|
143
265
|
dev_dependencies_to_add << entry
|
|
@@ -147,8 +269,36 @@ Dir.chdir(Rails.root) do
|
|
|
147
269
|
end
|
|
148
270
|
|
|
149
271
|
say "Adding shakapacker peerDependencies"
|
|
150
|
-
|
|
272
|
+
begin
|
|
273
|
+
@package_json.manager.add!(dependencies_to_add, type: :production)
|
|
274
|
+
rescue PackageJson::Error
|
|
275
|
+
say "Shakapacker installation failed 😭 See above for details.", :red
|
|
276
|
+
exit 1
|
|
277
|
+
end
|
|
151
278
|
|
|
152
279
|
say "Installing webpack-dev-server for live reloading as a development dependency"
|
|
153
|
-
|
|
280
|
+
begin
|
|
281
|
+
@package_json.manager.add!(dev_dependencies_to_add, type: :dev)
|
|
282
|
+
rescue PackageJson::Error
|
|
283
|
+
say "Shakapacker installation failed 😭 See above for details.", :red
|
|
284
|
+
exit 1
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Configure babel preset in package.json if babel is being used
|
|
288
|
+
if @transpiler_to_install == "babel"
|
|
289
|
+
@package_json.merge! do |pj|
|
|
290
|
+
babel = pj.fetch("babel", {})
|
|
291
|
+
babel["presets"] ||= []
|
|
292
|
+
unless babel["presets"].include?("./node_modules/shakapacker/package/babel/preset.js")
|
|
293
|
+
babel["presets"].push("./node_modules/shakapacker/package/babel/preset.js")
|
|
294
|
+
end
|
|
295
|
+
{ "babel" => babel }
|
|
296
|
+
end
|
|
297
|
+
end
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
# Helper methods defined at the end (Rails template convention)
|
|
301
|
+
|
|
302
|
+
def package_json
|
|
303
|
+
@package_json
|
|
154
304
|
end
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "yaml"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
|
|
6
|
+
module Shakapacker
|
|
7
|
+
# Provides functionality to switch between webpack and rspack bundlers
|
|
8
|
+
class BundlerSwitcher
|
|
9
|
+
SHAKAPACKER_CONFIG = "config/shakapacker.yml"
|
|
10
|
+
CUSTOM_DEPS_CONFIG = ".shakapacker-switch-bundler-dependencies.yml"
|
|
11
|
+
|
|
12
|
+
# Default dependencies for each bundler (package names only, no versions)
|
|
13
|
+
DEFAULT_RSPACK_DEPS = {
|
|
14
|
+
dev: %w[@rspack/cli @rspack/plugin-react-refresh],
|
|
15
|
+
prod: %w[@rspack/core rspack-manifest-plugin]
|
|
16
|
+
}.freeze
|
|
17
|
+
|
|
18
|
+
DEFAULT_WEBPACK_DEPS = {
|
|
19
|
+
dev: %w[webpack webpack-cli webpack-dev-server @pmmmwh/react-refresh-webpack-plugin @swc/core swc-loader],
|
|
20
|
+
prod: %w[webpack-assets-manifest webpack-merge]
|
|
21
|
+
}.freeze
|
|
22
|
+
|
|
23
|
+
attr_reader :root_path
|
|
24
|
+
|
|
25
|
+
def initialize(root_path = nil)
|
|
26
|
+
@root_path = root_path || (defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd))
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def current_bundler
|
|
30
|
+
config = load_yaml_config(config_path)
|
|
31
|
+
config.dig("default", "assets_bundler") || "webpack"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def switch_to(bundler, install_deps: false, no_uninstall: false)
|
|
35
|
+
unless %w[webpack rspack].include?(bundler)
|
|
36
|
+
raise ArgumentError, "Invalid bundler: #{bundler}. Must be 'webpack' or 'rspack'"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
current = current_bundler
|
|
40
|
+
if current == bundler && !install_deps
|
|
41
|
+
puts "✅ Already using #{bundler}"
|
|
42
|
+
return
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
if current == bundler && install_deps
|
|
46
|
+
puts "✅ Already using #{bundler} - reinstalling dependencies as requested"
|
|
47
|
+
manage_dependencies(bundler, install_deps, switching: false, no_uninstall: no_uninstall)
|
|
48
|
+
return
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
update_config(bundler)
|
|
52
|
+
|
|
53
|
+
puts "✅ Switched from #{current} to #{bundler}"
|
|
54
|
+
puts ""
|
|
55
|
+
puts "📝 Configuration updated in #{SHAKAPACKER_CONFIG}"
|
|
56
|
+
|
|
57
|
+
manage_dependencies(bundler, install_deps, no_uninstall: no_uninstall)
|
|
58
|
+
|
|
59
|
+
puts ""
|
|
60
|
+
puts "🎯 Next steps:"
|
|
61
|
+
puts " 1. Restart your dev server: bin/dev"
|
|
62
|
+
puts " 2. Verify build works: bin/shakapacker"
|
|
63
|
+
puts ""
|
|
64
|
+
puts "💡 Tip: Both webpack and rspack can coexist in package.json during migration"
|
|
65
|
+
puts " Use --install-deps to automatically manage dependencies, or manage manually"
|
|
66
|
+
puts " Use --no-uninstall to skip removing old bundler packages (faster switching)"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def init_config
|
|
70
|
+
if File.exist?(custom_config_path)
|
|
71
|
+
puts "⚠️ #{CUSTOM_DEPS_CONFIG} already exists"
|
|
72
|
+
return
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
config = {
|
|
76
|
+
"rspack" => {
|
|
77
|
+
"devDependencies" => DEFAULT_RSPACK_DEPS[:dev],
|
|
78
|
+
"dependencies" => DEFAULT_RSPACK_DEPS[:prod]
|
|
79
|
+
},
|
|
80
|
+
"webpack" => {
|
|
81
|
+
"devDependencies" => DEFAULT_WEBPACK_DEPS[:dev],
|
|
82
|
+
"dependencies" => DEFAULT_WEBPACK_DEPS[:prod]
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
File.write(custom_config_path, YAML.dump(config))
|
|
87
|
+
puts "✅ Created #{CUSTOM_DEPS_CONFIG}"
|
|
88
|
+
puts ""
|
|
89
|
+
puts "You can now customize the dependencies for each bundler in this file."
|
|
90
|
+
puts "The script will automatically use these custom dependencies when switching bundlers."
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def show_usage
|
|
94
|
+
current = current_bundler
|
|
95
|
+
puts "Current bundler: #{current}"
|
|
96
|
+
puts ""
|
|
97
|
+
puts "Usage:"
|
|
98
|
+
puts " rails shakapacker:switch_bundler [webpack|rspack] [OPTIONS]"
|
|
99
|
+
puts " rake shakapacker:switch_bundler [webpack|rspack] -- [OPTIONS]"
|
|
100
|
+
puts ""
|
|
101
|
+
puts "Options:"
|
|
102
|
+
puts " --install-deps Automatically install/uninstall dependencies"
|
|
103
|
+
puts " --no-uninstall Skip uninstalling old bundler packages (faster, keeps both bundlers)"
|
|
104
|
+
puts " --init-config Create #{CUSTOM_DEPS_CONFIG} with default dependencies"
|
|
105
|
+
puts " --help, -h Show this help message"
|
|
106
|
+
puts ""
|
|
107
|
+
puts "Examples:"
|
|
108
|
+
puts " # Using rails command"
|
|
109
|
+
puts " rails shakapacker:switch_bundler rspack --install-deps"
|
|
110
|
+
puts " rails shakapacker:switch_bundler webpack --install-deps --no-uninstall"
|
|
111
|
+
puts " rails shakapacker:switch_bundler --init-config"
|
|
112
|
+
puts ""
|
|
113
|
+
puts " # Using rake command (note the -- separator)"
|
|
114
|
+
puts " rake shakapacker:switch_bundler rspack -- --install-deps"
|
|
115
|
+
puts " rake shakapacker:switch_bundler webpack -- --install-deps --no-uninstall"
|
|
116
|
+
puts " rake shakapacker:switch_bundler -- --init-config"
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def config_path
|
|
122
|
+
root_path.join(SHAKAPACKER_CONFIG)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def custom_config_path
|
|
126
|
+
root_path.join(CUSTOM_DEPS_CONFIG)
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def load_dependencies
|
|
130
|
+
if File.exist?(custom_config_path)
|
|
131
|
+
puts "📝 Using custom dependencies from #{CUSTOM_DEPS_CONFIG}"
|
|
132
|
+
begin
|
|
133
|
+
custom = load_yaml_config(custom_config_path)
|
|
134
|
+
rescue Psych::SyntaxError => e
|
|
135
|
+
puts "❌ Error parsing #{CUSTOM_DEPS_CONFIG}: #{e.message}"
|
|
136
|
+
puts " Please fix the YAML syntax or delete the file to use defaults"
|
|
137
|
+
raise
|
|
138
|
+
end
|
|
139
|
+
rspack_deps = {
|
|
140
|
+
dev: custom.dig("rspack", "devDependencies") || DEFAULT_RSPACK_DEPS[:dev],
|
|
141
|
+
prod: custom.dig("rspack", "dependencies") || DEFAULT_RSPACK_DEPS[:prod]
|
|
142
|
+
}
|
|
143
|
+
webpack_deps = {
|
|
144
|
+
dev: custom.dig("webpack", "devDependencies") || DEFAULT_WEBPACK_DEPS[:dev],
|
|
145
|
+
prod: custom.dig("webpack", "dependencies") || DEFAULT_WEBPACK_DEPS[:prod]
|
|
146
|
+
}
|
|
147
|
+
[rspack_deps, webpack_deps]
|
|
148
|
+
else
|
|
149
|
+
[DEFAULT_RSPACK_DEPS, DEFAULT_WEBPACK_DEPS]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def update_config(bundler)
|
|
154
|
+
content = File.read(config_path)
|
|
155
|
+
|
|
156
|
+
# Replace assets_bundler value (handles spaces, tabs, and various quote styles)
|
|
157
|
+
# Only matches uncommented lines
|
|
158
|
+
content.gsub!(/^([ \t]*assets_bundler:[ \t]*['"]?)(webpack|rspack)(['"]?)/, "\\1#{bundler}\\3")
|
|
159
|
+
|
|
160
|
+
# Update javascript_transpiler recommendation for rspack
|
|
161
|
+
# Only update if not already set to swc and only on uncommented lines
|
|
162
|
+
if bundler == "rspack" && content !~ /^[ \t]*javascript_transpiler:[ \t]*['"]?swc['"]?/
|
|
163
|
+
content.gsub!(/^([ \t]*javascript_transpiler:[ \t]*['"]?)\w+(['"]?)/, "\\1swc\\2")
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
File.write(config_path, content)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
def manage_dependencies(bundler, install_deps, switching: true, no_uninstall: false)
|
|
170
|
+
rspack_deps, webpack_deps = load_dependencies
|
|
171
|
+
deps_to_install = bundler == "rspack" ? rspack_deps : webpack_deps
|
|
172
|
+
deps_to_remove = bundler == "rspack" ? webpack_deps : rspack_deps
|
|
173
|
+
|
|
174
|
+
if install_deps
|
|
175
|
+
puts ""
|
|
176
|
+
puts "📦 Managing dependencies..."
|
|
177
|
+
puts ""
|
|
178
|
+
|
|
179
|
+
# Show what will be removed (only when switching and not no_uninstall)
|
|
180
|
+
if switching && !no_uninstall && (!deps_to_remove[:dev].empty? || !deps_to_remove[:prod].empty?)
|
|
181
|
+
puts " 🗑️ Removing:"
|
|
182
|
+
deps_to_remove[:dev].each { |dep| puts " - #{dep} (dev)" }
|
|
183
|
+
deps_to_remove[:prod].each { |dep| puts " - #{dep} (prod)" }
|
|
184
|
+
puts ""
|
|
185
|
+
elsif switching && no_uninstall
|
|
186
|
+
puts " ⏭️ Skipping uninstall (--no-uninstall)"
|
|
187
|
+
puts ""
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Show what will be installed
|
|
191
|
+
if !deps_to_install[:dev].empty? || !deps_to_install[:prod].empty?
|
|
192
|
+
puts " 📦 Installing:"
|
|
193
|
+
deps_to_install[:dev].each { |dep| puts " - #{dep} (dev)" }
|
|
194
|
+
deps_to_install[:prod].each { |dep| puts " - #{dep} (prod)" }
|
|
195
|
+
puts ""
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Remove old bundler dependencies (only when switching and not no_uninstall)
|
|
199
|
+
if switching && !no_uninstall
|
|
200
|
+
remove_dependencies(deps_to_remove)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
# Install new bundler dependencies
|
|
204
|
+
install_dependencies(deps_to_install)
|
|
205
|
+
|
|
206
|
+
puts " ✅ Dependencies updated"
|
|
207
|
+
else
|
|
208
|
+
print_manual_dependency_instructions(bundler, deps_to_install, deps_to_remove)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def remove_dependencies(deps)
|
|
213
|
+
package_json = get_package_json
|
|
214
|
+
|
|
215
|
+
unless deps[:dev].empty?
|
|
216
|
+
unless package_json.manager.remove(deps[:dev])
|
|
217
|
+
puts " ⚠️ Warning: Failed to uninstall some dev dependencies"
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
unless deps[:prod].empty?
|
|
222
|
+
unless package_json.manager.remove(deps[:prod])
|
|
223
|
+
puts " ⚠️ Warning: Failed to uninstall some prod dependencies"
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
def install_dependencies(deps)
|
|
229
|
+
package_json = get_package_json
|
|
230
|
+
|
|
231
|
+
unless deps[:dev].empty?
|
|
232
|
+
unless package_json.manager.add(deps[:dev], type: :dev)
|
|
233
|
+
puts "❌ Failed to install dev dependencies"
|
|
234
|
+
raise "Failed to install dev dependencies"
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
unless deps[:prod].empty?
|
|
239
|
+
unless package_json.manager.add(deps[:prod], type: :production)
|
|
240
|
+
puts "❌ Failed to install prod dependencies"
|
|
241
|
+
raise "Failed to install prod dependencies"
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
# Run a full install to ensure optional dependencies (like native bindings) are properly resolved
|
|
246
|
+
# This is especially important for packages like @rspack/core that use platform-specific native modules
|
|
247
|
+
unless package_json.manager.install
|
|
248
|
+
puts "❌ Failed to run full install to resolve optional dependencies"
|
|
249
|
+
raise "Failed to run full install"
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def get_package_json
|
|
254
|
+
require "package_json"
|
|
255
|
+
PackageJson.read(root_path)
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def print_manual_dependency_instructions(bundler, deps_to_install, deps_to_remove)
|
|
259
|
+
puts ""
|
|
260
|
+
puts "⚠️ Dependencies not automatically installed (use --install-deps to auto-install)"
|
|
261
|
+
puts ""
|
|
262
|
+
|
|
263
|
+
package_manager = detect_package_manager
|
|
264
|
+
target_name = bundler == "rspack" ? "rspack" : "webpack"
|
|
265
|
+
old_name = bundler == "rspack" ? "webpack" : "rspack"
|
|
266
|
+
|
|
267
|
+
puts "📦 To install #{target_name} dependencies, run:"
|
|
268
|
+
print_install_commands(package_manager, deps_to_install)
|
|
269
|
+
puts ""
|
|
270
|
+
puts "🗑️ To remove #{old_name} dependencies, run:"
|
|
271
|
+
print_uninstall_commands(package_manager, deps_to_remove)
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
def detect_package_manager
|
|
275
|
+
get_package_json.manager.binary
|
|
276
|
+
rescue StandardError
|
|
277
|
+
"npm" # Fallback to npm if detection fails
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
def print_install_commands(package_manager, deps)
|
|
281
|
+
case package_manager
|
|
282
|
+
when "yarn"
|
|
283
|
+
puts " yarn add --dev #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
284
|
+
puts " yarn add #{deps[:prod].join(' ')}" unless deps[:prod].empty?
|
|
285
|
+
when "pnpm"
|
|
286
|
+
puts " pnpm add -D #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
287
|
+
puts " pnpm add #{deps[:prod].join(' ')}" unless deps[:prod].empty?
|
|
288
|
+
when "bun"
|
|
289
|
+
puts " bun add --dev #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
290
|
+
puts " bun add #{deps[:prod].join(' ')}" unless deps[:prod].empty?
|
|
291
|
+
else # npm
|
|
292
|
+
puts " npm install --save-dev #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
293
|
+
puts " npm install --save #{deps[:prod].join(' ')}" unless deps[:prod].empty?
|
|
294
|
+
end
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def print_uninstall_commands(package_manager, deps)
|
|
298
|
+
case package_manager
|
|
299
|
+
when "yarn"
|
|
300
|
+
puts " yarn remove #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
301
|
+
puts " yarn remove #{deps[:prod].join(' ')}" unless deps[:prod].empty?
|
|
302
|
+
when "pnpm"
|
|
303
|
+
puts " pnpm remove #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
304
|
+
puts " pnpm remove #{deps[:prod].join(' ')}" unless deps[:prod].empty?
|
|
305
|
+
when "bun"
|
|
306
|
+
puts " bun remove #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
307
|
+
puts " bun remove #{deps[:prod].join(' ')}" unless deps[:prod].empty?
|
|
308
|
+
else # npm
|
|
309
|
+
puts " npm uninstall #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
310
|
+
puts " npm uninstall #{deps[:prod].join(' ')}" unless deps[:prod].empty?
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
# Load YAML config file with Ruby version compatibility
|
|
315
|
+
# Ruby 3.1+ supports aliases: keyword, older versions need YAML.safe_load
|
|
316
|
+
def load_yaml_config(path)
|
|
317
|
+
if YAML.respond_to?(:unsafe_load_file)
|
|
318
|
+
# Ruby 3.1+: Use unsafe_load_file to support aliases/anchors
|
|
319
|
+
YAML.unsafe_load_file(path)
|
|
320
|
+
else
|
|
321
|
+
# Ruby 2.7-3.0: Use safe_load with aliases enabled
|
|
322
|
+
YAML.safe_load(File.read(path), permitted_classes: [], permitted_symbols: [], aliases: true)
|
|
323
|
+
end
|
|
324
|
+
rescue ArgumentError
|
|
325
|
+
# Ruby 2.7 doesn't support aliases keyword - fall back to YAML.load
|
|
326
|
+
YAML.load(File.read(path)) # rubocop:disable Security/YAMLLoad
|
|
327
|
+
end
|
|
328
|
+
end
|
|
329
|
+
end
|