shakapacker 10.1.0 → 10.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/CHANGELOG.md +29 -1
- data/README.md +116 -1368
- data/lib/install/bin/diff-bundler-config +89 -18
- data/lib/install/bin/shakapacker-config +89 -18
- data/lib/install/config/shakapacker.yml +7 -1
- data/lib/install/package.json +5 -3
- data/lib/install/template.rb +91 -12
- data/lib/shakapacker/base_strategy.rb +10 -4
- data/lib/shakapacker/bundler_switcher.rb +22 -6
- data/lib/shakapacker/compiler.rb +38 -8
- data/lib/shakapacker/compiler_strategy.rb +4 -4
- data/lib/shakapacker/configuration.rb +57 -0
- data/lib/shakapacker/dev_server_runner.rb +44 -19
- data/lib/shakapacker/digest_strategy.rb +3 -3
- data/lib/shakapacker/doctor.rb +446 -76
- data/lib/shakapacker/helper.rb +3 -3
- data/lib/shakapacker/install/env.rb +50 -0
- data/lib/shakapacker/instance.rb +1 -1
- data/lib/shakapacker/mtime_strategy.rb +1 -1
- data/lib/shakapacker/runner.rb +93 -55
- data/lib/shakapacker/utils/misc.rb +38 -0
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/version_checker.rb +16 -4
- data/lib/tasks/shakapacker/export_bundler_config.rake +30 -21
- data/lib/tasks/shakapacker/install.rake +5 -3
- data/lib/tasks/shakapacker.rake +1 -1
- data/package.json +15 -11
- data/sig/shakapacker/configuration.rbs +3 -0
- metadata +3 -3
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "rbconfig"
|
|
5
|
+
require "yaml"
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
-
# - lib/install/bin/shakapacker-config
|
|
8
|
-
# - lib/install/bin/diff-bundler-config
|
|
9
|
-
# - spec/dummy/bin/shakapacker-config
|
|
10
|
-
# - package/configExporter/cli.ts (createBinStub).
|
|
7
|
+
# This binstub is managed by Shakapacker. Delete it and rerun the install or init command to regenerate it.
|
|
11
8
|
def shakapacker_app_root
|
|
12
9
|
candidate = File.expand_path("..", __dir__)
|
|
13
10
|
return candidate if File.exist?(File.join(candidate, "Gemfile"))
|
|
@@ -28,9 +25,11 @@ def shakapacker_executable_candidates(executable)
|
|
|
28
25
|
end
|
|
29
26
|
|
|
30
27
|
def shakapacker_find_executable(executable)
|
|
31
|
-
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |path|
|
|
28
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR, -1).each do |path|
|
|
29
|
+
search_path = path.empty? ? Dir.pwd : path
|
|
30
|
+
|
|
32
31
|
shakapacker_executable_candidates(executable).each do |candidate|
|
|
33
|
-
executable_path = File.join(
|
|
32
|
+
executable_path = File.join(search_path, candidate)
|
|
34
33
|
return executable_path if File.file?(executable_path) && File.executable?(executable_path)
|
|
35
34
|
end
|
|
36
35
|
end
|
|
@@ -51,22 +50,94 @@ def shakapacker_node_env
|
|
|
51
50
|
%w[development test].include?(ENV["RAILS_ENV"]) ? "development" : "production"
|
|
52
51
|
end
|
|
53
52
|
|
|
53
|
+
def shakapacker_load_yaml_file(path)
|
|
54
|
+
YAML.load_file(path, aliases: true)
|
|
55
|
+
rescue ArgumentError
|
|
56
|
+
YAML.load_file(path)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def shakapacker_source_path(app_root)
|
|
60
|
+
config_path = ENV["SHAKAPACKER_CONFIG"] || File.join("config", "shakapacker.yml")
|
|
61
|
+
config_path = File.expand_path(config_path, app_root)
|
|
62
|
+
return nil unless File.file?(config_path)
|
|
63
|
+
|
|
64
|
+
config = shakapacker_load_yaml_file(config_path)
|
|
65
|
+
return nil unless config.is_a?(Hash)
|
|
66
|
+
|
|
67
|
+
env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
68
|
+
env_config = config[env] || config["production"]
|
|
69
|
+
return nil unless env_config.is_a?(Hash)
|
|
70
|
+
|
|
71
|
+
source_path = env_config["source_path"]
|
|
72
|
+
source_path if source_path.is_a?(String)
|
|
73
|
+
rescue Psych::Exception, SystemCallError, ArgumentError
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def shakapacker_path_within?(path, parent)
|
|
78
|
+
path == parent || path.start_with?("#{parent}#{File::SEPARATOR}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def shakapacker_package_root_marker?(path)
|
|
82
|
+
%w[
|
|
83
|
+
package.json
|
|
84
|
+
bun.lockb
|
|
85
|
+
pnpm-lock.yaml
|
|
86
|
+
yarn.lock
|
|
87
|
+
package-lock.json
|
|
88
|
+
node_modules
|
|
89
|
+
].any? { |entry| File.exist?(File.join(path, entry)) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def shakapacker_client_root(app_root)
|
|
93
|
+
source_path = shakapacker_source_path(app_root)
|
|
94
|
+
return app_root unless source_path && !source_path.empty?
|
|
95
|
+
|
|
96
|
+
app_root = File.expand_path(app_root)
|
|
97
|
+
current = File.expand_path(source_path, app_root)
|
|
98
|
+
return app_root unless shakapacker_path_within?(current, app_root)
|
|
99
|
+
|
|
100
|
+
loop do
|
|
101
|
+
return current if shakapacker_package_root_marker?(current)
|
|
102
|
+
break if current == app_root
|
|
103
|
+
|
|
104
|
+
parent = File.dirname(current)
|
|
105
|
+
break if parent == current
|
|
106
|
+
|
|
107
|
+
current = parent
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
app_root
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def shakapacker_package_script_path(package_root, package_script)
|
|
114
|
+
File.join(
|
|
115
|
+
package_root,
|
|
116
|
+
"node_modules",
|
|
117
|
+
"shakapacker",
|
|
118
|
+
"package",
|
|
119
|
+
"bin",
|
|
120
|
+
package_script
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def shakapacker_package_script_paths(app_root, client_root, package_script)
|
|
125
|
+
[client_root, app_root].uniq.map do |package_root|
|
|
126
|
+
shakapacker_package_script_path(package_root, package_script)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
54
130
|
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
|
|
55
131
|
ENV["NODE_ENV"] ||= shakapacker_node_env
|
|
56
132
|
|
|
57
133
|
app_root = shakapacker_app_root
|
|
134
|
+
client_root = shakapacker_client_root(app_root)
|
|
58
135
|
node_bin = shakapacker_node_binary
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"package"
|
|
64
|
-
"bin",
|
|
65
|
-
"diff-bundler-config.cjs"
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
unless File.file?(script_path)
|
|
69
|
-
warn "[Shakapacker] Could not find #{script_path}. Run your package manager install command and try again."
|
|
136
|
+
script_paths = shakapacker_package_script_paths(app_root, client_root, "diff-bundler-config.cjs")
|
|
137
|
+
script_path = script_paths.find { |path| File.file?(path) }
|
|
138
|
+
|
|
139
|
+
unless script_path
|
|
140
|
+
warn "[Shakapacker] Could not find #{script_paths.join(' or ')}. Run your package manager install command and try again."
|
|
70
141
|
exit 1
|
|
71
142
|
end
|
|
72
143
|
|
|
@@ -2,12 +2,9 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "rbconfig"
|
|
5
|
+
require "yaml"
|
|
5
6
|
|
|
6
|
-
#
|
|
7
|
-
# - lib/install/bin/shakapacker-config
|
|
8
|
-
# - lib/install/bin/diff-bundler-config
|
|
9
|
-
# - spec/dummy/bin/shakapacker-config
|
|
10
|
-
# - package/configExporter/cli.ts (createBinStub).
|
|
7
|
+
# This binstub is managed by Shakapacker. Delete it and rerun the install or init command to regenerate it.
|
|
11
8
|
def shakapacker_app_root
|
|
12
9
|
candidate = File.expand_path("..", __dir__)
|
|
13
10
|
return candidate if File.exist?(File.join(candidate, "Gemfile"))
|
|
@@ -28,9 +25,11 @@ def shakapacker_executable_candidates(executable)
|
|
|
28
25
|
end
|
|
29
26
|
|
|
30
27
|
def shakapacker_find_executable(executable)
|
|
31
|
-
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |path|
|
|
28
|
+
ENV.fetch("PATH", "").split(File::PATH_SEPARATOR, -1).each do |path|
|
|
29
|
+
search_path = path.empty? ? Dir.pwd : path
|
|
30
|
+
|
|
32
31
|
shakapacker_executable_candidates(executable).each do |candidate|
|
|
33
|
-
executable_path = File.join(
|
|
32
|
+
executable_path = File.join(search_path, candidate)
|
|
34
33
|
return executable_path if File.file?(executable_path) && File.executable?(executable_path)
|
|
35
34
|
end
|
|
36
35
|
end
|
|
@@ -51,22 +50,94 @@ def shakapacker_node_env
|
|
|
51
50
|
%w[development test].include?(ENV["RAILS_ENV"]) ? "development" : "production"
|
|
52
51
|
end
|
|
53
52
|
|
|
53
|
+
def shakapacker_load_yaml_file(path)
|
|
54
|
+
YAML.load_file(path, aliases: true)
|
|
55
|
+
rescue ArgumentError
|
|
56
|
+
YAML.load_file(path)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def shakapacker_source_path(app_root)
|
|
60
|
+
config_path = ENV["SHAKAPACKER_CONFIG"] || File.join("config", "shakapacker.yml")
|
|
61
|
+
config_path = File.expand_path(config_path, app_root)
|
|
62
|
+
return nil unless File.file?(config_path)
|
|
63
|
+
|
|
64
|
+
config = shakapacker_load_yaml_file(config_path)
|
|
65
|
+
return nil unless config.is_a?(Hash)
|
|
66
|
+
|
|
67
|
+
env = ENV["RAILS_ENV"] || ENV["RACK_ENV"] || "development"
|
|
68
|
+
env_config = config[env] || config["production"]
|
|
69
|
+
return nil unless env_config.is_a?(Hash)
|
|
70
|
+
|
|
71
|
+
source_path = env_config["source_path"]
|
|
72
|
+
source_path if source_path.is_a?(String)
|
|
73
|
+
rescue Psych::Exception, SystemCallError, ArgumentError
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def shakapacker_path_within?(path, parent)
|
|
78
|
+
path == parent || path.start_with?("#{parent}#{File::SEPARATOR}")
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def shakapacker_package_root_marker?(path)
|
|
82
|
+
%w[
|
|
83
|
+
package.json
|
|
84
|
+
bun.lockb
|
|
85
|
+
pnpm-lock.yaml
|
|
86
|
+
yarn.lock
|
|
87
|
+
package-lock.json
|
|
88
|
+
node_modules
|
|
89
|
+
].any? { |entry| File.exist?(File.join(path, entry)) }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def shakapacker_client_root(app_root)
|
|
93
|
+
source_path = shakapacker_source_path(app_root)
|
|
94
|
+
return app_root unless source_path && !source_path.empty?
|
|
95
|
+
|
|
96
|
+
app_root = File.expand_path(app_root)
|
|
97
|
+
current = File.expand_path(source_path, app_root)
|
|
98
|
+
return app_root unless shakapacker_path_within?(current, app_root)
|
|
99
|
+
|
|
100
|
+
loop do
|
|
101
|
+
return current if shakapacker_package_root_marker?(current)
|
|
102
|
+
break if current == app_root
|
|
103
|
+
|
|
104
|
+
parent = File.dirname(current)
|
|
105
|
+
break if parent == current
|
|
106
|
+
|
|
107
|
+
current = parent
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
app_root
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def shakapacker_package_script_path(package_root, package_script)
|
|
114
|
+
File.join(
|
|
115
|
+
package_root,
|
|
116
|
+
"node_modules",
|
|
117
|
+
"shakapacker",
|
|
118
|
+
"package",
|
|
119
|
+
"bin",
|
|
120
|
+
package_script
|
|
121
|
+
)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def shakapacker_package_script_paths(app_root, client_root, package_script)
|
|
125
|
+
[client_root, app_root].uniq.map do |package_root|
|
|
126
|
+
shakapacker_package_script_path(package_root, package_script)
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
54
130
|
ENV["RAILS_ENV"] ||= ENV["RACK_ENV"] || "development"
|
|
55
131
|
ENV["NODE_ENV"] ||= shakapacker_node_env
|
|
56
132
|
|
|
57
133
|
app_root = shakapacker_app_root
|
|
134
|
+
client_root = shakapacker_client_root(app_root)
|
|
58
135
|
node_bin = shakapacker_node_binary
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
"package"
|
|
64
|
-
"bin",
|
|
65
|
-
"shakapacker-config.cjs"
|
|
66
|
-
)
|
|
67
|
-
|
|
68
|
-
unless File.file?(script_path)
|
|
69
|
-
warn "[Shakapacker] Could not find #{script_path}. Run your package manager install command and try again."
|
|
136
|
+
script_paths = shakapacker_package_script_paths(app_root, client_root, "shakapacker-config.cjs")
|
|
137
|
+
script_path = script_paths.find { |path| File.file?(path) }
|
|
138
|
+
|
|
139
|
+
unless script_path
|
|
140
|
+
warn "[Shakapacker] Could not find #{script_paths.join(' or ')}. Run your package manager install command and try again."
|
|
70
141
|
exit 1
|
|
71
142
|
end
|
|
72
143
|
|
|
@@ -35,6 +35,12 @@ default: &default
|
|
|
35
35
|
public_output_path: packs
|
|
36
36
|
cache_path: tmp/shakapacker
|
|
37
37
|
webpack_compile_output: true
|
|
38
|
+
|
|
39
|
+
# Extra command-line flags passed to webpack/rspack when compiling.
|
|
40
|
+
# Do not include "--", Shakapacker wrapper flags, help/version flags, watch flags/forms, or managed flags.
|
|
41
|
+
# Example: ["--progress", "--fail-on-warnings"]
|
|
42
|
+
webpack_compile_flags: []
|
|
43
|
+
|
|
38
44
|
# See https://github.com/shakacode/shakapacker#deployment
|
|
39
45
|
shakapacker_precompile: true
|
|
40
46
|
|
|
@@ -59,7 +65,7 @@ default: &default
|
|
|
59
65
|
javascript_transpiler: "swc"
|
|
60
66
|
|
|
61
67
|
# Select assets bundler to use
|
|
62
|
-
# Available options: 'webpack'
|
|
68
|
+
# Available options: 'webpack' or 'rspack'
|
|
63
69
|
assets_bundler: "webpack"
|
|
64
70
|
|
|
65
71
|
# Path to the directory containing webpack/rspack config files
|
data/lib/install/package.json
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"rspack": {
|
|
3
|
-
"@rspack/cli": "^
|
|
4
|
-
"@rspack/core": "^
|
|
5
|
-
"rspack-
|
|
3
|
+
"@rspack/cli": "^2.0.0",
|
|
4
|
+
"@rspack/core": "^2.0.0",
|
|
5
|
+
"@rspack/dev-server": "^2.0.0",
|
|
6
|
+
"rspack-manifest-plugin": "^5.2.2"
|
|
6
7
|
},
|
|
7
8
|
"webpack": {
|
|
8
9
|
"mini-css-extract-plugin": "^2.0.0",
|
|
@@ -17,6 +18,7 @@
|
|
|
17
18
|
"common": {
|
|
18
19
|
"compression-webpack-plugin": "^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0",
|
|
19
20
|
"css-loader": "^6.0.0 || ^7.0.0",
|
|
21
|
+
"sass": "^1.50.0",
|
|
20
22
|
"sass-loader": "^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0",
|
|
21
23
|
"style-loader": "^3.0.0 || ^4.0.0"
|
|
22
24
|
},
|
data/lib/install/template.rb
CHANGED
|
@@ -15,6 +15,31 @@ require "json"
|
|
|
15
15
|
@package_json ||= PackageJson.new
|
|
16
16
|
install_dir = File.expand_path(File.dirname(__FILE__))
|
|
17
17
|
|
|
18
|
+
# Read the existing app's bundler (if any) before copy_file can overwrite it, so a
|
|
19
|
+
# re-install can default to that bundler instead of silently switching it.
|
|
20
|
+
config_path = Rails.root.join("config/shakapacker.yml")
|
|
21
|
+
shakapacker_config_preexisting = config_path.exist?
|
|
22
|
+
existing_assets_bundler =
|
|
23
|
+
File.read(config_path)[/assets_bundler:\s*"([^"]+)"/, 1] if shakapacker_config_preexisting
|
|
24
|
+
|
|
25
|
+
# New installs default to rspack; an existing app keeps its current bundler on a
|
|
26
|
+
# re-install unless overridden by the env var/argument or a FORCE overwrite (see
|
|
27
|
+
# Env.resolve_assets_bundler for the precedence rules). The bundled shakapacker.yml
|
|
28
|
+
# ships "webpack" for backward compatibility and is rewritten below when the chosen
|
|
29
|
+
# bundler differs.
|
|
30
|
+
assets_bundler = Shakapacker::Install::Env.resolve_assets_bundler(
|
|
31
|
+
env_value: ENV["SHAKAPACKER_ASSETS_BUNDLER"],
|
|
32
|
+
existing_bundler: existing_assets_bundler,
|
|
33
|
+
force: @conflict_option[:force]
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
# Fail fast on a misspelled SHAKAPACKER_ASSETS_BUNDLER instead of failing later
|
|
37
|
+
# with a confusing missing-config-directory or peer-lookup error.
|
|
38
|
+
unless Shakapacker::Install::Env::VALID_BUNDLERS.include?(assets_bundler)
|
|
39
|
+
say "❌ Unknown bundler '#{assets_bundler}'. Set SHAKAPACKER_ASSETS_BUNDLER to one of: #{Shakapacker::Install::Env::VALID_BUNDLERS.join(", ")}.", :red
|
|
40
|
+
exit 1
|
|
41
|
+
end
|
|
42
|
+
|
|
18
43
|
# Installation strategy:
|
|
19
44
|
# - USE_BABEL_PACKAGES installs both babel AND swc for compatibility
|
|
20
45
|
# - Otherwise install only the specified transpiler
|
|
@@ -35,7 +60,6 @@ else
|
|
|
35
60
|
end
|
|
36
61
|
|
|
37
62
|
# Copy config file
|
|
38
|
-
shakapacker_config_preexisting = Rails.root.join("config/shakapacker.yml").exist?
|
|
39
63
|
copy_file "#{install_dir}/config/shakapacker.yml", "config/shakapacker.yml", @conflict_option
|
|
40
64
|
|
|
41
65
|
# Update config to match the selected transpiler
|
|
@@ -46,14 +70,59 @@ if Shakapacker::Install::Env.update_transpiler_config?(
|
|
|
46
70
|
config_preexisting: shakapacker_config_preexisting
|
|
47
71
|
)
|
|
48
72
|
gsub_file "config/shakapacker.yml", 'javascript_transpiler: "swc"', "javascript_transpiler: \"#{@transpiler_to_install}\""
|
|
49
|
-
|
|
73
|
+
# Unlike the bundler rewrite below (which only runs on installer-owned files and
|
|
74
|
+
# aborts on a miss), this can also run against a pre-existing user config whose
|
|
75
|
+
# transpiler line may legitimately differ from the shipped "swc" literal. A no-op
|
|
76
|
+
# there is not an error, so only claim success when the value actually landed.
|
|
77
|
+
if File.read(config_path).include?("javascript_transpiler: \"#{@transpiler_to_install}\"")
|
|
78
|
+
say " 📝 Updated config/shakapacker.yml to use #{@transpiler_to_install} transpiler", :green
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Update config to match the selected bundler (see update_assets_bundler_config?
|
|
83
|
+
# for when the installer rewrites the shipped "webpack" default vs. preserves an
|
|
84
|
+
# existing config).
|
|
85
|
+
if Shakapacker::Install::Env.update_assets_bundler_config?(
|
|
86
|
+
assets_bundler_to_install: assets_bundler,
|
|
87
|
+
conflict_option: @conflict_option,
|
|
88
|
+
config_preexisting: shakapacker_config_preexisting
|
|
89
|
+
)
|
|
90
|
+
gsub_file "config/shakapacker.yml", 'assets_bundler: "webpack"', "assets_bundler: \"#{assets_bundler}\""
|
|
91
|
+
# gsub_file silently no-ops if the shipped literal is ever reformatted, so verify
|
|
92
|
+
# the value landed. Abort rather than continue, since the installer is about to set
|
|
93
|
+
# up the chosen bundler's dependencies and config, and a mismatched bundler value
|
|
94
|
+
# would produce a broken install. This runs before any dependencies are installed.
|
|
95
|
+
if File.read(config_path).include?("assets_bundler: \"#{assets_bundler}\"")
|
|
96
|
+
say " 📝 Updated config/shakapacker.yml to use #{assets_bundler} bundler", :green
|
|
97
|
+
else
|
|
98
|
+
say "❌ Could not set assets_bundler to \"#{assets_bundler}\" in config/shakapacker.yml " \
|
|
99
|
+
"— the expected 'assets_bundler: \"webpack\"' line was not found. Aborting so the " \
|
|
100
|
+
"install doesn't proceed with a mismatched bundler config.", :red
|
|
101
|
+
exit 1
|
|
102
|
+
end
|
|
103
|
+
else
|
|
104
|
+
# The bundler config was left as-is (an existing app's config is preserved unless
|
|
105
|
+
# FORCE overwrites it). Report the value present, and warn if it differs from the
|
|
106
|
+
# bundler whose dependencies/config are being installed — usually when a bundler is
|
|
107
|
+
# requested explicitly (env var or task argument) against a preserved config, but
|
|
108
|
+
# also when a preserved config holds an unrecognized bundler and resolve_assets_bundler
|
|
109
|
+
# falls back to rspack. Either case would otherwise be a silent mismatch. Only act
|
|
110
|
+
# when we can read the value back — never guess, or the message could contradict the file.
|
|
111
|
+
retained_bundler = File.read(config_path)[/assets_bundler:\s*"([^"]+)"/, 1]
|
|
112
|
+
if retained_bundler && retained_bundler != assets_bundler
|
|
113
|
+
say "⚠️ Installing #{assets_bundler} dependencies, but config/shakapacker.yml keeps " \
|
|
114
|
+
"assets_bundler: \"#{retained_bundler}\". To switch an existing app's bundler, run " \
|
|
115
|
+
"`bin/rake shakapacker:switch_bundler #{assets_bundler} -- --install-deps`, " \
|
|
116
|
+
"or re-run the installer with FORCE=true to overwrite the config.", :yellow
|
|
117
|
+
elsif retained_bundler
|
|
118
|
+
say " 📝 Keeping assets_bundler: \"#{retained_bundler}\" in config/shakapacker.yml", :green
|
|
119
|
+
end
|
|
50
120
|
end
|
|
51
121
|
|
|
52
122
|
# Detect TypeScript usage
|
|
53
123
|
# Auto-detect from tsconfig.json or explicit via SHAKAPACKER_USE_TYPESCRIPT env var
|
|
54
124
|
@use_typescript = File.exist?(Rails.root.join("tsconfig.json")) ||
|
|
55
125
|
Shakapacker::Install::Env.truthy_env?("SHAKAPACKER_USE_TYPESCRIPT")
|
|
56
|
-
assets_bundler = ENV["SHAKAPACKER_ASSETS_BUNDLER"] || "webpack"
|
|
57
126
|
config_extension = @use_typescript ? "ts" : "js"
|
|
58
127
|
|
|
59
128
|
say "Copying #{assets_bundler} core config (#{config_extension.upcase})"
|
|
@@ -224,9 +293,12 @@ Dir.chdir(Rails.root) do
|
|
|
224
293
|
end
|
|
225
294
|
|
|
226
295
|
# Inline fetch_peer_dependencies and fetch_common_dependencies
|
|
227
|
-
peers = PackageJson.read(install_dir).fetch(
|
|
296
|
+
peers = PackageJson.read(install_dir).fetch(assets_bundler)
|
|
228
297
|
common_deps = Shakapacker::Install::Env.truthy_env?("SKIP_COMMON_LOADERS") ? {} : PackageJson.read(install_dir).fetch("common")
|
|
229
|
-
peers =
|
|
298
|
+
peers = common_deps.merge(peers)
|
|
299
|
+
if assets_bundler == "rspack" && common_deps.key?("css-loader")
|
|
300
|
+
peers["css-loader"] = "^7.1.4"
|
|
301
|
+
end
|
|
230
302
|
|
|
231
303
|
# Add transpiler-specific dependencies based on detected/configured transpiler
|
|
232
304
|
# Inline the logic here since methods can't be called before they're defined in Rails templates
|
|
@@ -255,7 +327,9 @@ Dir.chdir(Rails.root) do
|
|
|
255
327
|
peers = peers.merge(esbuild_deps)
|
|
256
328
|
end
|
|
257
329
|
|
|
258
|
-
|
|
330
|
+
# Lists both dev servers for classification only; just the one matching the chosen
|
|
331
|
+
# bundler appears in `peers`, so only that package is actually installed.
|
|
332
|
+
dev_dependency_packages = ["webpack-dev-server", "@rspack/dev-server"]
|
|
259
333
|
|
|
260
334
|
dependencies_to_add = []
|
|
261
335
|
dev_dependencies_to_add = []
|
|
@@ -290,12 +364,17 @@ Dir.chdir(Rails.root) do
|
|
|
290
364
|
exit 1
|
|
291
365
|
end
|
|
292
366
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
say "
|
|
298
|
-
|
|
367
|
+
if dev_dependencies_to_add.any?
|
|
368
|
+
# Strip the trailing @version, keeping the package name; the regex drops only
|
|
369
|
+
# the last @-segment so scoped names (e.g. @rspack/dev-server) survive.
|
|
370
|
+
dev_dependency_names = dev_dependencies_to_add.map { |entry| entry.sub(/@[^@]+\z/, "") }
|
|
371
|
+
say "Installing development dependencies: #{dev_dependency_names.join(", ")}"
|
|
372
|
+
begin
|
|
373
|
+
@package_json.manager.add!(dev_dependencies_to_add, type: :dev)
|
|
374
|
+
rescue PackageJson::Error
|
|
375
|
+
say "Shakapacker installation failed 😭 See above for details.", :red
|
|
376
|
+
exit 1
|
|
377
|
+
end
|
|
299
378
|
end
|
|
300
379
|
|
|
301
380
|
# Configure babel preset in package.json if babel is being used
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
module Shakapacker
|
|
2
2
|
class BaseStrategy
|
|
3
|
-
def initialize
|
|
4
|
-
@
|
|
3
|
+
def initialize(instance = Shakapacker.instance)
|
|
4
|
+
@instance = instance
|
|
5
5
|
end
|
|
6
6
|
|
|
7
7
|
def after_compile_hook
|
|
@@ -10,7 +10,13 @@ module Shakapacker
|
|
|
10
10
|
|
|
11
11
|
private
|
|
12
12
|
|
|
13
|
-
|
|
13
|
+
def config
|
|
14
|
+
@instance.config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def env
|
|
18
|
+
@instance.env
|
|
19
|
+
end
|
|
14
20
|
|
|
15
21
|
def default_watched_paths
|
|
16
22
|
[
|
|
@@ -18,7 +24,7 @@ module Shakapacker
|
|
|
18
24
|
"#{config.source_path}{,/**/*}",
|
|
19
25
|
"package.json", "package-lock.json", "yarn.lock",
|
|
20
26
|
"pnpm-lock.yaml", "bun.lockb",
|
|
21
|
-
"config/webpack{,/**/*}"
|
|
27
|
+
"config/{webpack,rspack}{,/**/*}"
|
|
22
28
|
].freeze
|
|
23
29
|
end
|
|
24
30
|
end
|
|
@@ -19,12 +19,13 @@ module Shakapacker
|
|
|
19
19
|
prod: %w[webpack-merge]
|
|
20
20
|
}.freeze
|
|
21
21
|
|
|
22
|
-
# Default dependencies for each bundler
|
|
22
|
+
# Default dependencies for each bundler. Rspack entries include install-time
|
|
23
|
+
# version ranges; package_names strips them before removal and display.
|
|
23
24
|
# Note: Excludes independent/optional dependencies like @swc/core, swc-loader (user-configured
|
|
24
25
|
# transpilers)
|
|
25
26
|
DEFAULT_RSPACK_DEPS = {
|
|
26
|
-
dev: %w[@rspack/cli @rspack/plugin-react-refresh],
|
|
27
|
-
prod: %w[@rspack/core rspack-manifest-plugin]
|
|
27
|
+
dev: %w[@rspack/cli@^2.0.0 @rspack/dev-server@^2.0.0 @rspack/plugin-react-refresh@^2.0.0],
|
|
28
|
+
prod: %w[@rspack/core@^2.0.0 rspack-manifest-plugin@^5.2.2]
|
|
28
29
|
}.freeze
|
|
29
30
|
|
|
30
31
|
DEFAULT_WEBPACK_DEPS = {
|
|
@@ -267,8 +268,8 @@ module Shakapacker
|
|
|
267
268
|
# Show what will be removed (only when switching and not no_uninstall)
|
|
268
269
|
if switching && !no_uninstall && (!deps_to_remove[:dev].empty? || !deps_to_remove[:prod].empty?)
|
|
269
270
|
puts " 🗑️ Removing:"
|
|
270
|
-
deps_to_remove[:dev].each { |dep| puts " - #{dep} (dev)" }
|
|
271
|
-
deps_to_remove[:prod].each { |dep| puts " - #{dep} (prod)" }
|
|
271
|
+
package_names(deps_to_remove[:dev]).each { |dep| puts " - #{dep} (dev)" }
|
|
272
|
+
package_names(deps_to_remove[:prod]).each { |dep| puts " - #{dep} (prod)" }
|
|
272
273
|
puts ""
|
|
273
274
|
elsif switching && no_uninstall
|
|
274
275
|
puts " ⏭️ Skipping uninstall (--no-uninstall)"
|
|
@@ -302,7 +303,7 @@ module Shakapacker
|
|
|
302
303
|
|
|
303
304
|
# Combine dev and prod dependencies into a single list for removal
|
|
304
305
|
# Package managers remove packages from both dependencies and devDependencies sections if present
|
|
305
|
-
all_deps = deps[:dev] + deps[:prod]
|
|
306
|
+
all_deps = package_names(deps[:dev] + deps[:prod])
|
|
306
307
|
|
|
307
308
|
unless all_deps.empty?
|
|
308
309
|
unless package_json.manager.remove(all_deps)
|
|
@@ -381,6 +382,11 @@ module Shakapacker
|
|
|
381
382
|
end
|
|
382
383
|
|
|
383
384
|
def print_uninstall_commands(package_manager, deps)
|
|
385
|
+
deps = {
|
|
386
|
+
dev: package_names(deps[:dev]),
|
|
387
|
+
prod: package_names(deps[:prod])
|
|
388
|
+
}
|
|
389
|
+
|
|
384
390
|
case package_manager
|
|
385
391
|
when "yarn"
|
|
386
392
|
puts " yarn remove #{deps[:dev].join(' ')}" unless deps[:dev].empty?
|
|
@@ -397,6 +403,16 @@ module Shakapacker
|
|
|
397
403
|
end
|
|
398
404
|
end
|
|
399
405
|
|
|
406
|
+
def package_names(dependencies)
|
|
407
|
+
dependencies.map do |dependency|
|
|
408
|
+
if dependency.start_with?("@")
|
|
409
|
+
dependency.count("@") > 1 ? dependency.sub(/@[^@]+\z/, "") : dependency
|
|
410
|
+
else
|
|
411
|
+
dependency.sub(/@[^@]+\z/, "")
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
|
|
400
416
|
# Load YAML config file with Ruby version compatibility
|
|
401
417
|
# Ruby 3.1+ supports aliases: keyword, older versions need YAML.safe_load
|
|
402
418
|
def load_yaml_config(path)
|
data/lib/shakapacker/compiler.rb
CHANGED
|
@@ -5,6 +5,9 @@ require "shellwords"
|
|
|
5
5
|
require_relative "compiler_strategy"
|
|
6
6
|
|
|
7
7
|
class Shakapacker::Compiler
|
|
8
|
+
SpawnFailure = Class.new(StandardError)
|
|
9
|
+
private_constant :SpawnFailure
|
|
10
|
+
|
|
8
11
|
# Additional environment variables that the compiler is being run with
|
|
9
12
|
# Shakapacker::Compiler.env['FRONTEND_API_KEY'] = 'your_secret_key'
|
|
10
13
|
cattr_accessor(:env) { {} }
|
|
@@ -36,9 +39,16 @@ class Shakapacker::Compiler
|
|
|
36
39
|
else
|
|
37
40
|
acquire_ipc_lock do
|
|
38
41
|
run_precompile_hook if should_run_precompile_hook?
|
|
39
|
-
|
|
40
|
-
|
|
42
|
+
spawn_failed = false
|
|
43
|
+
begin
|
|
44
|
+
success = run_webpack
|
|
45
|
+
rescue SpawnFailure
|
|
46
|
+
spawn_failed = true
|
|
47
|
+
success = false
|
|
41
48
|
end
|
|
49
|
+
|
|
50
|
+
after_compile_hook unless spawn_failed
|
|
51
|
+
success
|
|
42
52
|
end
|
|
43
53
|
end
|
|
44
54
|
end
|
|
@@ -83,8 +93,9 @@ class Shakapacker::Compiler
|
|
|
83
93
|
config.root_path.join("tmp/shakapacker.lock")
|
|
84
94
|
end
|
|
85
95
|
|
|
96
|
+
# Returns one executable path for array-form Open3, not a shell command snippet.
|
|
86
97
|
def optional_ruby_runner
|
|
87
|
-
first_line = File.readlines(bin_shakapacker_path).first
|
|
98
|
+
first_line = File.readlines(bin_shakapacker_path).first&.chomp || ""
|
|
88
99
|
/ruby/.match?(first_line) ? RbConfig.ruby : ""
|
|
89
100
|
end
|
|
90
101
|
|
|
@@ -176,11 +187,21 @@ class Shakapacker::Compiler
|
|
|
176
187
|
def run_webpack
|
|
177
188
|
logger.info "Compiling..."
|
|
178
189
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
190
|
+
# Fetch compile flags before the spawn rescue so configuration errors stay explicit.
|
|
191
|
+
compile_flags = config.webpack_compile_flags
|
|
192
|
+
|
|
193
|
+
begin
|
|
194
|
+
command = shakapacker_command(compile_flags)
|
|
195
|
+
stdout, stderr, status = Open3.capture3(
|
|
196
|
+
webpack_env,
|
|
197
|
+
*command,
|
|
198
|
+
chdir: File.expand_path(config.root_path)
|
|
199
|
+
)
|
|
200
|
+
rescue Errno::EACCES, Errno::ENOENT, Errno::ENOEXEC, Errno::EPERM, Errno::ENOTDIR => e
|
|
201
|
+
logger.error "\nCOMPILATION FAILED:\n#{e.class}: #{e.message}"
|
|
202
|
+
show_doctor_hint_once
|
|
203
|
+
raise SpawnFailure
|
|
204
|
+
end
|
|
184
205
|
|
|
185
206
|
if status.success?
|
|
186
207
|
logger.info "Compiled all packs in #{config.public_output_path}"
|
|
@@ -211,6 +232,15 @@ class Shakapacker::Compiler
|
|
|
211
232
|
config.root_path.join("bin/shakapacker")
|
|
212
233
|
end
|
|
213
234
|
|
|
235
|
+
def shakapacker_command(compile_flags)
|
|
236
|
+
runner = optional_ruby_runner
|
|
237
|
+
bin_path = bin_shakapacker_path.to_s
|
|
238
|
+
flags_part = compile_flags.any? ? ["--", *compile_flags] : []
|
|
239
|
+
|
|
240
|
+
# Use the [cmd, argv0] form so Open3 never routes a lone binstub path through the shell.
|
|
241
|
+
runner.empty? ? [[bin_path, bin_path], *flags_part] : [runner, bin_path, *flags_part]
|
|
242
|
+
end
|
|
243
|
+
|
|
214
244
|
# Fires only after a failed compile, so users in a healthy loop never see the tip.
|
|
215
245
|
def show_doctor_hint_once
|
|
216
246
|
return if self.class.doctor_hint_shown
|
|
@@ -3,14 +3,14 @@ require_relative "digest_strategy"
|
|
|
3
3
|
|
|
4
4
|
module Shakapacker
|
|
5
5
|
class CompilerStrategy
|
|
6
|
-
def self.from_config
|
|
7
|
-
strategy_from_config =
|
|
6
|
+
def self.from_config(instance = Shakapacker.instance)
|
|
7
|
+
strategy_from_config = instance.config.compiler_strategy
|
|
8
8
|
|
|
9
9
|
case strategy_from_config
|
|
10
10
|
when "mtime"
|
|
11
|
-
Shakapacker::MtimeStrategy.new
|
|
11
|
+
Shakapacker::MtimeStrategy.new(instance)
|
|
12
12
|
when "digest"
|
|
13
|
-
Shakapacker::DigestStrategy.new
|
|
13
|
+
Shakapacker::DigestStrategy.new(instance)
|
|
14
14
|
else
|
|
15
15
|
raise "Unknown strategy '#{strategy_from_config}'. " \
|
|
16
16
|
"Available options are 'mtime' and 'digest'."
|