shakapacker 9.1.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 +7 -0
- data/CHANGELOG.md +50 -4
- data/CLAUDE.md +6 -1
- data/CONTRIBUTING.md +0 -1
- data/Gemfile.lock +1 -1
- data/README.md +35 -14
- data/TODO.md +10 -2
- data/TODO_v9.md +13 -3
- data/bin/export-bundler-config +11 -0
- 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 +68 -6
- 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 +195 -0
- data/docs/rspack.md +25 -21
- data/docs/rspack_migration_guide.md +363 -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 +122 -23
- 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 +11 -0
- data/lib/install/bin/shakapacker +1 -1
- data/lib/install/bin/shakapacker-dev-server +1 -1
- data/lib/install/config/shakapacker.yml +16 -5
- data/lib/shakapacker/bundler_switcher.rb +7 -0
- data/lib/shakapacker/compiler.rb +80 -0
- data/lib/shakapacker/configuration.rb +56 -2
- data/lib/shakapacker/dev_server_runner.rb +140 -1
- data/lib/shakapacker/doctor.rb +302 -57
- data/lib/shakapacker/instance.rb +8 -3
- data/lib/shakapacker/rspack_runner.rb +1 -1
- data/lib/shakapacker/runner.rb +245 -9
- data/lib/shakapacker/version.rb +1 -1
- data/lib/shakapacker/webpack_runner.rb +1 -1
- data/lib/shakapacker.rb +10 -0
- data/lib/tasks/shakapacker/doctor.rake +42 -2
- data/lib/tasks/shakapacker/export_bundler_config.rake +72 -0
- data/package/babel/preset.ts +7 -4
- data/package/config.ts +42 -30
- data/package/configExporter/cli.ts +1274 -0
- data/package/configExporter/configDocs.ts +102 -0
- data/package/configExporter/configFile.ts +520 -0
- data/package/configExporter/fileWriter.ts +96 -0
- data/package/configExporter/index.ts +13 -0
- data/package/configExporter/types.ts +70 -0
- data/package/configExporter/yamlSerializer.ts +280 -0
- 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 +37 -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 +1635 -1442
- metadata +29 -3
- data/.eslintignore +0 -5
data/lib/shakapacker/doctor.rb
CHANGED
|
@@ -5,17 +5,28 @@ require "semantic_range"
|
|
|
5
5
|
|
|
6
6
|
module Shakapacker
|
|
7
7
|
class Doctor
|
|
8
|
-
attr_reader :config, :root_path, :issues, :warnings, :info
|
|
8
|
+
attr_reader :config, :root_path, :issues, :warnings, :info, :options
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
# Warning categories for better organization
|
|
11
|
+
CATEGORY_ACTION_REQUIRED = :action_required
|
|
12
|
+
CATEGORY_RECOMMENDED = :recommended
|
|
13
|
+
CATEGORY_INFO = :info
|
|
14
|
+
|
|
15
|
+
def initialize(config = nil, root_path = nil, options = {})
|
|
11
16
|
@config = config || Shakapacker.config
|
|
12
17
|
@root_path = root_path || (defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd))
|
|
13
18
|
@issues = []
|
|
14
|
-
@warnings = []
|
|
19
|
+
@warnings = [] # Now stores hashes: { category: :symbol, message: "..." }
|
|
15
20
|
@info = []
|
|
21
|
+
@options = options
|
|
16
22
|
end
|
|
17
23
|
|
|
18
24
|
def run
|
|
25
|
+
if options[:help]
|
|
26
|
+
print_help
|
|
27
|
+
return
|
|
28
|
+
end
|
|
29
|
+
|
|
19
30
|
perform_checks
|
|
20
31
|
report_results
|
|
21
32
|
end
|
|
@@ -26,6 +37,49 @@ module Shakapacker
|
|
|
26
37
|
|
|
27
38
|
private
|
|
28
39
|
|
|
40
|
+
def add_warning(message, category = CATEGORY_RECOMMENDED)
|
|
41
|
+
@warnings << { category: category, message: message }
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def add_action_required(message)
|
|
45
|
+
add_warning(message, CATEGORY_ACTION_REQUIRED)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def add_info_warning(message)
|
|
49
|
+
add_warning(message, CATEGORY_INFO)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def print_help
|
|
53
|
+
puts <<~HELP
|
|
54
|
+
Shakapacker Doctor - Diagnostic tool for Shakapacker configuration
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
bin/rails shakapacker:doctor [options]
|
|
58
|
+
|
|
59
|
+
Options:
|
|
60
|
+
--help Show this help message
|
|
61
|
+
--verbose Show detailed information about all checks
|
|
62
|
+
|
|
63
|
+
Description:
|
|
64
|
+
The doctor command checks for common configuration issues and missing
|
|
65
|
+
dependencies in your Shakapacker setup, including:
|
|
66
|
+
|
|
67
|
+
• Configuration file validity
|
|
68
|
+
• Entry points and output paths
|
|
69
|
+
• Node.js and package manager installation
|
|
70
|
+
• Required npm dependencies
|
|
71
|
+
• JavaScript transpiler configuration
|
|
72
|
+
• CSS and CSS Modules setup
|
|
73
|
+
• Binstubs presence
|
|
74
|
+
• Version consistency
|
|
75
|
+
• Legacy file detection
|
|
76
|
+
|
|
77
|
+
Exit codes:
|
|
78
|
+
0 - No issues found
|
|
79
|
+
1 - Issues detected (see output for details)
|
|
80
|
+
HELP
|
|
81
|
+
end
|
|
82
|
+
|
|
29
83
|
def perform_checks
|
|
30
84
|
# Core configuration checks
|
|
31
85
|
check_config_file
|
|
@@ -80,7 +134,7 @@ module Shakapacker
|
|
|
80
134
|
# Check for at least one entry point
|
|
81
135
|
entry_files = Dir.glob(File.join(source_entry_path, "**/*.{js,jsx,ts,tsx,coffee}"))
|
|
82
136
|
if entry_files.empty?
|
|
83
|
-
|
|
137
|
+
add_warning("No entry point files found in #{source_entry_path}")
|
|
84
138
|
end
|
|
85
139
|
end
|
|
86
140
|
|
|
@@ -109,7 +163,7 @@ module Shakapacker
|
|
|
109
163
|
begin
|
|
110
164
|
manifest_content = JSON.parse(File.read(manifest_path))
|
|
111
165
|
if manifest_content.empty?
|
|
112
|
-
|
|
166
|
+
add_warning("Manifest file is empty - you may need to run 'bin/rails assets:precompile'")
|
|
113
167
|
end
|
|
114
168
|
rescue JSON::ParserError
|
|
115
169
|
@issues << "Manifest file #{manifest_path} contains invalid JSON"
|
|
@@ -125,13 +179,17 @@ module Shakapacker
|
|
|
125
179
|
|
|
126
180
|
def check_deprecated_config
|
|
127
181
|
config_file = File.read(config.config_path)
|
|
182
|
+
config_relative_path = config.config_path.relative_path_from(root_path)
|
|
128
183
|
|
|
129
184
|
if config_file.include?("webpack_loader:")
|
|
130
|
-
|
|
185
|
+
add_action_required("Deprecated config: 'webpack_loader' should be renamed to 'javascript_transpiler' in #{config_relative_path}")
|
|
131
186
|
end
|
|
132
187
|
|
|
133
|
-
|
|
134
|
-
|
|
188
|
+
# Check for standalone "bundler:" but not "assets_bundler:"
|
|
189
|
+
# Match "bundler:" at start of line or preceded by non-underscore character
|
|
190
|
+
if config_file.match?(/^\s*bundler:/m) || config_file.match?(/[^_]bundler:/)
|
|
191
|
+
add_action_required("Deprecated config: 'bundler' should be renamed to 'assets_bundler' in #{config_relative_path}.")
|
|
192
|
+
add_action_required(" Fix: Open #{config_relative_path} and change 'bundler:' to 'assets_bundler:'.")
|
|
135
193
|
end
|
|
136
194
|
rescue => e
|
|
137
195
|
# Ignore read errors as config file check already handles missing file
|
|
@@ -146,15 +204,18 @@ module Shakapacker
|
|
|
146
204
|
package_json.dig("devDependencies", "shakapacker")
|
|
147
205
|
|
|
148
206
|
if npm_version
|
|
207
|
+
# Skip version check for github/file references
|
|
208
|
+
return if npm_version.start_with?("github:", "git+", "file:", "link:", "./", "../", "/")
|
|
209
|
+
|
|
149
210
|
gem_version = Shakapacker::VERSION rescue nil
|
|
150
211
|
if gem_version && !versions_compatible?(gem_version, npm_version)
|
|
151
|
-
|
|
212
|
+
add_info_warning("Version mismatch: shakapacker gem is #{gem_version} but npm package is #{npm_version}")
|
|
152
213
|
end
|
|
153
214
|
end
|
|
154
215
|
|
|
155
|
-
# Check if ensure_consistent_versioning is enabled
|
|
216
|
+
# Check if ensure_consistent_versioning is enabled
|
|
156
217
|
if config.ensure_consistent_versioning?
|
|
157
|
-
@info << "Version consistency checking
|
|
218
|
+
@info << "Version consistency checking: enabled (ensures shakapacker gem and npm package versions match at runtime)"
|
|
158
219
|
end
|
|
159
220
|
end
|
|
160
221
|
|
|
@@ -163,7 +224,7 @@ module Shakapacker
|
|
|
163
224
|
node_env = ENV["NODE_ENV"]
|
|
164
225
|
|
|
165
226
|
if rails_env && node_env && rails_env != node_env
|
|
166
|
-
|
|
227
|
+
add_warning("Environment mismatch: Rails.env is '#{rails_env}' but NODE_ENV is '#{node_env}'")
|
|
167
228
|
end
|
|
168
229
|
|
|
169
230
|
# Check SHAKAPACKER_ASSET_HOST for production
|
|
@@ -206,7 +267,7 @@ module Shakapacker
|
|
|
206
267
|
|
|
207
268
|
# Check for conflicting installations
|
|
208
269
|
if package_installed?("webpack") && package_installed?("@rspack/core")
|
|
209
|
-
|
|
270
|
+
add_warning("Both webpack and rspack are installed - ensure assets_bundler is set correctly")
|
|
210
271
|
end
|
|
211
272
|
end
|
|
212
273
|
|
|
@@ -242,7 +303,7 @@ module Shakapacker
|
|
|
242
303
|
|
|
243
304
|
# Check for case sensitivity issues
|
|
244
305
|
if File.exist?(root_path.join("App")) || File.exist?(root_path.join("APP"))
|
|
245
|
-
|
|
306
|
+
add_warning("Potential case sensitivity issue detected on Windows filesystem")
|
|
246
307
|
end
|
|
247
308
|
end
|
|
248
309
|
end
|
|
@@ -254,7 +315,8 @@ module Shakapacker
|
|
|
254
315
|
# Check if manifest is recent (within last 24 hours)
|
|
255
316
|
manifest_age_hours = (Time.now - File.mtime(manifest_path)) / 3600
|
|
256
317
|
|
|
257
|
-
if manifest_age_hours > 24
|
|
318
|
+
if manifest_age_hours > 24 && options[:verbose]
|
|
319
|
+
# Only show age in verbose mode - it's not actionable information
|
|
258
320
|
@info << "Assets were last compiled #{manifest_age_hours.round} hours ago. Consider recompiling if you've made changes."
|
|
259
321
|
end
|
|
260
322
|
|
|
@@ -263,15 +325,16 @@ module Shakapacker
|
|
|
263
325
|
if source_files.any?
|
|
264
326
|
newest_source = source_files.map { |f| File.mtime(f) }.max
|
|
265
327
|
if newest_source > File.mtime(manifest_path)
|
|
266
|
-
|
|
328
|
+
add_warning("Source files have been modified after last asset compilation. Run 'bin/rails assets:precompile'")
|
|
267
329
|
end
|
|
268
330
|
end
|
|
269
331
|
else
|
|
270
332
|
rails_env = defined?(Rails) ? Rails.env : ENV["RAILS_ENV"]
|
|
271
333
|
if rails_env == "production"
|
|
272
|
-
@issues << "No compiled assets found (manifest.json missing). Run 'rails assets:precompile'"
|
|
273
|
-
|
|
274
|
-
|
|
334
|
+
@issues << "No compiled assets found (manifest.json missing). Run 'bin/rails assets:precompile'"
|
|
335
|
+
elsif options[:verbose]
|
|
336
|
+
# Only show in verbose mode for non-production environments
|
|
337
|
+
@info << "Assets not yet compiled. Run 'bin/rails assets:precompile' or start the dev server"
|
|
275
338
|
end
|
|
276
339
|
end
|
|
277
340
|
end
|
|
@@ -287,7 +350,7 @@ module Shakapacker
|
|
|
287
350
|
legacy_files.each do |file|
|
|
288
351
|
file_path = root_path.join(file)
|
|
289
352
|
if file_path.exist?
|
|
290
|
-
|
|
353
|
+
add_warning("Legacy webpacker file found: #{file} - consider removing after migration")
|
|
291
354
|
end
|
|
292
355
|
end
|
|
293
356
|
end
|
|
@@ -302,7 +365,7 @@ module Shakapacker
|
|
|
302
365
|
if version_match
|
|
303
366
|
major = version_match[1].to_i
|
|
304
367
|
if major < 14
|
|
305
|
-
|
|
368
|
+
add_warning("Node.js version #{node_version} is outdated. Recommend upgrading to v14 or higher")
|
|
306
369
|
end
|
|
307
370
|
end
|
|
308
371
|
else
|
|
@@ -313,7 +376,7 @@ module Shakapacker
|
|
|
313
376
|
rescue Errno::EACCES
|
|
314
377
|
@issues << "Permission denied when checking Node.js version"
|
|
315
378
|
rescue StandardError => e
|
|
316
|
-
|
|
379
|
+
add_warning("Unable to check Node.js version: #{e.message}")
|
|
317
380
|
end
|
|
318
381
|
|
|
319
382
|
def check_package_manager
|
|
@@ -323,9 +386,23 @@ module Shakapacker
|
|
|
323
386
|
end
|
|
324
387
|
|
|
325
388
|
def check_binstub
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
389
|
+
missing_binstubs = []
|
|
390
|
+
|
|
391
|
+
expected_binstubs = {
|
|
392
|
+
"bin/shakapacker" => "Main Shakapacker binstub",
|
|
393
|
+
"bin/shakapacker-dev-server" => "Development server binstub",
|
|
394
|
+
"bin/export-bundler-config" => "Config export binstub"
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
expected_binstubs.each do |path, description|
|
|
398
|
+
unless root_path.join(path).exist?
|
|
399
|
+
missing_binstubs << "#{path} (#{description})"
|
|
400
|
+
end
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
unless missing_binstubs.empty?
|
|
404
|
+
add_action_required("Missing binstubs: #{missing_binstubs.join(', ')}.")
|
|
405
|
+
add_action_required(" Fix: Run 'bin/rails shakapacker:binstubs' to create them.")
|
|
329
406
|
end
|
|
330
407
|
end
|
|
331
408
|
|
|
@@ -389,7 +466,16 @@ module Shakapacker
|
|
|
389
466
|
# Rspack has built-in SWC support
|
|
390
467
|
@info << "Rspack has built-in SWC support - no additional loaders needed"
|
|
391
468
|
if package_installed?("swc-loader")
|
|
392
|
-
|
|
469
|
+
package_manager = detect_package_manager
|
|
470
|
+
remove_cmd = case package_manager
|
|
471
|
+
when "yarn" then "yarn remove swc-loader"
|
|
472
|
+
when "npm" then "npm uninstall swc-loader"
|
|
473
|
+
when "pnpm" then "pnpm remove swc-loader"
|
|
474
|
+
when "bun" then "bun remove swc-loader"
|
|
475
|
+
else "npm uninstall swc-loader"
|
|
476
|
+
end
|
|
477
|
+
add_warning("swc-loader is not needed with Rspack (SWC is built-in). Rspack includes SWC transpilation natively, so this package is redundant.")
|
|
478
|
+
add_warning(" Fix: Remove it with: #{remove_cmd}.")
|
|
393
479
|
end
|
|
394
480
|
end
|
|
395
481
|
end
|
|
@@ -413,26 +499,32 @@ module Shakapacker
|
|
|
413
499
|
]
|
|
414
500
|
|
|
415
501
|
babel_config_exists = babel_configs.any?(&:exist?)
|
|
502
|
+
babel_in_package_json = false
|
|
416
503
|
|
|
417
504
|
# Check if package.json has babel config
|
|
418
505
|
if package_json_exists?
|
|
419
506
|
package_json = read_package_json
|
|
420
|
-
|
|
507
|
+
babel_in_package_json = package_json.key?("babel")
|
|
508
|
+
babel_config_exists ||= babel_in_package_json
|
|
421
509
|
end
|
|
422
510
|
|
|
423
511
|
transpiler = config.javascript_transpiler
|
|
424
512
|
|
|
425
513
|
if babel_config_exists && transpiler != "babel"
|
|
426
|
-
|
|
514
|
+
babel_files = babel_configs.select(&:exist?).map { |f| f.relative_path_from(root_path) }
|
|
515
|
+
babel_files << "package.json" if babel_in_package_json
|
|
516
|
+
babel_files_str = babel_files.join(", ")
|
|
517
|
+
add_warning("Babel configuration files found (#{babel_files_str}) but javascript_transpiler is '#{transpiler}'. These Babel configs are ignored by Shakapacker (though they may still be used by ESLint or other tools).")
|
|
518
|
+
add_warning(" Fix: Remove Babel config files if not needed, or set javascript_transpiler: 'babel' in shakapacker.yml to use Babel for transpilation.")
|
|
427
519
|
end
|
|
428
520
|
|
|
429
521
|
# Check for redundant dependencies
|
|
430
522
|
if transpiler == "swc" && package_installed?("babel-loader")
|
|
431
|
-
|
|
523
|
+
add_warning("Both SWC and Babel dependencies are installed. Consider removing Babel dependencies to reduce node_modules size")
|
|
432
524
|
end
|
|
433
525
|
|
|
434
526
|
if transpiler == "esbuild" && package_installed?("babel-loader")
|
|
435
|
-
|
|
527
|
+
add_warning("Both esbuild and Babel dependencies are installed. Consider removing Babel dependencies to reduce node_modules size")
|
|
436
528
|
end
|
|
437
529
|
|
|
438
530
|
# Check for SWC configuration conflicts
|
|
@@ -446,10 +538,10 @@ module Shakapacker
|
|
|
446
538
|
swc_config_path = root_path.join("config/swc.config.js")
|
|
447
539
|
|
|
448
540
|
if swcrc_path.exist?
|
|
449
|
-
|
|
541
|
+
add_warning("SWC configuration: .swcrc file detected. This file completely overrides Shakapacker's default SWC settings and may cause build failures. " \
|
|
450
542
|
"Please migrate to config/swc.config.js which properly merges with Shakapacker defaults. " \
|
|
451
543
|
"To migrate: Move your custom settings from .swcrc to config/swc.config.js (see docs for format). " \
|
|
452
|
-
"See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md"
|
|
544
|
+
"See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md")
|
|
453
545
|
end
|
|
454
546
|
|
|
455
547
|
if swc_config_path.exist?
|
|
@@ -463,18 +555,18 @@ module Shakapacker
|
|
|
463
555
|
|
|
464
556
|
# Check for loose: true (deprecated default)
|
|
465
557
|
if config_content.match?(/loose\s*:\s*true/)
|
|
466
|
-
|
|
558
|
+
add_warning("SWC configuration: 'loose: true' detected in config/swc.config.js. " \
|
|
467
559
|
"This can cause silent failures with Stimulus controllers and incorrect spread operator behavior. " \
|
|
468
560
|
"Consider removing this setting to use Shakapacker's default 'loose: false' (spec-compliant). " \
|
|
469
|
-
"See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md#using-swc-with-stimulus"
|
|
561
|
+
"See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md#using-swc-with-stimulus")
|
|
470
562
|
end
|
|
471
563
|
|
|
472
564
|
# Check for missing keepClassNames with Stimulus
|
|
473
565
|
if stimulus_likely_used? && !config_content.match?(/keepClassNames\s*:\s*true/)
|
|
474
|
-
|
|
566
|
+
add_warning("SWC configuration: Stimulus appears to be in use, but 'keepClassNames: true' is not set in config/swc.config.js. " \
|
|
475
567
|
"Without this setting, Stimulus controllers will fail silently. " \
|
|
476
568
|
"Add 'keepClassNames: true' to jsc config. " \
|
|
477
|
-
"See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md#using-swc-with-stimulus"
|
|
569
|
+
"See: https://github.com/shakacode/shakapacker/blob/main/docs/using_swc_loader.md#using-swc-with-stimulus")
|
|
478
570
|
elsif config_content.match?(/keepClassNames\s*:\s*true/)
|
|
479
571
|
@info << "SWC configuration: 'keepClassNames: true' is set (good for Stimulus compatibility)"
|
|
480
572
|
end
|
|
@@ -488,7 +580,7 @@ module Shakapacker
|
|
|
488
580
|
end
|
|
489
581
|
rescue => e
|
|
490
582
|
# Don't fail doctor if SWC config check has issues
|
|
491
|
-
|
|
583
|
+
add_warning("Unable to validate SWC configuration: #{e.message}")
|
|
492
584
|
end
|
|
493
585
|
|
|
494
586
|
def stimulus_likely_used?
|
|
@@ -538,19 +630,13 @@ module Shakapacker
|
|
|
538
630
|
@issues << " Using exportLocalsConvention: 'camelCase' with namedExport: true will cause build errors"
|
|
539
631
|
@issues << " Change to 'camelCaseOnly' or 'dashesOnly'. See docs/v9_upgrade.md for details"
|
|
540
632
|
end
|
|
541
|
-
|
|
542
|
-
# Warn if CSS modules are used but no configuration is found
|
|
543
|
-
if !config_content.match(/namedExport/) && !config_content.match(/exportLocalsConvention/)
|
|
544
|
-
@info << "CSS module files found but no explicit CSS modules configuration detected"
|
|
545
|
-
@info << " v9 defaults: namedExport: true, exportLocalsConvention: 'camelCaseOnly'"
|
|
546
|
-
end
|
|
547
633
|
end
|
|
548
634
|
|
|
549
635
|
# Check for common v8 to v9 migration issues
|
|
550
636
|
check_css_modules_import_patterns
|
|
551
637
|
rescue => e
|
|
552
638
|
# Don't fail doctor if CSS modules check has issues
|
|
553
|
-
|
|
639
|
+
add_warning("Unable to validate CSS modules configuration: #{e.message}")
|
|
554
640
|
end
|
|
555
641
|
|
|
556
642
|
def check_css_modules_import_patterns
|
|
@@ -567,10 +653,10 @@ module Shakapacker
|
|
|
567
653
|
|
|
568
654
|
# Check for v8 default import pattern with .module.css
|
|
569
655
|
if v8_pattern.match?(content)
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
656
|
+
add_warning("Potential v8-style CSS module imports detected (using default import)")
|
|
657
|
+
add_warning(" v9 uses named exports. Update to: import { className } from './styles.module.css'")
|
|
658
|
+
add_warning(" Or use: import * as styles from './styles.module.css' (TypeScript)")
|
|
659
|
+
add_warning(" See docs/v9_upgrade.md for migration guide")
|
|
574
660
|
break # Stop after finding first occurrence
|
|
575
661
|
end
|
|
576
662
|
end
|
|
@@ -638,7 +724,7 @@ module Shakapacker
|
|
|
638
724
|
|
|
639
725
|
def check_optional_dependency(package_name, warnings_array, description)
|
|
640
726
|
unless package_installed?(package_name)
|
|
641
|
-
|
|
727
|
+
add_warning("Optional dependency '#{package_name}' for #{description} is not installed")
|
|
642
728
|
end
|
|
643
729
|
end
|
|
644
730
|
|
|
@@ -754,25 +840,91 @@ module Shakapacker
|
|
|
754
840
|
|
|
755
841
|
private
|
|
756
842
|
|
|
843
|
+
def verbose?
|
|
844
|
+
doctor.options[:verbose]
|
|
845
|
+
end
|
|
846
|
+
|
|
757
847
|
def print_header
|
|
758
848
|
puts "Running Shakapacker doctor..."
|
|
759
849
|
puts "=" * 60
|
|
850
|
+
puts ""
|
|
851
|
+
if verbose?
|
|
852
|
+
puts "Mode: Verbose (showing all checks)"
|
|
853
|
+
puts ""
|
|
854
|
+
end
|
|
760
855
|
end
|
|
761
856
|
|
|
762
857
|
def print_checks
|
|
763
858
|
if doctor.config.config_path.exist?
|
|
764
|
-
|
|
859
|
+
config_relative_path = doctor.config.config_path.relative_path_from(doctor.root_path)
|
|
860
|
+
puts "✓ Configuration file found (#{config_relative_path})"
|
|
861
|
+
if verbose?
|
|
862
|
+
puts " Assets bundler: #{doctor.config.assets_bundler}"
|
|
863
|
+
puts " Source path: #{doctor.config.source_path.relative_path_from(doctor.root_path)}"
|
|
864
|
+
puts " Public output path: #{doctor.config.public_output_path.relative_path_from(doctor.root_path)}"
|
|
865
|
+
end
|
|
765
866
|
print_transpiler_status
|
|
766
867
|
print_bundler_status
|
|
767
868
|
print_css_status
|
|
869
|
+
elsif verbose?
|
|
870
|
+
puts "✗ Configuration file not found"
|
|
768
871
|
end
|
|
769
872
|
|
|
770
873
|
print_node_status
|
|
771
874
|
print_package_manager_status
|
|
772
875
|
print_binstub_status
|
|
876
|
+
print_verbose_checks if verbose?
|
|
773
877
|
print_info_messages
|
|
774
878
|
end
|
|
775
879
|
|
|
880
|
+
def print_verbose_checks
|
|
881
|
+
puts "\nVerbose diagnostics:"
|
|
882
|
+
|
|
883
|
+
# Show environment info
|
|
884
|
+
rails_env = defined?(Rails) ? Rails.env : ENV["RAILS_ENV"]
|
|
885
|
+
node_env = ENV["NODE_ENV"]
|
|
886
|
+
puts " • Rails environment: #{rails_env || 'not set'}"
|
|
887
|
+
puts " • Node environment: #{node_env || 'not set'}"
|
|
888
|
+
|
|
889
|
+
# Show gem/npm versions
|
|
890
|
+
if doctor.send(:package_json_exists?)
|
|
891
|
+
package_json = doctor.send(:read_package_json)
|
|
892
|
+
npm_version = package_json.dig("dependencies", "shakapacker") ||
|
|
893
|
+
package_json.dig("devDependencies", "shakapacker")
|
|
894
|
+
puts " • Shakapacker gem version: #{Shakapacker::VERSION}"
|
|
895
|
+
puts " • Shakapacker npm version: #{npm_version || 'not installed'}"
|
|
896
|
+
end
|
|
897
|
+
|
|
898
|
+
# Show paths
|
|
899
|
+
puts " • Root path: #{doctor.root_path}"
|
|
900
|
+
if doctor.config.config_path.exist?
|
|
901
|
+
puts " • Cache path: #{doctor.config.cache_path}"
|
|
902
|
+
puts " • Manifest path: #{doctor.config.manifest_path}"
|
|
903
|
+
end
|
|
904
|
+
|
|
905
|
+
# Show environment-specific shakapacker.yml configuration values
|
|
906
|
+
if doctor.config.config_path.exist?
|
|
907
|
+
puts "\nConfiguration values for '#{doctor.config.env}' environment:"
|
|
908
|
+
config_data = doctor.config.send(:data)
|
|
909
|
+
if config_data.any?
|
|
910
|
+
config_data.each do |key, value|
|
|
911
|
+
# Format the value nicely - truncate long arrays/hashes
|
|
912
|
+
formatted_value = case value
|
|
913
|
+
when Array
|
|
914
|
+
value.length > 3 ? "#{value.first(3).inspect}... (#{value.length} items)" : value.inspect
|
|
915
|
+
when Hash
|
|
916
|
+
value.length > 3 ? "{...} (#{value.length} keys)" : value.inspect
|
|
917
|
+
else
|
|
918
|
+
value.inspect
|
|
919
|
+
end
|
|
920
|
+
puts " • #{key}: #{formatted_value}"
|
|
921
|
+
end
|
|
922
|
+
else
|
|
923
|
+
puts " (using bundled defaults - no environment-specific config found)"
|
|
924
|
+
end
|
|
925
|
+
end
|
|
926
|
+
end
|
|
927
|
+
|
|
776
928
|
def print_transpiler_status
|
|
777
929
|
transpiler = doctor.config.javascript_transpiler
|
|
778
930
|
return if transpiler.nil? || transpiler == "none"
|
|
@@ -826,9 +978,20 @@ module Shakapacker
|
|
|
826
978
|
end
|
|
827
979
|
|
|
828
980
|
def print_binstub_status
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
981
|
+
binstubs = [
|
|
982
|
+
"bin/shakapacker",
|
|
983
|
+
"bin/shakapacker-dev-server",
|
|
984
|
+
"bin/export-bundler-config"
|
|
985
|
+
]
|
|
986
|
+
|
|
987
|
+
existing_binstubs = binstubs.select { |b| doctor.root_path.join(b).exist? }
|
|
988
|
+
|
|
989
|
+
if existing_binstubs.length == binstubs.length
|
|
990
|
+
puts "✓ All Shakapacker binstubs found (#{existing_binstubs.join(', ')})"
|
|
991
|
+
elsif existing_binstubs.any?
|
|
992
|
+
existing_binstubs.each do |binstub|
|
|
993
|
+
puts "✓ #{binstub} found"
|
|
994
|
+
end
|
|
832
995
|
end
|
|
833
996
|
end
|
|
834
997
|
|
|
@@ -843,13 +1006,14 @@ module Shakapacker
|
|
|
843
1006
|
|
|
844
1007
|
def print_summary
|
|
845
1008
|
puts "=" * 60
|
|
1009
|
+
puts ""
|
|
846
1010
|
|
|
847
1011
|
if doctor.issues.empty? && doctor.warnings.empty?
|
|
848
1012
|
puts "✅ No issues found! Shakapacker appears to be configured correctly."
|
|
849
1013
|
else
|
|
850
1014
|
print_issues if doctor.issues.any?
|
|
851
1015
|
print_warnings if doctor.warnings.any?
|
|
852
|
-
print_fix_instructions
|
|
1016
|
+
print_fix_instructions if has_dependency_issues?
|
|
853
1017
|
end
|
|
854
1018
|
end
|
|
855
1019
|
|
|
@@ -862,17 +1026,98 @@ module Shakapacker
|
|
|
862
1026
|
end
|
|
863
1027
|
|
|
864
1028
|
def print_warnings
|
|
865
|
-
|
|
866
|
-
doctor.warnings.
|
|
867
|
-
|
|
1029
|
+
# Count only main items (not sub-items)
|
|
1030
|
+
main_item_count = doctor.warnings.count { |w| !w[:message].start_with?(" ") }
|
|
1031
|
+
puts "⚠️ Warnings (#{main_item_count}):"
|
|
1032
|
+
puts ""
|
|
1033
|
+
|
|
1034
|
+
item_number = 0
|
|
1035
|
+
doctor.warnings.each do |warning|
|
|
1036
|
+
category_prefix = case warning[:category]
|
|
1037
|
+
when :action_required then "[REQUIRED]"
|
|
1038
|
+
when :info then "[INFO]"
|
|
1039
|
+
when :recommended then "[RECOMMENDED]"
|
|
1040
|
+
else ""
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
# Sub-items start with whitespace (indented fix instructions)
|
|
1044
|
+
is_subitem = warning[:message].start_with?(" ")
|
|
1045
|
+
|
|
1046
|
+
if is_subitem
|
|
1047
|
+
# Fix instructions align at column 16 (length of "N. [RECOMMENDED] ")
|
|
1048
|
+
# This ensures all Fix lines align vertically regardless of category
|
|
1049
|
+
subitem_prefix = " " * 15
|
|
1050
|
+
wrapped = wrap_text(warning[:message], 100, subitem_prefix)
|
|
1051
|
+
puts wrapped
|
|
1052
|
+
else
|
|
1053
|
+
item_number += 1
|
|
1054
|
+
# Format: N. [CATEGORY] Message
|
|
1055
|
+
prefix = "#{item_number}. #{category_prefix} "
|
|
1056
|
+
wrapped = wrap_text(warning[:message], 100, prefix)
|
|
1057
|
+
puts wrapped
|
|
1058
|
+
end
|
|
868
1059
|
end
|
|
869
1060
|
puts ""
|
|
870
1061
|
end
|
|
871
1062
|
|
|
1063
|
+
def wrap_text(text, max_width, prefix)
|
|
1064
|
+
# Strip leading whitespace from sub-items
|
|
1065
|
+
text = text.strip
|
|
1066
|
+
|
|
1067
|
+
# Calculate available width for text
|
|
1068
|
+
available_width = max_width - prefix.length
|
|
1069
|
+
return prefix + text if text.length <= available_width
|
|
1070
|
+
|
|
1071
|
+
# Wrap long lines
|
|
1072
|
+
words = text.split(" ")
|
|
1073
|
+
lines = []
|
|
1074
|
+
current_line = []
|
|
1075
|
+
current_length = 0
|
|
1076
|
+
|
|
1077
|
+
words.each do |word|
|
|
1078
|
+
word_length = word.length + (current_line.empty? ? 0 : 1) # +1 for space
|
|
1079
|
+
|
|
1080
|
+
if current_length + word_length <= available_width
|
|
1081
|
+
current_line << word
|
|
1082
|
+
current_length += word_length
|
|
1083
|
+
else
|
|
1084
|
+
lines << current_line.join(" ") unless current_line.empty?
|
|
1085
|
+
current_line = [word]
|
|
1086
|
+
current_length = word.length
|
|
1087
|
+
end
|
|
1088
|
+
end
|
|
1089
|
+
lines << current_line.join(" ") unless current_line.empty?
|
|
1090
|
+
|
|
1091
|
+
# Format output
|
|
1092
|
+
result = prefix + lines[0]
|
|
1093
|
+
lines[1..].each do |line|
|
|
1094
|
+
result += "\n" + (" " * prefix.length) + line
|
|
1095
|
+
end
|
|
1096
|
+
result
|
|
1097
|
+
end
|
|
1098
|
+
|
|
1099
|
+
def has_dependency_issues?
|
|
1100
|
+
# Check if any issues or warnings are about missing npm/package dependencies
|
|
1101
|
+
# Exclude optional dependencies - only show install instructions for required dependencies
|
|
1102
|
+
all_messages = doctor.issues + doctor.warnings.map { |w| w[:message] }
|
|
1103
|
+
all_messages.any? do |msg|
|
|
1104
|
+
next if msg.include?("Optional")
|
|
1105
|
+
(msg.include?("Missing") && msg.include?("dependency")) ||
|
|
1106
|
+
msg.include?("not installed") ||
|
|
1107
|
+
msg.include?("is not installed")
|
|
1108
|
+
end
|
|
1109
|
+
end
|
|
1110
|
+
|
|
872
1111
|
def print_fix_instructions
|
|
873
1112
|
package_manager = doctor.send(:package_manager)
|
|
874
1113
|
puts "To fix missing dependencies, run:"
|
|
875
1114
|
puts " #{package_manager_install_command(package_manager)}"
|
|
1115
|
+
puts ""
|
|
1116
|
+
puts "For debugging configuration issues, export your webpack/rspack config:"
|
|
1117
|
+
puts " bin/export-bundler-config --doctor"
|
|
1118
|
+
puts " (Exports annotated YAML configs for dev and production - best for troubleshooting)"
|
|
1119
|
+
puts ""
|
|
1120
|
+
puts " See 'bin/export-bundler-config --help' for more options"
|
|
876
1121
|
end
|
|
877
1122
|
|
|
878
1123
|
def package_manager_install_command(manager)
|
data/lib/shakapacker/instance.rb
CHANGED
|
@@ -5,9 +5,14 @@ class Shakapacker::Instance
|
|
|
5
5
|
|
|
6
6
|
attr_reader :root_path, :config_path
|
|
7
7
|
|
|
8
|
-
def initialize(root_path:
|
|
9
|
-
|
|
10
|
-
@
|
|
8
|
+
def initialize(root_path: nil, config_path: nil)
|
|
9
|
+
# Use Rails.root if Rails is defined and no root_path is provided
|
|
10
|
+
@root_path = root_path || (defined?(Rails) && Rails&.root) || Pathname.new(Dir.pwd)
|
|
11
|
+
|
|
12
|
+
# Use the determined root_path to construct the default config path
|
|
13
|
+
default_config_path = @root_path.join("config/shakapacker.yml")
|
|
14
|
+
|
|
15
|
+
@config_path = Pathname.new(ENV["SHAKAPACKER_CONFIG"] || config_path || default_config_path)
|
|
11
16
|
end
|
|
12
17
|
|
|
13
18
|
def env
|