shakapacker 9.2.0 → 9.3.0.beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.md +6 -9
- data/.github/ISSUE_TEMPLATE/feature_request.md +6 -8
- data/.github/workflows/claude-code-review.yml +4 -5
- data/.github/workflows/claude.yml +1 -2
- data/.github/workflows/dummy.yml +4 -4
- data/.github/workflows/generator.yml +9 -9
- data/.github/workflows/node.yml +11 -2
- data/.github/workflows/ruby.yml +16 -16
- data/.github/workflows/test-bundlers.yml +9 -9
- data/.gitignore +4 -0
- data/CHANGELOG.md +19 -4
- data/CLAUDE.md +6 -1
- data/CONTRIBUTING.md +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +14 -14
- data/TODO.md +10 -2
- data/TODO_v9.md +13 -3
- data/bin/export-bundler-config +1 -1
- data/conductor-setup.sh +1 -1
- data/conductor.json +1 -1
- data/docs/cdn_setup.md +13 -8
- data/docs/common-upgrades.md +2 -1
- data/docs/configuration.md +630 -0
- data/docs/css-modules-export-mode.md +120 -100
- data/docs/customizing_babel_config.md +16 -16
- data/docs/deployment.md +18 -0
- data/docs/developing_shakapacker.md +6 -0
- data/docs/optional-peer-dependencies.md +9 -4
- data/docs/peer-dependencies.md +17 -6
- data/docs/precompile_hook.md +342 -0
- data/docs/react.md +57 -47
- data/docs/releasing.md +0 -2
- data/docs/rspack.md +25 -21
- data/docs/rspack_migration_guide.md +335 -8
- data/docs/sprockets.md +1 -0
- data/docs/style_loader_vs_mini_css.md +12 -12
- data/docs/subresource_integrity.md +13 -7
- data/docs/transpiler-performance.md +40 -19
- data/docs/troubleshooting.md +0 -2
- data/docs/typescript-migration.md +48 -39
- data/docs/typescript.md +12 -8
- data/docs/using_esbuild_loader.md +10 -10
- data/docs/v6_upgrade.md +33 -20
- data/docs/v7_upgrade.md +8 -6
- data/docs/v8_upgrade.md +13 -12
- data/docs/v9_upgrade.md +2 -1
- data/eslint.config.fast.js +134 -0
- data/eslint.config.js +140 -0
- data/knip.ts +54 -0
- data/lib/install/bin/export-bundler-config +1 -1
- data/lib/install/config/shakapacker.yml +16 -5
- data/lib/shakapacker/compiler.rb +80 -0
- data/lib/shakapacker/configuration.rb +33 -5
- data/lib/shakapacker/dev_server_runner.rb +140 -1
- data/lib/shakapacker/doctor.rb +294 -65
- data/lib/shakapacker/instance.rb +8 -3
- data/lib/shakapacker/runner.rb +244 -8
- data/lib/shakapacker/version.rb +1 -1
- data/lib/tasks/shakapacker/doctor.rake +42 -2
- data/package/babel/preset.ts +7 -4
- data/package/config.ts +42 -30
- data/package/configExporter/cli.ts +799 -208
- data/package/configExporter/configFile.ts +520 -0
- data/package/configExporter/fileWriter.ts +12 -8
- data/package/configExporter/index.ts +9 -1
- data/package/configExporter/types.ts +36 -2
- data/package/configExporter/yamlSerializer.ts +22 -8
- data/package/dev_server.ts +1 -1
- data/package/environments/__type-tests__/rspack-plugin-compatibility.ts +11 -5
- data/package/environments/base.ts +18 -13
- data/package/environments/development.ts +1 -1
- data/package/environments/production.ts +4 -1
- data/package/index.d.ts +50 -3
- data/package/index.d.ts.template +50 -0
- data/package/index.ts +7 -7
- data/package/loaders.d.ts +2 -2
- data/package/optimization/rspack.ts +1 -1
- data/package/plugins/rspack.ts +15 -4
- data/package/plugins/webpack.ts +7 -3
- data/package/rspack/index.ts +10 -2
- data/package/rules/raw.ts +3 -2
- data/package/rules/sass.ts +1 -1
- data/package/types/README.md +15 -13
- data/package/types/index.ts +5 -5
- data/package/types.ts +0 -1
- data/package/utils/defaultConfigPath.ts +4 -1
- data/package/utils/errorCodes.ts +129 -100
- data/package/utils/errorHelpers.ts +34 -29
- data/package/utils/getStyleRule.ts +5 -2
- data/package/utils/helpers.ts +21 -11
- data/package/utils/pathValidation.ts +43 -35
- data/package/utils/requireOrError.ts +1 -1
- data/package/utils/snakeToCamelCase.ts +1 -1
- data/package/utils/typeGuards.ts +132 -83
- data/package/utils/validateDependencies.ts +1 -1
- data/package/webpack-types.d.ts +3 -3
- data/package/webpackDevServerConfig.ts +22 -10
- data/package-lock.json +2 -2
- data/package.json +36 -28
- data/scripts/type-check-no-emit.js +1 -1
- data/test/configExporter/configFile.test.js +392 -0
- data/test/configExporter/integration.test.js +275 -0
- data/test/helpers.js +1 -1
- data/test/package/configExporter.test.js +154 -0
- data/test/package/helpers.test.js +2 -2
- data/test/package/rules/sass-version-parsing.test.js +71 -0
- data/test/package/rules/sass.test.js +2 -4
- data/test/package/rules/sass1.test.js +1 -3
- data/test/package/rules/sass16.test.js +23 -0
- data/tools/README.md +15 -5
- data/tsconfig.eslint.json +2 -9
- data/yarn.lock +1894 -1492
- metadata +19 -3
- data/.eslintignore +0 -5
@@ -43,11 +43,22 @@ default: &default
|
|
43
43
|
# Select JavaScript transpiler to use
|
44
44
|
# Available options: 'swc' (default, 20x faster), 'babel', or 'esbuild'
|
45
45
|
# Note: When using rspack, swc is used automatically regardless of this setting
|
46
|
-
javascript_transpiler:
|
46
|
+
javascript_transpiler: "swc"
|
47
47
|
|
48
48
|
# Select assets bundler to use
|
49
49
|
# Available options: 'webpack' (default) or 'rspack'
|
50
|
-
assets_bundler:
|
50
|
+
assets_bundler: "webpack"
|
51
|
+
|
52
|
+
# Path to the directory containing webpack/rspack config files
|
53
|
+
# Defaults to 'config/webpack' for webpack or 'config/rspack' for rspack
|
54
|
+
# Use '.' to specify the root directory of your project
|
55
|
+
# assets_bundler_config_path: config/webpack
|
56
|
+
|
57
|
+
# Hook to run before webpack compilation (e.g., for generating dynamic entry points)
|
58
|
+
# SECURITY: Only reference trusted scripts within your project. The hook command will be
|
59
|
+
# validated to ensure it points to a file within the project root.
|
60
|
+
# Example: precompile_hook: 'bin/shakapacker-precompile-hook'
|
61
|
+
# precompile_hook: ~
|
51
62
|
|
52
63
|
# Raises an error if there is a mismatch in the shakapacker gem and npm package being used
|
53
64
|
ensure_consistent_versioning: true
|
@@ -112,14 +123,14 @@ development:
|
|
112
123
|
# Should we use gzip compression?
|
113
124
|
compress: true
|
114
125
|
# Note that apps that do not check the host are vulnerable to DNS rebinding attacks
|
115
|
-
allowed_hosts:
|
126
|
+
allowed_hosts: "auto"
|
116
127
|
# Shows progress and colorizes output of bin/shakapacker[-dev-server]
|
117
128
|
pretty: true
|
118
129
|
headers:
|
119
|
-
|
130
|
+
"Access-Control-Allow-Origin": "*"
|
120
131
|
static:
|
121
132
|
watch:
|
122
|
-
ignored:
|
133
|
+
ignored: "**/node_modules/**"
|
123
134
|
|
124
135
|
test:
|
125
136
|
<<: *default
|
data/lib/shakapacker/compiler.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "open3"
|
2
2
|
require "fileutils"
|
3
|
+
require "shellwords"
|
3
4
|
|
4
5
|
require_relative "compiler_strategy"
|
5
6
|
|
@@ -26,6 +27,7 @@ class Shakapacker::Compiler
|
|
26
27
|
true
|
27
28
|
else
|
28
29
|
acquire_ipc_lock do
|
30
|
+
run_precompile_hook if config.precompile_hook
|
29
31
|
run_webpack.tap do |success|
|
30
32
|
after_compile_hook
|
31
33
|
end
|
@@ -78,6 +80,84 @@ class Shakapacker::Compiler
|
|
78
80
|
/ruby/.match?(first_line) ? RbConfig.ruby : ""
|
79
81
|
end
|
80
82
|
|
83
|
+
def run_precompile_hook
|
84
|
+
hook_command = config.precompile_hook
|
85
|
+
hook_spec = validate_precompile_hook(hook_command)
|
86
|
+
|
87
|
+
logger.info "Running precompile hook: #{hook_command}"
|
88
|
+
|
89
|
+
runtime_env = webpack_env.merge(hook_spec[:env])
|
90
|
+
stdout, stderr, status = Open3.capture3(
|
91
|
+
runtime_env,
|
92
|
+
hook_spec[:executable],
|
93
|
+
*hook_spec[:args],
|
94
|
+
chdir: File.expand_path(config.root_path)
|
95
|
+
)
|
96
|
+
|
97
|
+
if status.success?
|
98
|
+
logger.info "Precompile hook completed successfully"
|
99
|
+
logger.info stdout unless stdout.empty?
|
100
|
+
logger.warn stderr unless stderr.empty?
|
101
|
+
else
|
102
|
+
non_empty_streams = [stdout, stderr].delete_if(&:empty?)
|
103
|
+
logger.error "\nPRECOMPILE HOOK FAILED:\nEXIT STATUS: #{status.exitstatus}\nCOMMAND: #{hook_command}\nOUTPUTS:\n#{non_empty_streams.join("\n\n")}"
|
104
|
+
logger.error "\nTo fix this:"
|
105
|
+
logger.error " 1. Check that the hook script exists and is executable"
|
106
|
+
logger.error " 2. Test the hook command manually: #{hook_command}"
|
107
|
+
logger.error " 3. Review the error output above for details"
|
108
|
+
logger.error " 4. You can disable the hook temporarily by commenting out 'precompile_hook' in shakapacker.yml"
|
109
|
+
raise "Precompile hook '#{hook_command}' failed with exit status #{status.exitstatus}"
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def validate_precompile_hook(hook_command)
|
114
|
+
hook_tokens = begin
|
115
|
+
Shellwords.shellsplit(hook_command)
|
116
|
+
rescue ArgumentError => e
|
117
|
+
raise "Shakapacker configuration error: Invalid precompile_hook command syntax: #{e.message}. Check for unmatched quotes in: #{hook_command}"
|
118
|
+
end
|
119
|
+
|
120
|
+
env_assignments = {}
|
121
|
+
while hook_tokens.first&.match?(/\A[A-Za-z_][A-Za-z0-9_]*=/)
|
122
|
+
key, value = hook_tokens.shift.split("=", 2)
|
123
|
+
env_assignments[key] = value
|
124
|
+
end
|
125
|
+
|
126
|
+
executable = hook_tokens.shift
|
127
|
+
if executable.nil? || executable.empty?
|
128
|
+
raise "Shakapacker configuration error: precompile_hook must include an executable command. Got: #{hook_command}"
|
129
|
+
end
|
130
|
+
|
131
|
+
executable_path = config.root_path.join(executable)
|
132
|
+
|
133
|
+
# Security: Resolve symlinks and verify the hook points to a file within the project
|
134
|
+
# This prevents symlink bypass attacks and path traversal attacks
|
135
|
+
begin
|
136
|
+
resolved_path = executable_path.realpath
|
137
|
+
resolved_root = config.root_path.realpath
|
138
|
+
rescue Errno::ENOENT
|
139
|
+
# If file doesn't exist, use cleanpath for basic validation
|
140
|
+
resolved_path = executable_path.cleanpath
|
141
|
+
resolved_root = config.root_path.cleanpath
|
142
|
+
end
|
143
|
+
|
144
|
+
# Verify path is within project root with proper separator check
|
145
|
+
# Using File::SEPARATOR prevents partial path matches (e.g., /project vs /project-evil)
|
146
|
+
unless resolved_path.to_s.start_with?(resolved_root.to_s + File::SEPARATOR)
|
147
|
+
raise "Security Error: precompile_hook must reference a script within the project root. " \
|
148
|
+
"Got: #{hook_command} (resolved to: #{resolved_path})"
|
149
|
+
end
|
150
|
+
|
151
|
+
# Warn if the executable doesn't exist within the project
|
152
|
+
unless File.exist?(executable_path)
|
153
|
+
logger.warn "⚠️ Warning: precompile_hook executable not found: #{executable_path}"
|
154
|
+
logger.warn " The hook command is configured but the script does not exist within the project root."
|
155
|
+
logger.warn " Please ensure the script exists or remove 'precompile_hook' from your shakapacker.yml configuration."
|
156
|
+
end
|
157
|
+
|
158
|
+
{ env: env_assignments, executable: executable, args: hook_tokens }
|
159
|
+
end
|
160
|
+
|
81
161
|
def run_webpack
|
82
162
|
logger.info "Compiling..."
|
83
163
|
|
@@ -117,6 +117,17 @@ class Shakapacker::Configuration
|
|
117
117
|
assets_bundler == "webpack"
|
118
118
|
end
|
119
119
|
|
120
|
+
def precompile_hook
|
121
|
+
hook = fetch(:precompile_hook)
|
122
|
+
return nil if hook.nil? || (hook.is_a?(String) && hook.strip.empty?)
|
123
|
+
|
124
|
+
unless hook.is_a?(String)
|
125
|
+
raise "Shakapacker configuration error: precompile_hook must be a string, got #{hook.class}"
|
126
|
+
end
|
127
|
+
|
128
|
+
hook.strip
|
129
|
+
end
|
130
|
+
|
120
131
|
def javascript_transpiler
|
121
132
|
# Show deprecation warning if using old 'webpack_loader' key
|
122
133
|
if data.has_key?(:webpack_loader) && !data.has_key?(:javascript_transpiler)
|
@@ -137,6 +148,14 @@ class Shakapacker::Configuration
|
|
137
148
|
javascript_transpiler
|
138
149
|
end
|
139
150
|
|
151
|
+
def assets_bundler_config_path
|
152
|
+
custom_path = fetch(:assets_bundler_config_path)
|
153
|
+
return custom_path if custom_path
|
154
|
+
|
155
|
+
# Default paths based on bundler type
|
156
|
+
rspack? ? "config/rspack" : "config/webpack"
|
157
|
+
end
|
158
|
+
|
140
159
|
private
|
141
160
|
|
142
161
|
def default_javascript_transpiler
|
@@ -308,11 +327,20 @@ class Shakapacker::Configuration
|
|
308
327
|
end
|
309
328
|
|
310
329
|
def log_fallback(requested_env, fallback_env)
|
311
|
-
|
330
|
+
message = "Shakapacker environment '#{requested_env}' not found in #{config_path}, " \
|
331
|
+
"falling back to '#{fallback_env}'"
|
312
332
|
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
333
|
+
# Try to use the logger if available, otherwise fall back to stdout
|
334
|
+
begin
|
335
|
+
if Shakapacker.respond_to?(:logger) && Shakapacker.logger
|
336
|
+
Shakapacker.logger.info(message)
|
337
|
+
else
|
338
|
+
puts message
|
339
|
+
end
|
340
|
+
rescue NameError, NoMethodError
|
341
|
+
# If logger access fails (e.g., circular dependency in standalone runner context),
|
342
|
+
# fall back to stdout so the message still gets displayed
|
343
|
+
puts message
|
344
|
+
end
|
317
345
|
end
|
318
346
|
end
|
@@ -4,13 +4,150 @@ require "socket"
|
|
4
4
|
require_relative "configuration"
|
5
5
|
require_relative "dev_server"
|
6
6
|
require_relative "runner"
|
7
|
+
require_relative "version"
|
7
8
|
|
8
9
|
module Shakapacker
|
9
10
|
class DevServerRunner < Shakapacker::Runner
|
10
11
|
def self.run(argv)
|
12
|
+
# Show Shakapacker help and exit (don't call bundler)
|
13
|
+
if argv.include?("--help") || argv.include?("-h")
|
14
|
+
print_help
|
15
|
+
exit(0)
|
16
|
+
elsif argv.include?("--version") || argv.include?("-v")
|
17
|
+
print_version
|
18
|
+
exit(0)
|
19
|
+
end
|
20
|
+
|
11
21
|
new(argv).run
|
12
22
|
end
|
13
23
|
|
24
|
+
def self.print_help
|
25
|
+
puts <<~HELP
|
26
|
+
================================================================================
|
27
|
+
SHAKAPACKER DEV SERVER - Development Server with Hot Module Replacement
|
28
|
+
================================================================================
|
29
|
+
|
30
|
+
Usage: bin/shakapacker-dev-server [options]
|
31
|
+
|
32
|
+
Shakapacker-specific options:
|
33
|
+
-h, --help Show this help message
|
34
|
+
-v, --version Show Shakapacker version
|
35
|
+
--debug-shakapacker Enable Node.js debugging (--inspect-brk)
|
36
|
+
|
37
|
+
Examples:
|
38
|
+
bin/shakapacker-dev-server # Start dev server
|
39
|
+
bin/shakapacker-dev-server --no-hot # Disable HMR
|
40
|
+
bin/shakapacker-dev-server --open # Open browser automatically
|
41
|
+
bin/shakapacker-dev-server --debug-shakapacker # Debug with Node inspector
|
42
|
+
|
43
|
+
HELP
|
44
|
+
|
45
|
+
print_dev_server_help
|
46
|
+
|
47
|
+
puts <<~HELP
|
48
|
+
|
49
|
+
Options managed by Shakapacker (configured in config/shakapacker.yml):
|
50
|
+
--host Set from dev_server.host (default: localhost)
|
51
|
+
--port Set from dev_server.port (default: 3035)
|
52
|
+
--https Set from dev_server.server (http or https)
|
53
|
+
--config Set automatically to config/webpack/webpack.config.js
|
54
|
+
or config/rspack/rspack.config.js
|
55
|
+
|
56
|
+
Note: CLI flags for --host, --port, and --https are NOT supported.
|
57
|
+
Configure these in config/shakapacker.yml instead.
|
58
|
+
HELP
|
59
|
+
end
|
60
|
+
|
61
|
+
def self.print_dev_server_help
|
62
|
+
bundler_type, bundler_help = get_dev_server_help
|
63
|
+
|
64
|
+
if bundler_help
|
65
|
+
bundler_name = bundler_type == :rspack ? "RSPACK" : "WEBPACK"
|
66
|
+
puts "=" * 80
|
67
|
+
puts "AVAILABLE #{bundler_name} DEV SERVER OPTIONS (Passed directly to #{bundler_name.downcase})"
|
68
|
+
puts "=" * 80
|
69
|
+
puts
|
70
|
+
puts filter_managed_options(bundler_help)
|
71
|
+
puts
|
72
|
+
puts "For complete documentation:"
|
73
|
+
if bundler_type == :rspack
|
74
|
+
puts " https://rspack.dev/config/dev-server"
|
75
|
+
else
|
76
|
+
puts " https://webpack.js.org/configuration/dev-server/"
|
77
|
+
end
|
78
|
+
else
|
79
|
+
puts "For complete documentation:"
|
80
|
+
puts " Webpack: https://webpack.js.org/configuration/dev-server/"
|
81
|
+
puts " Rspack: https://rspack.dev/config/dev-server"
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def self.get_dev_server_help
|
86
|
+
Runner.execute_bundler_command("serve", "--help") { |stdout| stdout }
|
87
|
+
end
|
88
|
+
|
89
|
+
# Filter dev server help output to remove Shakapacker-managed options
|
90
|
+
#
|
91
|
+
# This method processes the raw help output from webpack-dev-server/rspack serve
|
92
|
+
# and removes options that Shakapacker manages automatically:
|
93
|
+
# - --config (set from config/webpack or config/rspack)
|
94
|
+
# - --host, --port (set from config/shakapacker.yml dev_server settings)
|
95
|
+
# - --help, --version (shown separately in Shakapacker's help)
|
96
|
+
#
|
97
|
+
# The filtering uses skip_until_blank to track multi-line option descriptions
|
98
|
+
# and skip them entirely when the option header matches a managed option.
|
99
|
+
#
|
100
|
+
# Note: This relies on dev server help format conventions. If webpack-dev-server
|
101
|
+
# or rspack significantly changes their help output format, this may need adjustment.
|
102
|
+
def self.filter_managed_options(help_text)
|
103
|
+
lines = help_text.lines
|
104
|
+
filtered_lines = []
|
105
|
+
skip_until_blank = false
|
106
|
+
|
107
|
+
lines.each do |line|
|
108
|
+
# Skip options that Shakapacker manages and their descriptions
|
109
|
+
# These options are shown in the "Options managed by Shakapacker" section
|
110
|
+
if line.match?(/^\s*(-c,\s*)?--config\b/) ||
|
111
|
+
line.match?(/^\s*--configName\b/) ||
|
112
|
+
line.match?(/^\s*--configLoader\b/) ||
|
113
|
+
line.match?(/^\s*--nodeEnv\b/) ||
|
114
|
+
line.match?(/^\s*--host\b/) ||
|
115
|
+
line.match?(/^\s*--port\b/) ||
|
116
|
+
line.match?(/^\s*--https\b/) ||
|
117
|
+
line.match?(/^\s*(-h,\s*)?--help\b/) ||
|
118
|
+
line.match?(/^\s*(-v,\s*)?--version\b/)
|
119
|
+
skip_until_blank = true
|
120
|
+
next
|
121
|
+
end
|
122
|
+
|
123
|
+
# Continue skipping lines that are part of a filtered option's description
|
124
|
+
# Reset when we hit a blank line or the start of a new option (starts with -)
|
125
|
+
if skip_until_blank
|
126
|
+
if line.strip.empty? || line.match?(/^\s*-/)
|
127
|
+
skip_until_blank = false
|
128
|
+
else
|
129
|
+
next
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
filtered_lines << line
|
134
|
+
end
|
135
|
+
|
136
|
+
filtered_lines.join
|
137
|
+
end
|
138
|
+
|
139
|
+
def self.print_version
|
140
|
+
puts "Shakapacker #{Shakapacker::VERSION}"
|
141
|
+
puts "Framework: Rails #{Rails.version}" if defined?(Rails)
|
142
|
+
|
143
|
+
# Try to get bundler version
|
144
|
+
bundler_type, bundler_version = Runner.get_bundler_version
|
145
|
+
if bundler_version
|
146
|
+
bundler_name = bundler_type == :rspack ? "Rspack" : "Webpack"
|
147
|
+
puts "Bundler: #{bundler_name} #{bundler_version}"
|
148
|
+
end
|
149
|
+
end
|
150
|
+
|
14
151
|
def run
|
15
152
|
load_config
|
16
153
|
detect_unsupported_switches!
|
@@ -96,8 +233,10 @@ module Shakapacker
|
|
96
233
|
cmd += @argv
|
97
234
|
|
98
235
|
Dir.chdir(@app_path) do
|
99
|
-
|
236
|
+
system(env, *cmd)
|
100
237
|
end
|
238
|
+
|
239
|
+
exit($?.exitstatus || 1) unless $?.success?
|
101
240
|
end
|
102
241
|
|
103
242
|
def build_cmd
|