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
data/lib/shakapacker/doctor.rb
CHANGED
|
@@ -23,6 +23,55 @@ module Shakapacker
|
|
|
23
23
|
bin/diff-bundler-config
|
|
24
24
|
].freeze
|
|
25
25
|
|
|
26
|
+
PACKAGE_MANAGER_LOCKFILES = {
|
|
27
|
+
"bun.lockb" => "bun",
|
|
28
|
+
"pnpm-lock.yaml" => "pnpm",
|
|
29
|
+
"yarn.lock" => "yarn",
|
|
30
|
+
"package-lock.json" => "npm"
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
SASS_IMPLEMENTATION_PACKAGES = %w[
|
|
34
|
+
sass
|
|
35
|
+
sass-embedded
|
|
36
|
+
].freeze
|
|
37
|
+
|
|
38
|
+
SASS_IMPLEMENTATION_DEPENDENCY_MESSAGE = (
|
|
39
|
+
"Missing required dependency 'sass' or 'sass-embedded' " \
|
|
40
|
+
"for Sass/SCSS implementation"
|
|
41
|
+
).freeze
|
|
42
|
+
|
|
43
|
+
REQUIRED_RSPACK_DEPS = {
|
|
44
|
+
"@rspack/core" => "^2.0.0",
|
|
45
|
+
"@rspack/cli" => "^2.0.0",
|
|
46
|
+
"rspack-manifest-plugin" => "^5.2.2"
|
|
47
|
+
}.freeze
|
|
48
|
+
|
|
49
|
+
RSPACK_DEV_SERVER_DEP = {
|
|
50
|
+
"@rspack/dev-server" => "^2.0.0"
|
|
51
|
+
}.freeze
|
|
52
|
+
|
|
53
|
+
RSPACK_V2_ONLY_DEPS = REQUIRED_RSPACK_DEPS
|
|
54
|
+
.slice("@rspack/core", "@rspack/cli")
|
|
55
|
+
.merge(RSPACK_DEV_SERVER_DEP)
|
|
56
|
+
.freeze
|
|
57
|
+
|
|
58
|
+
OPTIONAL_RSPACK_V2_ONLY_DEPS = {
|
|
59
|
+
"@rspack/plugin-react-refresh" => "^2.0.0"
|
|
60
|
+
}.freeze
|
|
61
|
+
|
|
62
|
+
CUSTOM_HYBRID_LOADER_DEPS = %w[
|
|
63
|
+
babel-loader
|
|
64
|
+
esbuild-loader
|
|
65
|
+
ts-loader
|
|
66
|
+
].freeze
|
|
67
|
+
|
|
68
|
+
BUNDLER_CONFIG_EXTENSIONS = %w[
|
|
69
|
+
ts
|
|
70
|
+
js
|
|
71
|
+
].freeze
|
|
72
|
+
|
|
73
|
+
PACKAGE_ROOT_MARKERS = (["package.json"] + PACKAGE_MANAGER_LOCKFILES.keys + ["node_modules"]).freeze
|
|
74
|
+
|
|
26
75
|
def initialize(config = nil, root_path = nil, options = {})
|
|
27
76
|
@config = config || Shakapacker.config
|
|
28
77
|
@root_path = root_path || (defined?(Rails) ? Rails.root : Pathname.new(Dir.pwd))
|
|
@@ -134,6 +183,8 @@ module Shakapacker
|
|
|
134
183
|
end
|
|
135
184
|
|
|
136
185
|
def check_config_file
|
|
186
|
+
report_empty_assets_bundler_env_override if empty_assets_bundler_env_override?
|
|
187
|
+
|
|
137
188
|
unless config.config_path.exist?
|
|
138
189
|
@issues << "Configuration file not found at #{config.config_path}"
|
|
139
190
|
end
|
|
@@ -220,10 +271,9 @@ module Shakapacker
|
|
|
220
271
|
def check_version_consistency
|
|
221
272
|
return unless package_json_exists?
|
|
222
273
|
|
|
223
|
-
# Check if shakapacker npm package version matches gem version
|
|
224
|
-
|
|
225
|
-
npm_version =
|
|
226
|
-
package_json.dig("devDependencies", "shakapacker")
|
|
274
|
+
# Check if shakapacker npm package version matches gem version. Use the
|
|
275
|
+
# flattened dependency map so a nearer package root wins across sections.
|
|
276
|
+
npm_version = package_json_dependency_version("shakapacker")
|
|
227
277
|
|
|
228
278
|
if npm_version
|
|
229
279
|
# Skip version check for github/file references
|
|
@@ -259,7 +309,7 @@ module Shakapacker
|
|
|
259
309
|
integrity_config = config.integrity
|
|
260
310
|
return unless integrity_config&.dig(:enabled)
|
|
261
311
|
|
|
262
|
-
bundler =
|
|
312
|
+
bundler = assets_bundler
|
|
263
313
|
if bundler == "webpack"
|
|
264
314
|
unless package_installed?("webpack-subresource-integrity")
|
|
265
315
|
@issues << "SRI is enabled but 'webpack-subresource-integrity' is not installed"
|
|
@@ -277,9 +327,8 @@ module Shakapacker
|
|
|
277
327
|
def check_peer_dependencies
|
|
278
328
|
return unless package_json_exists?
|
|
279
329
|
|
|
280
|
-
bundler =
|
|
281
|
-
|
|
282
|
-
all_deps = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
330
|
+
bundler = assets_bundler
|
|
331
|
+
all_deps = declared_package_dependencies
|
|
283
332
|
|
|
284
333
|
if bundler == "webpack"
|
|
285
334
|
check_webpack_peer_deps(all_deps)
|
|
@@ -289,7 +338,13 @@ module Shakapacker
|
|
|
289
338
|
|
|
290
339
|
# Check for conflicting installations
|
|
291
340
|
if package_installed?("webpack") && package_installed?("@rspack/core")
|
|
292
|
-
|
|
341
|
+
if assets_bundler_configured?
|
|
342
|
+
add_warning("Both webpack and rspack are installed - ensure assets_bundler is set correctly")
|
|
343
|
+
else
|
|
344
|
+
add_warning("Both webpack and rspack are installed while assets_bundler is inferred as '#{bundler}'. " \
|
|
345
|
+
"This can be intentional for custom hybrid webpack/Rspack setups; set assets_bundler " \
|
|
346
|
+
"explicitly to document the active Shakapacker-managed bundler.")
|
|
347
|
+
end
|
|
293
348
|
end
|
|
294
349
|
end
|
|
295
350
|
|
|
@@ -307,16 +362,48 @@ module Shakapacker
|
|
|
307
362
|
end
|
|
308
363
|
|
|
309
364
|
def check_rspack_peer_deps(deps)
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
"@rspack/core" => "^1.0.0"
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
essential_rspack.each do |package, version|
|
|
316
|
-
unless deps[package]
|
|
365
|
+
REQUIRED_RSPACK_DEPS.each do |package, version|
|
|
366
|
+
unless deps[package] || installed_package_version(package)
|
|
317
367
|
@issues << "Missing essential rspack dependency: #{package} (#{version})"
|
|
318
368
|
end
|
|
319
369
|
end
|
|
370
|
+
|
|
371
|
+
RSPACK_DEV_SERVER_DEP.each do |package, version|
|
|
372
|
+
unless deps[package] || installed_package_version(package)
|
|
373
|
+
add_warning("Missing recommended rspack dependency: #{package} (#{version}) for Rspack dev server")
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
unsupported_packages = RSPACK_V2_ONLY_DEPS.keys.select do |package|
|
|
378
|
+
deps[package] &&
|
|
379
|
+
(rspack_major_version_for(package) == 1 || rspack_declared_major_version_for(package) == 1)
|
|
380
|
+
end
|
|
381
|
+
|
|
382
|
+
if unsupported_packages.any?
|
|
383
|
+
@issues << "Unsupported rspack dependency version: Shakapacker supports Rspack v2 only. " \
|
|
384
|
+
"Upgrade #{unsupported_packages.join(' and ')} to ^2.0.0."
|
|
385
|
+
end
|
|
386
|
+
|
|
387
|
+
unsupported_optional_packages = OPTIONAL_RSPACK_V2_ONLY_DEPS.keys.select do |package|
|
|
388
|
+
deps[package] &&
|
|
389
|
+
(rspack_major_version_for(package) == 1 || rspack_declared_major_version_for(package) == 1)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
if unsupported_optional_packages.any?
|
|
393
|
+
add_warning("Unsupported optional rspack dependency version: Shakapacker supports Rspack v2 only. " \
|
|
394
|
+
"Upgrade #{unsupported_optional_packages.join(' and ')} to ^2.0.0.")
|
|
395
|
+
end
|
|
396
|
+
|
|
397
|
+
manifest_status = package_version_status("rspack-manifest-plugin", "5.2.2")
|
|
398
|
+
if deps["rspack-manifest-plugin"] && manifest_status[:installed_below]
|
|
399
|
+
@issues << "Unsupported rspack-manifest-plugin version: Shakapacker requires rspack-manifest-plugin " \
|
|
400
|
+
"^5.2.2 for Rspack v2."
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
if deps["rspack-manifest-plugin"] && manifest_status[:declared_below]
|
|
404
|
+
@issues << "Declared rspack-manifest-plugin range allows unsupported versions. " \
|
|
405
|
+
"Update package.json to require rspack-manifest-plugin ^5.2.2 for Rspack v2."
|
|
406
|
+
end
|
|
320
407
|
end
|
|
321
408
|
|
|
322
409
|
def check_windows_platform
|
|
@@ -423,9 +510,8 @@ module Shakapacker
|
|
|
423
510
|
end
|
|
424
511
|
|
|
425
512
|
def check_javascript_transpiler_dependencies
|
|
426
|
-
transpiler =
|
|
513
|
+
transpiler = explicit_javascript_transpiler
|
|
427
514
|
|
|
428
|
-
# Default to SWC for v9+ if not configured
|
|
429
515
|
if transpiler.nil?
|
|
430
516
|
@info << "No javascript_transpiler configured - defaulting to SWC (20x faster than Babel)"
|
|
431
517
|
transpiler = "swc"
|
|
@@ -433,14 +519,31 @@ module Shakapacker
|
|
|
433
519
|
|
|
434
520
|
return if transpiler == "none"
|
|
435
521
|
|
|
436
|
-
bundler =
|
|
522
|
+
bundler = assets_bundler
|
|
523
|
+
unconfigured_hybrid_graph = unconfigured_hybrid_loader_graph?
|
|
524
|
+
inferred_hybrid_graph = inferred_hybrid_loader_graph?(
|
|
525
|
+
transpiler,
|
|
526
|
+
bundler,
|
|
527
|
+
unconfigured_hybrid_graph: unconfigured_hybrid_graph
|
|
528
|
+
)
|
|
529
|
+
if inferred_hybrid_graph
|
|
530
|
+
add_info_warning("Detected a custom hybrid webpack/Rspack setup while Doctor inferred webpack/SWC. " \
|
|
531
|
+
"Skipping SWC dependency issue checks for this inferred default. For custom hybrid webpack/Rspack configs, " \
|
|
532
|
+
"set javascript_transpiler: \"none\" when Shakapacker should not validate loader dependencies, " \
|
|
533
|
+
"or set javascript_transpiler/assets_bundler explicitly when Shakapacker owns that build path.")
|
|
534
|
+
elsif unconfigured_hybrid_graph
|
|
535
|
+
add_info_warning("Detected a custom hybrid webpack/Rspack setup with inferred Shakapacker defaults. " \
|
|
536
|
+
"Doctor is validating the active #{bundler}/#{transpiler} default only. " \
|
|
537
|
+
"Set javascript_transpiler: \"none\" when Shakapacker should not validate loader dependencies, " \
|
|
538
|
+
"or set javascript_transpiler/assets_bundler explicitly when Shakapacker owns that build path.")
|
|
539
|
+
end
|
|
437
540
|
|
|
438
541
|
case transpiler
|
|
439
542
|
when "babel"
|
|
440
543
|
check_babel_dependencies
|
|
441
544
|
check_babel_performance_suggestion
|
|
442
545
|
when "swc"
|
|
443
|
-
check_swc_dependencies(bundler)
|
|
546
|
+
check_swc_dependencies(bundler) unless inferred_hybrid_graph
|
|
444
547
|
when "esbuild"
|
|
445
548
|
check_esbuild_dependencies
|
|
446
549
|
else
|
|
@@ -451,7 +554,7 @@ module Shakapacker
|
|
|
451
554
|
end
|
|
452
555
|
end
|
|
453
556
|
|
|
454
|
-
check_transpiler_config_consistency
|
|
557
|
+
check_transpiler_config_consistency(transpiler, inferred_hybrid_graph: inferred_hybrid_graph)
|
|
455
558
|
end
|
|
456
559
|
|
|
457
560
|
def check_babel_dependencies
|
|
@@ -505,7 +608,9 @@ module Shakapacker
|
|
|
505
608
|
end
|
|
506
609
|
end
|
|
507
610
|
|
|
508
|
-
def check_transpiler_config_consistency
|
|
611
|
+
def check_transpiler_config_consistency(transpiler = javascript_transpiler, inferred_hybrid_graph: nil)
|
|
612
|
+
inferred_hybrid_graph = inferred_hybrid_loader_graph?(transpiler, assets_bundler) if inferred_hybrid_graph.nil?
|
|
613
|
+
|
|
509
614
|
babel_configs = [
|
|
510
615
|
root_path.join(".babelrc"),
|
|
511
616
|
root_path.join(".babelrc.js"),
|
|
@@ -519,14 +624,11 @@ module Shakapacker
|
|
|
519
624
|
|
|
520
625
|
# Check if package.json has babel config
|
|
521
626
|
if package_json_exists?
|
|
522
|
-
|
|
523
|
-
babel_in_package_json = package_json.key?("babel")
|
|
627
|
+
babel_in_package_json = package_json_key?("babel")
|
|
524
628
|
babel_config_exists ||= babel_in_package_json
|
|
525
629
|
end
|
|
526
630
|
|
|
527
|
-
transpiler
|
|
528
|
-
|
|
529
|
-
if babel_config_exists && transpiler != "babel"
|
|
631
|
+
if babel_config_exists && transpiler != "babel" && !inferred_hybrid_graph
|
|
530
632
|
babel_files = babel_configs.select(&:exist?).map { |f| f.relative_path_from(root_path) }
|
|
531
633
|
babel_files << "package.json" if babel_in_package_json
|
|
532
634
|
babel_files_str = babel_files.join(", ")
|
|
@@ -535,7 +637,7 @@ module Shakapacker
|
|
|
535
637
|
end
|
|
536
638
|
|
|
537
639
|
# Check for redundant dependencies
|
|
538
|
-
if transpiler == "swc" && package_installed?("babel-loader")
|
|
640
|
+
if transpiler == "swc" && package_installed?("babel-loader") && !inferred_hybrid_graph
|
|
539
641
|
add_warning("Both SWC and Babel dependencies are installed. Consider removing Babel dependencies to reduce node_modules size")
|
|
540
642
|
end
|
|
541
643
|
|
|
@@ -549,6 +651,49 @@ module Shakapacker
|
|
|
549
651
|
end
|
|
550
652
|
end
|
|
551
653
|
|
|
654
|
+
def inferred_hybrid_loader_graph?(transpiler, bundler, unconfigured_hybrid_graph: nil)
|
|
655
|
+
unconfigured_hybrid_graph = unconfigured_hybrid_loader_graph? if unconfigured_hybrid_graph.nil?
|
|
656
|
+
|
|
657
|
+
transpiler == "swc" &&
|
|
658
|
+
bundler == "webpack" &&
|
|
659
|
+
unconfigured_hybrid_graph
|
|
660
|
+
end
|
|
661
|
+
|
|
662
|
+
def unconfigured_hybrid_loader_graph?
|
|
663
|
+
!javascript_transpiler_configured? &&
|
|
664
|
+
!assets_bundler_configured? &&
|
|
665
|
+
package_installed?("webpack") &&
|
|
666
|
+
package_installed?("@rspack/core") &&
|
|
667
|
+
inferred_hybrid_bundler_config_present? &&
|
|
668
|
+
custom_hybrid_loader_dependency?
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
def inferred_hybrid_bundler_config_present?
|
|
672
|
+
same_directory_hybrid_config_present?(config.assets_bundler_config_path.to_s) ||
|
|
673
|
+
default_split_hybrid_config_present?
|
|
674
|
+
end
|
|
675
|
+
|
|
676
|
+
def same_directory_hybrid_config_present?(directory)
|
|
677
|
+
bundler_config_present?(directory, "webpack") &&
|
|
678
|
+
bundler_config_present?(directory, "rspack")
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
def default_split_hybrid_config_present?
|
|
682
|
+
bundler_config_present?("config/webpack", "webpack") &&
|
|
683
|
+
(bundler_config_present?("config/rspack", "rspack") ||
|
|
684
|
+
bundler_config_present?("config/rspack", "webpack"))
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def bundler_config_present?(directory, basename)
|
|
688
|
+
BUNDLER_CONFIG_EXTENSIONS.any? do |extension|
|
|
689
|
+
Pathname.new(File.join(root_path.to_s, directory.to_s, "#{basename}.config.#{extension}")).exist?
|
|
690
|
+
end
|
|
691
|
+
end
|
|
692
|
+
|
|
693
|
+
def custom_hybrid_loader_dependency?
|
|
694
|
+
CUSTOM_HYBRID_LOADER_DEPS.any? { |package_name| package_installed?(package_name) }
|
|
695
|
+
end
|
|
696
|
+
|
|
552
697
|
def check_swc_config_conflicts
|
|
553
698
|
swcrc_path = root_path.join(".swcrc")
|
|
554
699
|
swc_config_path = root_path.join("config/swc.config.js")
|
|
@@ -602,11 +747,9 @@ module Shakapacker
|
|
|
602
747
|
def stimulus_likely_used?
|
|
603
748
|
return false unless package_json_exists?
|
|
604
749
|
|
|
605
|
-
package_json = read_package_json
|
|
606
|
-
dependencies = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
607
|
-
|
|
608
750
|
# Check for @hotwired/stimulus or stimulus package
|
|
609
|
-
|
|
751
|
+
declared_package_dependencies.key?("@hotwired/stimulus") ||
|
|
752
|
+
declared_package_dependencies.key?("stimulus")
|
|
610
753
|
end
|
|
611
754
|
|
|
612
755
|
def check_css_dependencies
|
|
@@ -681,7 +824,7 @@ module Shakapacker
|
|
|
681
824
|
end
|
|
682
825
|
|
|
683
826
|
def check_bundler_dependencies
|
|
684
|
-
bundler =
|
|
827
|
+
bundler = assets_bundler
|
|
685
828
|
case bundler
|
|
686
829
|
when "webpack"
|
|
687
830
|
check_dependency("webpack", @issues, "webpack")
|
|
@@ -698,9 +841,9 @@ module Shakapacker
|
|
|
698
841
|
rspack_major = rspack_major_version
|
|
699
842
|
|
|
700
843
|
if rspack_major == 1
|
|
701
|
-
add_warning("Rspack v1 detected:
|
|
702
|
-
"
|
|
703
|
-
add_fix_hint("Bump @rspack/core and @rspack/cli to ^2.0.0
|
|
844
|
+
add_warning("Rspack v1 detected: Shakapacker supports Rspack v2 only. " \
|
|
845
|
+
"Upgrade to Rspack v2 for supported builds and stable persistent caching.")
|
|
846
|
+
add_fix_hint("Bump @rspack/core and @rspack/cli to ^2.0.0 in package.json. See https://rspack.rs/config/cache and docs/rspack.md for details.")
|
|
704
847
|
end
|
|
705
848
|
|
|
706
849
|
path = active_assets_bundler_config_path
|
|
@@ -943,17 +1086,24 @@ module Shakapacker
|
|
|
943
1086
|
end
|
|
944
1087
|
|
|
945
1088
|
def rspack_major_version
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
resolved = installed_rspack_major_version
|
|
949
|
-
return resolved unless resolved.nil?
|
|
950
|
-
|
|
951
|
-
%w[@rspack/core @rspack/cli].each do |package_name|
|
|
952
|
-
major = rspack_major_from_specifier(package_json_dependency_version(package_name))
|
|
953
|
-
return major unless major.nil?
|
|
1089
|
+
majors = %w[@rspack/core @rspack/cli].filter_map do |package_name|
|
|
1090
|
+
rspack_major_version_for(package_name)
|
|
954
1091
|
end
|
|
955
1092
|
|
|
956
|
-
|
|
1093
|
+
return 1 if majors.include?(1)
|
|
1094
|
+
|
|
1095
|
+
majors.first
|
|
1096
|
+
end
|
|
1097
|
+
|
|
1098
|
+
def rspack_major_version_for(package_name)
|
|
1099
|
+
installed = installed_rspack_major_version(package_name)
|
|
1100
|
+
return installed if installed
|
|
1101
|
+
|
|
1102
|
+
rspack_declared_major_version_for(package_name)
|
|
1103
|
+
end
|
|
1104
|
+
|
|
1105
|
+
def rspack_declared_major_version_for(package_name)
|
|
1106
|
+
rspack_major_from_specifier(package_json_dependency_version(package_name))
|
|
957
1107
|
end
|
|
958
1108
|
|
|
959
1109
|
def rspack_major_from_specifier(version)
|
|
@@ -964,16 +1114,23 @@ module Shakapacker
|
|
|
964
1114
|
# ">=1.5 <2". Accept shorthand forms (e.g. `^1`, `~1`, `1`, `1.x`) so
|
|
965
1115
|
# we still emit the v1 advisory when node_modules isn't populated yet.
|
|
966
1116
|
cleaned = version.strip
|
|
1117
|
+
if cleaned.include?("||")
|
|
1118
|
+
majors = cleaned.split("||").filter_map { |specifier| rspack_major_from_specifier(specifier) }
|
|
1119
|
+
return 1 if majors.include?(1)
|
|
1120
|
+
|
|
1121
|
+
return majors.first
|
|
1122
|
+
end
|
|
1123
|
+
|
|
967
1124
|
return nil unless cleaned.match?(/\A[\^~]?\d/)
|
|
968
|
-
return nil if cleaned.match?(/(\s
|
|
1125
|
+
return nil if cleaned.match?(/(\s|[<>=:]|\A(?:git|file|link|workspace|npm):)/)
|
|
969
1126
|
return nil unless cleaned.match?(/\A[\^~]?\d+(?:\.(?:\d+|x|\*)){0,2}(?:-[0-9A-Za-z.-]+)?\z/i)
|
|
970
1127
|
|
|
971
1128
|
match = cleaned.sub(/\A[\^~]/, "").match(/\A(\d+)/)
|
|
972
1129
|
match && match[1].to_i
|
|
973
1130
|
end
|
|
974
1131
|
|
|
975
|
-
def installed_rspack_major_version
|
|
976
|
-
rspack_pkg =
|
|
1132
|
+
def installed_rspack_major_version(package_name)
|
|
1133
|
+
rspack_pkg = installed_package_json_path(package_name)
|
|
977
1134
|
return nil unless rspack_pkg.exist?
|
|
978
1135
|
|
|
979
1136
|
version = JSON.parse(File.read(rspack_pkg))["version"]
|
|
@@ -986,11 +1143,66 @@ module Shakapacker
|
|
|
986
1143
|
def package_json_dependency_version(name)
|
|
987
1144
|
return nil unless package_json_exists?
|
|
988
1145
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1146
|
+
declared_package_dependencies[name]
|
|
1147
|
+
end
|
|
1148
|
+
|
|
1149
|
+
def package_version_status(package_name, minimum_version)
|
|
1150
|
+
minimum = Gem::Version.new(minimum_version)
|
|
1151
|
+
declared_specifier = package_json_dependency_version(package_name)
|
|
1152
|
+
declared = package_version_from_specifier(declared_specifier)
|
|
1153
|
+
installed = installed_package_version(package_name)
|
|
1154
|
+
|
|
1155
|
+
{
|
|
1156
|
+
declared_below: declared && declared < minimum,
|
|
1157
|
+
installed_below: installed && installed < minimum
|
|
1158
|
+
}
|
|
1159
|
+
end
|
|
1160
|
+
|
|
1161
|
+
def package_version_from_specifier(version)
|
|
1162
|
+
return nil unless version
|
|
1163
|
+
|
|
1164
|
+
cleaned = version.strip
|
|
1165
|
+
if cleaned.include?("||")
|
|
1166
|
+
versions = cleaned.split("||").filter_map { |specifier| package_version_from_specifier(specifier) }
|
|
1167
|
+
return versions.min
|
|
1168
|
+
end
|
|
1169
|
+
|
|
1170
|
+
return nil unless cleaned.match?(/\A[\^~]?\d/)
|
|
1171
|
+
return nil if cleaned.match?(/(\s|\|\||[<>=:]|\A(?:git|file|link|workspace|npm):)/)
|
|
1172
|
+
|
|
1173
|
+
match = cleaned.sub(/\A[\^~]/, "").match(/\A(\d+(?:\.\d+){0,2})(?:-[0-9A-Za-z.-]+)?\z/)
|
|
1174
|
+
match && Gem::Version.new(match[1])
|
|
1175
|
+
rescue ArgumentError
|
|
1176
|
+
nil
|
|
1177
|
+
end
|
|
1178
|
+
|
|
1179
|
+
def declared_package_dependencies
|
|
1180
|
+
@declared_package_dependencies ||= begin
|
|
1181
|
+
package_json_paths.reverse_each.each_with_object({}) do |path, dependencies|
|
|
1182
|
+
package_json = parse_package_json(path)
|
|
1183
|
+
next unless package_json
|
|
1184
|
+
|
|
1185
|
+
dependencies.merge!(installable_package_dependencies(package_json))
|
|
1186
|
+
end
|
|
1187
|
+
end
|
|
1188
|
+
end
|
|
1189
|
+
|
|
1190
|
+
def installable_package_dependencies(package_json)
|
|
1191
|
+
# Later sections take precedence when the same package is declared in more than one section.
|
|
1192
|
+
(package_json["optionalDependencies"] || {})
|
|
1193
|
+
.merge(package_json["devDependencies"] || {})
|
|
1194
|
+
.merge(package_json["dependencies"] || {})
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1197
|
+
def installed_package_version(package_name)
|
|
1198
|
+
package_json = installed_package_json_path(package_name)
|
|
1199
|
+
return nil unless package_json.exist?
|
|
1200
|
+
|
|
1201
|
+
version = JSON.parse(File.read(package_json))["version"]
|
|
1202
|
+
match = version.to_s.match(/\A(\d+(?:\.\d+){0,2})/)
|
|
1203
|
+
match && Gem::Version.new(match[1])
|
|
1204
|
+
rescue JSON::ParserError, SystemCallError, ArgumentError
|
|
1205
|
+
nil
|
|
994
1206
|
end
|
|
995
1207
|
|
|
996
1208
|
def check_file_type_dependencies
|
|
@@ -1005,7 +1217,7 @@ module Shakapacker
|
|
|
1005
1217
|
end
|
|
1006
1218
|
|
|
1007
1219
|
def check_typescript_dependencies
|
|
1008
|
-
transpiler =
|
|
1220
|
+
transpiler = javascript_transpiler
|
|
1009
1221
|
if transpiler == "babel"
|
|
1010
1222
|
check_optional_dependency("@babel/preset-typescript", @warnings, "TypeScript with Babel")
|
|
1011
1223
|
elsif transpiler != "esbuild" && transpiler != "swc"
|
|
@@ -1015,7 +1227,9 @@ module Shakapacker
|
|
|
1015
1227
|
|
|
1016
1228
|
def check_sass_dependencies
|
|
1017
1229
|
check_dependency("sass-loader", @issues, "Sass/SCSS")
|
|
1018
|
-
|
|
1230
|
+
unless SASS_IMPLEMENTATION_PACKAGES.any? { |package_name| package_installed?(package_name) }
|
|
1231
|
+
@issues << SASS_IMPLEMENTATION_DEPENDENCY_MESSAGE
|
|
1232
|
+
end
|
|
1019
1233
|
end
|
|
1020
1234
|
|
|
1021
1235
|
def check_less_dependencies
|
|
@@ -1048,27 +1262,170 @@ module Shakapacker
|
|
|
1048
1262
|
def package_installed?(package_name)
|
|
1049
1263
|
return false unless package_json_exists?
|
|
1050
1264
|
|
|
1051
|
-
|
|
1052
|
-
dependencies = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
1053
|
-
dependencies.key?(package_name)
|
|
1265
|
+
declared_package_dependencies.key?(package_name)
|
|
1054
1266
|
end
|
|
1055
1267
|
|
|
1056
1268
|
def package_json_exists?
|
|
1057
|
-
|
|
1269
|
+
package_json_paths.any?
|
|
1270
|
+
end
|
|
1271
|
+
|
|
1272
|
+
def javascript_transpiler_configured?
|
|
1273
|
+
!javascript_transpiler_env_override.nil? ||
|
|
1274
|
+
config_key_defined?(:javascript_transpiler) ||
|
|
1275
|
+
config_key_defined?(:webpack_loader)
|
|
1058
1276
|
end
|
|
1059
1277
|
|
|
1060
|
-
def
|
|
1061
|
-
|
|
1278
|
+
def javascript_transpiler
|
|
1279
|
+
transpiler = javascript_transpiler_env_override || config.javascript_transpiler
|
|
1280
|
+
blank_config_value?(transpiler) ? default_javascript_transpiler : transpiler
|
|
1062
1281
|
end
|
|
1063
1282
|
|
|
1064
|
-
def
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1283
|
+
def explicit_javascript_transpiler
|
|
1284
|
+
return javascript_transpiler_env_override if javascript_transpiler_env_override
|
|
1285
|
+
return nil unless javascript_transpiler_configured?
|
|
1286
|
+
|
|
1287
|
+
javascript_transpiler
|
|
1288
|
+
end
|
|
1289
|
+
|
|
1290
|
+
def javascript_transpiler_env_override
|
|
1291
|
+
value = ENV["SHAKAPACKER_JAVASCRIPT_TRANSPILER"]
|
|
1292
|
+
return nil if value.nil? || value.empty?
|
|
1293
|
+
|
|
1294
|
+
value
|
|
1295
|
+
end
|
|
1296
|
+
|
|
1297
|
+
def assets_bundler_configured?
|
|
1298
|
+
assets_bundler_override_configured? ||
|
|
1299
|
+
ENV.key?("SHAKAPACKER_ASSETS_BUNDLER") ||
|
|
1300
|
+
!assets_bundler_env_override.nil? ||
|
|
1301
|
+
config_key_present?(:assets_bundler) ||
|
|
1302
|
+
config_key_present?(:bundler)
|
|
1303
|
+
end
|
|
1304
|
+
|
|
1305
|
+
def assets_bundler
|
|
1306
|
+
config.assets_bundler
|
|
1307
|
+
end
|
|
1308
|
+
|
|
1309
|
+
def assets_bundler_env_override
|
|
1310
|
+
value = ENV["SHAKAPACKER_ASSETS_BUNDLER"]
|
|
1311
|
+
return nil if value.nil? || value.empty?
|
|
1312
|
+
|
|
1313
|
+
value
|
|
1314
|
+
end
|
|
1315
|
+
|
|
1316
|
+
def assets_bundler_override_configured?
|
|
1317
|
+
config.respond_to?(:bundler_override) && !blank_config_value?(config.bundler_override)
|
|
1318
|
+
end
|
|
1319
|
+
|
|
1320
|
+
def empty_assets_bundler_env_override?
|
|
1321
|
+
ENV.key?("SHAKAPACKER_ASSETS_BUNDLER") && ENV["SHAKAPACKER_ASSETS_BUNDLER"].empty?
|
|
1322
|
+
end
|
|
1323
|
+
|
|
1324
|
+
def report_empty_assets_bundler_env_override
|
|
1325
|
+
return if @empty_assets_bundler_env_override_reported
|
|
1326
|
+
|
|
1327
|
+
@issues << "SHAKAPACKER_ASSETS_BUNDLER is set but empty. Unset it, or set it to 'webpack' or 'rspack'."
|
|
1328
|
+
@empty_assets_bundler_env_override_reported = true
|
|
1329
|
+
end
|
|
1330
|
+
|
|
1331
|
+
def blank_config_value?(value)
|
|
1332
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
1333
|
+
end
|
|
1334
|
+
|
|
1335
|
+
def default_javascript_transpiler
|
|
1336
|
+
assets_bundler == "rspack" ? "swc" : "babel"
|
|
1337
|
+
end
|
|
1338
|
+
|
|
1339
|
+
def config_key_present?(key)
|
|
1340
|
+
!blank_config_value?(config_value(key))
|
|
1341
|
+
end
|
|
1342
|
+
|
|
1343
|
+
def config_key_defined?(key)
|
|
1344
|
+
return false unless config.respond_to?(:data)
|
|
1345
|
+
|
|
1346
|
+
data = config.data
|
|
1347
|
+
data.respond_to?(:key?) && data.key?(key)
|
|
1348
|
+
end
|
|
1349
|
+
|
|
1350
|
+
def config_value(key)
|
|
1351
|
+
return nil unless config.respond_to?(:data)
|
|
1352
|
+
|
|
1353
|
+
data = config.data
|
|
1354
|
+
return nil unless data.respond_to?(:key?) && data.key?(key)
|
|
1355
|
+
|
|
1356
|
+
data[key]
|
|
1357
|
+
end
|
|
1358
|
+
|
|
1359
|
+
def parse_package_json(path)
|
|
1360
|
+
JSON.parse(File.read(path))
|
|
1361
|
+
rescue JSON::ParserError, SystemCallError
|
|
1362
|
+
nil
|
|
1363
|
+
end
|
|
1364
|
+
|
|
1365
|
+
def package_json_key?(key)
|
|
1366
|
+
package_json_paths.any? do |path|
|
|
1367
|
+
package_json = parse_package_json(path)
|
|
1368
|
+
package_json.is_a?(Hash) && package_json.key?(key)
|
|
1369
|
+
end
|
|
1370
|
+
end
|
|
1371
|
+
|
|
1372
|
+
def package_json_paths
|
|
1373
|
+
@package_json_paths ||= package_root_paths
|
|
1374
|
+
.map { |path| path.join("package.json") }
|
|
1375
|
+
.select(&:exist?)
|
|
1376
|
+
end
|
|
1377
|
+
|
|
1378
|
+
def javascript_package_root_path
|
|
1379
|
+
@javascript_package_root_path ||= begin
|
|
1380
|
+
source_path = config.source_path.expand_path
|
|
1381
|
+
app_root = root_path.expand_path
|
|
1382
|
+
|
|
1383
|
+
if path_within?(source_path, app_root)
|
|
1384
|
+
current = source_path
|
|
1385
|
+
loop do
|
|
1386
|
+
break current if package_root_marker?(current)
|
|
1387
|
+
break root_path if current == app_root
|
|
1388
|
+
|
|
1389
|
+
parent = current.dirname
|
|
1390
|
+
break root_path if parent == current
|
|
1391
|
+
|
|
1392
|
+
current = parent
|
|
1393
|
+
end
|
|
1394
|
+
else
|
|
1395
|
+
root_path
|
|
1396
|
+
end
|
|
1397
|
+
rescue StandardError
|
|
1398
|
+
root_path
|
|
1069
1399
|
end
|
|
1070
1400
|
end
|
|
1071
1401
|
|
|
1402
|
+
def node_modules_path
|
|
1403
|
+
node_modules_paths.first
|
|
1404
|
+
end
|
|
1405
|
+
|
|
1406
|
+
def node_modules_paths
|
|
1407
|
+
@node_modules_paths ||= package_root_paths.map { |path| path.join("node_modules") }
|
|
1408
|
+
end
|
|
1409
|
+
|
|
1410
|
+
def installed_package_json_path(package_name)
|
|
1411
|
+
node_modules_paths
|
|
1412
|
+
.map { |path| path.join(package_name, "package.json") }
|
|
1413
|
+
.find(&:exist?) || node_modules_path.join(package_name, "package.json")
|
|
1414
|
+
end
|
|
1415
|
+
|
|
1416
|
+
def package_root_paths
|
|
1417
|
+
@package_root_paths ||= [javascript_package_root_path, root_path].uniq
|
|
1418
|
+
end
|
|
1419
|
+
|
|
1420
|
+
def package_root_marker?(path)
|
|
1421
|
+
# Keep aligned with shakapacker_package_root_marker? in the helper binstubs.
|
|
1422
|
+
PACKAGE_ROOT_MARKERS.any? { |entry| path.join(entry).exist? }
|
|
1423
|
+
end
|
|
1424
|
+
|
|
1425
|
+
def path_within?(path, parent)
|
|
1426
|
+
path.to_s == parent.to_s || path.to_s.start_with?("#{parent}#{File::SEPARATOR}")
|
|
1427
|
+
end
|
|
1428
|
+
|
|
1072
1429
|
def config_exists?
|
|
1073
1430
|
config.config_path.exist?
|
|
1074
1431
|
end
|
|
@@ -1099,10 +1456,25 @@ module Shakapacker
|
|
|
1099
1456
|
end
|
|
1100
1457
|
|
|
1101
1458
|
def detect_package_manager
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1459
|
+
root_package_manager = package_manager_for(root_path)
|
|
1460
|
+
|
|
1461
|
+
package_root_paths.each do |package_root|
|
|
1462
|
+
next if package_root == root_path
|
|
1463
|
+
|
|
1464
|
+
package_manager_name = package_manager_for(package_root)
|
|
1465
|
+
next unless package_manager_name
|
|
1466
|
+
|
|
1467
|
+
return package_manager_name if package_root.join("package.json").exist? || root_package_manager.nil?
|
|
1468
|
+
end
|
|
1469
|
+
|
|
1470
|
+
root_package_manager
|
|
1471
|
+
end
|
|
1472
|
+
|
|
1473
|
+
def package_manager_for(package_root)
|
|
1474
|
+
PACKAGE_MANAGER_LOCKFILES.each do |lockfile, package_manager_name|
|
|
1475
|
+
return package_manager_name if File.exist?(package_root.join(lockfile))
|
|
1476
|
+
end
|
|
1477
|
+
|
|
1106
1478
|
nil
|
|
1107
1479
|
end
|
|
1108
1480
|
|
|
@@ -1176,7 +1548,7 @@ module Shakapacker
|
|
|
1176
1548
|
config_relative_path = doctor.config.config_path.relative_path_from(doctor.root_path)
|
|
1177
1549
|
puts "✓ Configuration file found (#{config_relative_path})"
|
|
1178
1550
|
if verbose?
|
|
1179
|
-
puts " Assets bundler: #{doctor.
|
|
1551
|
+
puts " Assets bundler: #{doctor.send(:assets_bundler)}"
|
|
1180
1552
|
puts " Source path: #{doctor.config.source_path.relative_path_from(doctor.root_path)}"
|
|
1181
1553
|
puts " Public output path: #{doctor.config.public_output_path.relative_path_from(doctor.root_path)}"
|
|
1182
1554
|
end
|
|
@@ -1212,9 +1584,7 @@ module Shakapacker
|
|
|
1212
1584
|
def print_version_info
|
|
1213
1585
|
return unless doctor.send(:package_json_exists?)
|
|
1214
1586
|
|
|
1215
|
-
|
|
1216
|
-
npm_version = package_json.dig("dependencies", "shakapacker") ||
|
|
1217
|
-
package_json.dig("devDependencies", "shakapacker")
|
|
1587
|
+
npm_version = doctor.send(:package_json_dependency_version, "shakapacker")
|
|
1218
1588
|
puts " • Shakapacker gem version: #{Shakapacker::VERSION}"
|
|
1219
1589
|
puts " • Shakapacker npm version: #{npm_version || 'not installed'}"
|
|
1220
1590
|
end
|
|
@@ -1275,7 +1645,7 @@ module Shakapacker
|
|
|
1275
1645
|
end
|
|
1276
1646
|
|
|
1277
1647
|
def print_transpiler_status
|
|
1278
|
-
transpiler = doctor.
|
|
1648
|
+
transpiler = doctor.send(:javascript_transpiler)
|
|
1279
1649
|
return if transpiler.nil? || transpiler == "none"
|
|
1280
1650
|
|
|
1281
1651
|
loader_name = "#{transpiler}-loader"
|
|
@@ -1285,7 +1655,7 @@ module Shakapacker
|
|
|
1285
1655
|
end
|
|
1286
1656
|
|
|
1287
1657
|
def print_bundler_status
|
|
1288
|
-
bundler = doctor.
|
|
1658
|
+
bundler = doctor.send(:assets_bundler)
|
|
1289
1659
|
case bundler
|
|
1290
1660
|
when "webpack"
|
|
1291
1661
|
print_package_status("webpack", "webpack")
|