shakapacker 10.1.0 → 10.3.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 +43 -1
- data/README.md +117 -1368
- data/lib/install/bin/diff-bundler-config +113 -40
- data/lib/install/bin/shakapacker-config +113 -40
- data/lib/install/config/shakapacker.yml +10 -2
- data/lib/install/package.json +6 -4
- 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 +214 -38
- data/lib/shakapacker/dev_server_runner.rb +44 -19
- data/lib/shakapacker/digest_strategy.rb +3 -3
- data/lib/shakapacker/doctor.rb +501 -79
- 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 +16 -12
- 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,24 +510,42 @@ module Shakapacker
|
|
|
423
510
|
end
|
|
424
511
|
|
|
425
512
|
def check_javascript_transpiler_dependencies
|
|
426
|
-
transpiler =
|
|
513
|
+
transpiler = explicit_javascript_transpiler
|
|
514
|
+
implicit_babel_fallback = false
|
|
427
515
|
|
|
428
|
-
# Default to SWC for v9+ if not configured
|
|
429
516
|
if transpiler.nil?
|
|
430
|
-
|
|
431
|
-
|
|
517
|
+
transpiler = javascript_transpiler
|
|
518
|
+
implicit_babel_fallback = transpiler == "babel"
|
|
432
519
|
end
|
|
433
520
|
|
|
521
|
+
@resolved_javascript_transpiler = transpiler
|
|
434
522
|
return if transpiler == "none"
|
|
435
523
|
|
|
436
|
-
bundler =
|
|
524
|
+
bundler = assets_bundler
|
|
525
|
+
unconfigured_hybrid_graph = unconfigured_hybrid_loader_graph?
|
|
526
|
+
inferred_hybrid_graph = inferred_hybrid_loader_graph?(
|
|
527
|
+
transpiler,
|
|
528
|
+
bundler,
|
|
529
|
+
unconfigured_hybrid_graph: unconfigured_hybrid_graph
|
|
530
|
+
)
|
|
531
|
+
if inferred_hybrid_graph
|
|
532
|
+
add_info_warning("Detected a custom hybrid webpack/Rspack setup while Doctor inferred webpack/SWC. " \
|
|
533
|
+
"Skipping SWC dependency issue checks for this inferred default. For custom hybrid webpack/Rspack configs, " \
|
|
534
|
+
"set javascript_transpiler: \"none\" when Shakapacker should not validate loader dependencies, " \
|
|
535
|
+
"or set javascript_transpiler/assets_bundler explicitly when Shakapacker owns that build path.")
|
|
536
|
+
elsif unconfigured_hybrid_graph
|
|
537
|
+
add_info_warning("Detected a custom hybrid webpack/Rspack setup with inferred Shakapacker defaults. " \
|
|
538
|
+
"Doctor is validating the active #{bundler}/#{transpiler} default only. " \
|
|
539
|
+
"Set javascript_transpiler: \"none\" when Shakapacker should not validate loader dependencies, " \
|
|
540
|
+
"or set javascript_transpiler/assets_bundler explicitly when Shakapacker owns that build path.")
|
|
541
|
+
end
|
|
437
542
|
|
|
438
543
|
case transpiler
|
|
439
544
|
when "babel"
|
|
440
545
|
check_babel_dependencies
|
|
441
|
-
check_babel_performance_suggestion
|
|
546
|
+
check_babel_performance_suggestion unless implicit_babel_fallback
|
|
442
547
|
when "swc"
|
|
443
|
-
check_swc_dependencies(bundler)
|
|
548
|
+
check_swc_dependencies(bundler) unless inferred_hybrid_graph
|
|
444
549
|
when "esbuild"
|
|
445
550
|
check_esbuild_dependencies
|
|
446
551
|
else
|
|
@@ -451,7 +556,7 @@ module Shakapacker
|
|
|
451
556
|
end
|
|
452
557
|
end
|
|
453
558
|
|
|
454
|
-
check_transpiler_config_consistency
|
|
559
|
+
check_transpiler_config_consistency(transpiler, inferred_hybrid_graph: inferred_hybrid_graph)
|
|
455
560
|
end
|
|
456
561
|
|
|
457
562
|
def check_babel_dependencies
|
|
@@ -470,6 +575,20 @@ module Shakapacker
|
|
|
470
575
|
@info << "Consider switching to SWC for 20x faster compilation. Set javascript_transpiler: 'swc' in shakapacker.yml"
|
|
471
576
|
end
|
|
472
577
|
|
|
578
|
+
def implicit_javascript_transpiler
|
|
579
|
+
if assets_bundler == "webpack" && !package_installed?("swc-loader") && package_installed?("babel-loader")
|
|
580
|
+
@info << "`javascript_transpiler` is not set in config/shakapacker.yml. " \
|
|
581
|
+
"Shakapacker defaults to SWC, but swc-loader is not installed and Babel was detected, so Babel will be used. " \
|
|
582
|
+
"Set `javascript_transpiler: babel` (or `swc`) explicitly to silence this message. " \
|
|
583
|
+
"See https://github.com/shakacode/shakapacker/blob/main/docs/transpiler-migration.md"
|
|
584
|
+
"babel"
|
|
585
|
+
else
|
|
586
|
+
@info << "No javascript_transpiler configured - using bundled SWC default. " \
|
|
587
|
+
"Set javascript_transpiler: 'swc' or 'babel' explicitly in shakapacker.yml to silence this message."
|
|
588
|
+
"swc"
|
|
589
|
+
end
|
|
590
|
+
end
|
|
591
|
+
|
|
473
592
|
def check_swc_dependencies(bundler)
|
|
474
593
|
if bundler == "webpack"
|
|
475
594
|
unless package_installed?("@swc/core")
|
|
@@ -505,7 +624,9 @@ module Shakapacker
|
|
|
505
624
|
end
|
|
506
625
|
end
|
|
507
626
|
|
|
508
|
-
def check_transpiler_config_consistency
|
|
627
|
+
def check_transpiler_config_consistency(transpiler = javascript_transpiler, inferred_hybrid_graph: nil)
|
|
628
|
+
inferred_hybrid_graph = inferred_hybrid_loader_graph?(transpiler, assets_bundler) if inferred_hybrid_graph.nil?
|
|
629
|
+
|
|
509
630
|
babel_configs = [
|
|
510
631
|
root_path.join(".babelrc"),
|
|
511
632
|
root_path.join(".babelrc.js"),
|
|
@@ -519,14 +640,11 @@ module Shakapacker
|
|
|
519
640
|
|
|
520
641
|
# Check if package.json has babel config
|
|
521
642
|
if package_json_exists?
|
|
522
|
-
|
|
523
|
-
babel_in_package_json = package_json.key?("babel")
|
|
643
|
+
babel_in_package_json = package_json_key?("babel")
|
|
524
644
|
babel_config_exists ||= babel_in_package_json
|
|
525
645
|
end
|
|
526
646
|
|
|
527
|
-
transpiler
|
|
528
|
-
|
|
529
|
-
if babel_config_exists && transpiler != "babel"
|
|
647
|
+
if babel_config_exists && transpiler != "babel" && !inferred_hybrid_graph
|
|
530
648
|
babel_files = babel_configs.select(&:exist?).map { |f| f.relative_path_from(root_path) }
|
|
531
649
|
babel_files << "package.json" if babel_in_package_json
|
|
532
650
|
babel_files_str = babel_files.join(", ")
|
|
@@ -535,7 +653,7 @@ module Shakapacker
|
|
|
535
653
|
end
|
|
536
654
|
|
|
537
655
|
# Check for redundant dependencies
|
|
538
|
-
if transpiler == "swc" && package_installed?("babel-loader")
|
|
656
|
+
if transpiler == "swc" && package_installed?("babel-loader") && !inferred_hybrid_graph
|
|
539
657
|
add_warning("Both SWC and Babel dependencies are installed. Consider removing Babel dependencies to reduce node_modules size")
|
|
540
658
|
end
|
|
541
659
|
|
|
@@ -549,6 +667,49 @@ module Shakapacker
|
|
|
549
667
|
end
|
|
550
668
|
end
|
|
551
669
|
|
|
670
|
+
def inferred_hybrid_loader_graph?(transpiler, bundler, unconfigured_hybrid_graph: nil)
|
|
671
|
+
unconfigured_hybrid_graph = unconfigured_hybrid_loader_graph? if unconfigured_hybrid_graph.nil?
|
|
672
|
+
|
|
673
|
+
transpiler == "swc" &&
|
|
674
|
+
bundler == "webpack" &&
|
|
675
|
+
unconfigured_hybrid_graph
|
|
676
|
+
end
|
|
677
|
+
|
|
678
|
+
def unconfigured_hybrid_loader_graph?
|
|
679
|
+
!javascript_transpiler_configured? &&
|
|
680
|
+
!assets_bundler_configured? &&
|
|
681
|
+
package_installed?("webpack") &&
|
|
682
|
+
package_installed?("@rspack/core") &&
|
|
683
|
+
inferred_hybrid_bundler_config_present? &&
|
|
684
|
+
custom_hybrid_loader_dependency?
|
|
685
|
+
end
|
|
686
|
+
|
|
687
|
+
def inferred_hybrid_bundler_config_present?
|
|
688
|
+
same_directory_hybrid_config_present?(config.assets_bundler_config_path.to_s) ||
|
|
689
|
+
default_split_hybrid_config_present?
|
|
690
|
+
end
|
|
691
|
+
|
|
692
|
+
def same_directory_hybrid_config_present?(directory)
|
|
693
|
+
bundler_config_present?(directory, "webpack") &&
|
|
694
|
+
bundler_config_present?(directory, "rspack")
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
def default_split_hybrid_config_present?
|
|
698
|
+
bundler_config_present?("config/webpack", "webpack") &&
|
|
699
|
+
(bundler_config_present?("config/rspack", "rspack") ||
|
|
700
|
+
bundler_config_present?("config/rspack", "webpack"))
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
def bundler_config_present?(directory, basename)
|
|
704
|
+
BUNDLER_CONFIG_EXTENSIONS.any? do |extension|
|
|
705
|
+
Pathname.new(File.join(root_path.to_s, directory.to_s, "#{basename}.config.#{extension}")).exist?
|
|
706
|
+
end
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def custom_hybrid_loader_dependency?
|
|
710
|
+
CUSTOM_HYBRID_LOADER_DEPS.any? { |package_name| package_installed?(package_name) }
|
|
711
|
+
end
|
|
712
|
+
|
|
552
713
|
def check_swc_config_conflicts
|
|
553
714
|
swcrc_path = root_path.join(".swcrc")
|
|
554
715
|
swc_config_path = root_path.join("config/swc.config.js")
|
|
@@ -602,11 +763,9 @@ module Shakapacker
|
|
|
602
763
|
def stimulus_likely_used?
|
|
603
764
|
return false unless package_json_exists?
|
|
604
765
|
|
|
605
|
-
package_json = read_package_json
|
|
606
|
-
dependencies = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
607
|
-
|
|
608
766
|
# Check for @hotwired/stimulus or stimulus package
|
|
609
|
-
|
|
767
|
+
declared_package_dependencies.key?("@hotwired/stimulus") ||
|
|
768
|
+
declared_package_dependencies.key?("stimulus")
|
|
610
769
|
end
|
|
611
770
|
|
|
612
771
|
def check_css_dependencies
|
|
@@ -681,7 +840,7 @@ module Shakapacker
|
|
|
681
840
|
end
|
|
682
841
|
|
|
683
842
|
def check_bundler_dependencies
|
|
684
|
-
bundler =
|
|
843
|
+
bundler = assets_bundler
|
|
685
844
|
case bundler
|
|
686
845
|
when "webpack"
|
|
687
846
|
check_dependency("webpack", @issues, "webpack")
|
|
@@ -698,9 +857,9 @@ module Shakapacker
|
|
|
698
857
|
rspack_major = rspack_major_version
|
|
699
858
|
|
|
700
859
|
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
|
|
860
|
+
add_warning("Rspack v1 detected: Shakapacker supports Rspack v2 only. " \
|
|
861
|
+
"Upgrade to Rspack v2 for supported builds and stable persistent caching.")
|
|
862
|
+
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
863
|
end
|
|
705
864
|
|
|
706
865
|
path = active_assets_bundler_config_path
|
|
@@ -943,17 +1102,24 @@ module Shakapacker
|
|
|
943
1102
|
end
|
|
944
1103
|
|
|
945
1104
|
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?
|
|
1105
|
+
majors = %w[@rspack/core @rspack/cli].filter_map do |package_name|
|
|
1106
|
+
rspack_major_version_for(package_name)
|
|
954
1107
|
end
|
|
955
1108
|
|
|
956
|
-
|
|
1109
|
+
return 1 if majors.include?(1)
|
|
1110
|
+
|
|
1111
|
+
majors.first
|
|
1112
|
+
end
|
|
1113
|
+
|
|
1114
|
+
def rspack_major_version_for(package_name)
|
|
1115
|
+
installed = installed_rspack_major_version(package_name)
|
|
1116
|
+
return installed if installed
|
|
1117
|
+
|
|
1118
|
+
rspack_declared_major_version_for(package_name)
|
|
1119
|
+
end
|
|
1120
|
+
|
|
1121
|
+
def rspack_declared_major_version_for(package_name)
|
|
1122
|
+
rspack_major_from_specifier(package_json_dependency_version(package_name))
|
|
957
1123
|
end
|
|
958
1124
|
|
|
959
1125
|
def rspack_major_from_specifier(version)
|
|
@@ -964,16 +1130,23 @@ module Shakapacker
|
|
|
964
1130
|
# ">=1.5 <2". Accept shorthand forms (e.g. `^1`, `~1`, `1`, `1.x`) so
|
|
965
1131
|
# we still emit the v1 advisory when node_modules isn't populated yet.
|
|
966
1132
|
cleaned = version.strip
|
|
1133
|
+
if cleaned.include?("||")
|
|
1134
|
+
majors = cleaned.split("||").filter_map { |specifier| rspack_major_from_specifier(specifier) }
|
|
1135
|
+
return 1 if majors.include?(1)
|
|
1136
|
+
|
|
1137
|
+
return majors.first
|
|
1138
|
+
end
|
|
1139
|
+
|
|
967
1140
|
return nil unless cleaned.match?(/\A[\^~]?\d/)
|
|
968
|
-
return nil if cleaned.match?(/(\s
|
|
1141
|
+
return nil if cleaned.match?(/(\s|[<>=:]|\A(?:git|file|link|workspace|npm):)/)
|
|
969
1142
|
return nil unless cleaned.match?(/\A[\^~]?\d+(?:\.(?:\d+|x|\*)){0,2}(?:-[0-9A-Za-z.-]+)?\z/i)
|
|
970
1143
|
|
|
971
1144
|
match = cleaned.sub(/\A[\^~]/, "").match(/\A(\d+)/)
|
|
972
1145
|
match && match[1].to_i
|
|
973
1146
|
end
|
|
974
1147
|
|
|
975
|
-
def installed_rspack_major_version
|
|
976
|
-
rspack_pkg =
|
|
1148
|
+
def installed_rspack_major_version(package_name)
|
|
1149
|
+
rspack_pkg = installed_package_json_path(package_name)
|
|
977
1150
|
return nil unless rspack_pkg.exist?
|
|
978
1151
|
|
|
979
1152
|
version = JSON.parse(File.read(rspack_pkg))["version"]
|
|
@@ -986,11 +1159,66 @@ module Shakapacker
|
|
|
986
1159
|
def package_json_dependency_version(name)
|
|
987
1160
|
return nil unless package_json_exists?
|
|
988
1161
|
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
1162
|
+
declared_package_dependencies[name]
|
|
1163
|
+
end
|
|
1164
|
+
|
|
1165
|
+
def package_version_status(package_name, minimum_version)
|
|
1166
|
+
minimum = Gem::Version.new(minimum_version)
|
|
1167
|
+
declared_specifier = package_json_dependency_version(package_name)
|
|
1168
|
+
declared = package_version_from_specifier(declared_specifier)
|
|
1169
|
+
installed = installed_package_version(package_name)
|
|
1170
|
+
|
|
1171
|
+
{
|
|
1172
|
+
declared_below: declared && declared < minimum,
|
|
1173
|
+
installed_below: installed && installed < minimum
|
|
1174
|
+
}
|
|
1175
|
+
end
|
|
1176
|
+
|
|
1177
|
+
def package_version_from_specifier(version)
|
|
1178
|
+
return nil unless version
|
|
1179
|
+
|
|
1180
|
+
cleaned = version.strip
|
|
1181
|
+
if cleaned.include?("||")
|
|
1182
|
+
versions = cleaned.split("||").filter_map { |specifier| package_version_from_specifier(specifier) }
|
|
1183
|
+
return versions.min
|
|
1184
|
+
end
|
|
1185
|
+
|
|
1186
|
+
return nil unless cleaned.match?(/\A[\^~]?\d/)
|
|
1187
|
+
return nil if cleaned.match?(/(\s|\|\||[<>=:]|\A(?:git|file|link|workspace|npm):)/)
|
|
1188
|
+
|
|
1189
|
+
match = cleaned.sub(/\A[\^~]/, "").match(/\A(\d+(?:\.\d+){0,2})(?:-[0-9A-Za-z.-]+)?\z/)
|
|
1190
|
+
match && Gem::Version.new(match[1])
|
|
1191
|
+
rescue ArgumentError
|
|
1192
|
+
nil
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
def declared_package_dependencies
|
|
1196
|
+
@declared_package_dependencies ||= begin
|
|
1197
|
+
package_json_paths.reverse_each.each_with_object({}) do |path, dependencies|
|
|
1198
|
+
package_json = parse_package_json(path)
|
|
1199
|
+
next unless package_json
|
|
1200
|
+
|
|
1201
|
+
dependencies.merge!(installable_package_dependencies(package_json))
|
|
1202
|
+
end
|
|
1203
|
+
end
|
|
1204
|
+
end
|
|
1205
|
+
|
|
1206
|
+
def installable_package_dependencies(package_json)
|
|
1207
|
+
# Later sections take precedence when the same package is declared in more than one section.
|
|
1208
|
+
(package_json["optionalDependencies"] || {})
|
|
1209
|
+
.merge(package_json["devDependencies"] || {})
|
|
1210
|
+
.merge(package_json["dependencies"] || {})
|
|
1211
|
+
end
|
|
1212
|
+
|
|
1213
|
+
def installed_package_version(package_name)
|
|
1214
|
+
package_json = installed_package_json_path(package_name)
|
|
1215
|
+
return nil unless package_json.exist?
|
|
1216
|
+
|
|
1217
|
+
version = JSON.parse(File.read(package_json))["version"]
|
|
1218
|
+
match = version.to_s.match(/\A(\d+(?:\.\d+){0,2})/)
|
|
1219
|
+
match && Gem::Version.new(match[1])
|
|
1220
|
+
rescue JSON::ParserError, SystemCallError, ArgumentError
|
|
1221
|
+
nil
|
|
994
1222
|
end
|
|
995
1223
|
|
|
996
1224
|
def check_file_type_dependencies
|
|
@@ -1005,7 +1233,7 @@ module Shakapacker
|
|
|
1005
1233
|
end
|
|
1006
1234
|
|
|
1007
1235
|
def check_typescript_dependencies
|
|
1008
|
-
transpiler =
|
|
1236
|
+
transpiler = javascript_transpiler
|
|
1009
1237
|
if transpiler == "babel"
|
|
1010
1238
|
check_optional_dependency("@babel/preset-typescript", @warnings, "TypeScript with Babel")
|
|
1011
1239
|
elsif transpiler != "esbuild" && transpiler != "swc"
|
|
@@ -1015,7 +1243,9 @@ module Shakapacker
|
|
|
1015
1243
|
|
|
1016
1244
|
def check_sass_dependencies
|
|
1017
1245
|
check_dependency("sass-loader", @issues, "Sass/SCSS")
|
|
1018
|
-
|
|
1246
|
+
unless SASS_IMPLEMENTATION_PACKAGES.any? { |package_name| package_installed?(package_name) }
|
|
1247
|
+
@issues << SASS_IMPLEMENTATION_DEPENDENCY_MESSAGE
|
|
1248
|
+
end
|
|
1019
1249
|
end
|
|
1020
1250
|
|
|
1021
1251
|
def check_less_dependencies
|
|
@@ -1048,27 +1278,178 @@ module Shakapacker
|
|
|
1048
1278
|
def package_installed?(package_name)
|
|
1049
1279
|
return false unless package_json_exists?
|
|
1050
1280
|
|
|
1051
|
-
|
|
1052
|
-
dependencies = (package_json["dependencies"] || {}).merge(package_json["devDependencies"] || {})
|
|
1053
|
-
dependencies.key?(package_name)
|
|
1281
|
+
declared_package_dependencies.key?(package_name)
|
|
1054
1282
|
end
|
|
1055
1283
|
|
|
1056
1284
|
def package_json_exists?
|
|
1057
|
-
|
|
1285
|
+
package_json_paths.any?
|
|
1286
|
+
end
|
|
1287
|
+
|
|
1288
|
+
def javascript_transpiler_configured?
|
|
1289
|
+
!javascript_transpiler_env_override.nil? ||
|
|
1290
|
+
config_key_configured?(:javascript_transpiler) ||
|
|
1291
|
+
config_key_present?(:webpack_loader)
|
|
1292
|
+
end
|
|
1293
|
+
|
|
1294
|
+
def javascript_transpiler
|
|
1295
|
+
return @resolved_javascript_transpiler if defined?(@resolved_javascript_transpiler)
|
|
1296
|
+
|
|
1297
|
+
@resolved_javascript_transpiler =
|
|
1298
|
+
if javascript_transpiler_configured?
|
|
1299
|
+
transpiler = javascript_transpiler_env_override || config.javascript_transpiler
|
|
1300
|
+
blank_config_value?(transpiler) ? default_javascript_transpiler : transpiler
|
|
1301
|
+
else
|
|
1302
|
+
implicit_javascript_transpiler
|
|
1303
|
+
end
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
def explicit_javascript_transpiler
|
|
1307
|
+
return javascript_transpiler_env_override if javascript_transpiler_env_override
|
|
1308
|
+
return nil unless javascript_transpiler_configured?
|
|
1309
|
+
|
|
1310
|
+
javascript_transpiler
|
|
1311
|
+
end
|
|
1312
|
+
|
|
1313
|
+
def javascript_transpiler_env_override
|
|
1314
|
+
value = ENV["SHAKAPACKER_JAVASCRIPT_TRANSPILER"]
|
|
1315
|
+
return nil if value.nil? || value.empty?
|
|
1316
|
+
|
|
1317
|
+
value
|
|
1318
|
+
end
|
|
1319
|
+
|
|
1320
|
+
def assets_bundler_configured?
|
|
1321
|
+
assets_bundler_override_configured? ||
|
|
1322
|
+
ENV.key?("SHAKAPACKER_ASSETS_BUNDLER") ||
|
|
1323
|
+
!assets_bundler_env_override.nil? ||
|
|
1324
|
+
config_key_present?(:assets_bundler) ||
|
|
1325
|
+
config_key_present?(:bundler)
|
|
1326
|
+
end
|
|
1327
|
+
|
|
1328
|
+
def assets_bundler
|
|
1329
|
+
config.assets_bundler
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
def assets_bundler_env_override
|
|
1333
|
+
value = ENV["SHAKAPACKER_ASSETS_BUNDLER"]
|
|
1334
|
+
return nil if value.nil? || value.empty?
|
|
1335
|
+
|
|
1336
|
+
value
|
|
1337
|
+
end
|
|
1338
|
+
|
|
1339
|
+
def assets_bundler_override_configured?
|
|
1340
|
+
config.respond_to?(:bundler_override) && !blank_config_value?(config.bundler_override)
|
|
1341
|
+
end
|
|
1342
|
+
|
|
1343
|
+
def empty_assets_bundler_env_override?
|
|
1344
|
+
ENV.key?("SHAKAPACKER_ASSETS_BUNDLER") && ENV["SHAKAPACKER_ASSETS_BUNDLER"].empty?
|
|
1345
|
+
end
|
|
1346
|
+
|
|
1347
|
+
def report_empty_assets_bundler_env_override
|
|
1348
|
+
return if @empty_assets_bundler_env_override_reported
|
|
1349
|
+
|
|
1350
|
+
@issues << "SHAKAPACKER_ASSETS_BUNDLER is set but empty. Unset it, or set it to 'webpack' or 'rspack'."
|
|
1351
|
+
@empty_assets_bundler_env_override_reported = true
|
|
1352
|
+
end
|
|
1353
|
+
|
|
1354
|
+
def blank_config_value?(value)
|
|
1355
|
+
value.nil? || (value.is_a?(String) && value.strip.empty?) || (value.respond_to?(:empty?) && value.empty?)
|
|
1356
|
+
end
|
|
1357
|
+
|
|
1358
|
+
def default_javascript_transpiler
|
|
1359
|
+
assets_bundler == "rspack" ? "swc" : "babel"
|
|
1360
|
+
end
|
|
1361
|
+
|
|
1362
|
+
def config_key_present?(key)
|
|
1363
|
+
value = config_value(key)
|
|
1364
|
+
value.is_a?(String) && !value.strip.empty?
|
|
1365
|
+
end
|
|
1366
|
+
|
|
1367
|
+
def config_key_configured?(key)
|
|
1368
|
+
return false unless config.respond_to?(:data)
|
|
1369
|
+
|
|
1370
|
+
data = config.data
|
|
1371
|
+
data.respond_to?(:key?) && data.key?(key) && !blank_config_value?(data[key])
|
|
1372
|
+
end
|
|
1373
|
+
|
|
1374
|
+
def config_value(key)
|
|
1375
|
+
return nil unless config.respond_to?(:data)
|
|
1376
|
+
|
|
1377
|
+
data = config.data
|
|
1378
|
+
return nil unless data.respond_to?(:key?) && data.key?(key)
|
|
1379
|
+
|
|
1380
|
+
data[key]
|
|
1058
1381
|
end
|
|
1059
1382
|
|
|
1060
|
-
def
|
|
1061
|
-
|
|
1383
|
+
def parse_package_json(path)
|
|
1384
|
+
JSON.parse(File.read(path))
|
|
1385
|
+
rescue JSON::ParserError, SystemCallError
|
|
1386
|
+
nil
|
|
1387
|
+
end
|
|
1388
|
+
|
|
1389
|
+
def package_json_key?(key)
|
|
1390
|
+
package_json_paths.any? do |path|
|
|
1391
|
+
package_json = parse_package_json(path)
|
|
1392
|
+
package_json.is_a?(Hash) && package_json.key?(key)
|
|
1393
|
+
end
|
|
1394
|
+
end
|
|
1395
|
+
|
|
1396
|
+
def package_json_paths
|
|
1397
|
+
@package_json_paths ||= package_root_paths
|
|
1398
|
+
.map { |path| path.join("package.json") }
|
|
1399
|
+
.select(&:exist?)
|
|
1062
1400
|
end
|
|
1063
1401
|
|
|
1064
|
-
def
|
|
1065
|
-
@
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1402
|
+
def javascript_package_root_path
|
|
1403
|
+
@javascript_package_root_path ||= begin
|
|
1404
|
+
source_path = config.source_path.expand_path
|
|
1405
|
+
app_root = root_path.expand_path
|
|
1406
|
+
|
|
1407
|
+
if path_within?(source_path, app_root)
|
|
1408
|
+
current = source_path
|
|
1409
|
+
loop do
|
|
1410
|
+
break current if current.join("package.json").exist?
|
|
1411
|
+
break root_path if current == app_root
|
|
1412
|
+
|
|
1413
|
+
parent = current.dirname
|
|
1414
|
+
break root_path if parent == current
|
|
1415
|
+
|
|
1416
|
+
current = parent
|
|
1417
|
+
end
|
|
1418
|
+
else
|
|
1419
|
+
root_path
|
|
1420
|
+
end
|
|
1421
|
+
rescue StandardError
|
|
1422
|
+
root_path
|
|
1069
1423
|
end
|
|
1070
1424
|
end
|
|
1071
1425
|
|
|
1426
|
+
def node_modules_path
|
|
1427
|
+
node_modules_paths.first
|
|
1428
|
+
end
|
|
1429
|
+
|
|
1430
|
+
def node_modules_paths
|
|
1431
|
+
@node_modules_paths ||= package_root_paths.map { |path| path.join("node_modules") }
|
|
1432
|
+
end
|
|
1433
|
+
|
|
1434
|
+
def installed_package_json_path(package_name)
|
|
1435
|
+
node_modules_paths
|
|
1436
|
+
.map { |path| path.join(package_name, "package.json") }
|
|
1437
|
+
.find(&:exist?) || node_modules_path.join(package_name, "package.json")
|
|
1438
|
+
end
|
|
1439
|
+
|
|
1440
|
+
def package_root_paths
|
|
1441
|
+
@package_root_paths ||= [javascript_package_root_path, root_path].uniq
|
|
1442
|
+
end
|
|
1443
|
+
|
|
1444
|
+
def package_root_marker?(path)
|
|
1445
|
+
# Keep aligned with shakapacker_package_root_marker? in the helper binstubs.
|
|
1446
|
+
PACKAGE_ROOT_MARKERS.any? { |entry| path.join(entry).exist? }
|
|
1447
|
+
end
|
|
1448
|
+
|
|
1449
|
+
def path_within?(path, parent)
|
|
1450
|
+
path.to_s == parent.to_s || path.to_s.start_with?("#{parent}#{File::SEPARATOR}")
|
|
1451
|
+
end
|
|
1452
|
+
|
|
1072
1453
|
def config_exists?
|
|
1073
1454
|
config.config_path.exist?
|
|
1074
1455
|
end
|
|
@@ -1099,13 +1480,56 @@ module Shakapacker
|
|
|
1099
1480
|
end
|
|
1100
1481
|
|
|
1101
1482
|
def detect_package_manager
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1483
|
+
root_package_manager = package_manager_for(root_path)
|
|
1484
|
+
|
|
1485
|
+
package_manager_root_paths.each do |package_root|
|
|
1486
|
+
next if package_root == root_path
|
|
1487
|
+
|
|
1488
|
+
package_manager_name = package_manager_for(package_root)
|
|
1489
|
+
next unless package_manager_name
|
|
1490
|
+
|
|
1491
|
+
return package_manager_name if package_root.join("package.json").exist? || root_package_manager.nil?
|
|
1492
|
+
end
|
|
1493
|
+
|
|
1494
|
+
root_package_manager
|
|
1495
|
+
end
|
|
1496
|
+
|
|
1497
|
+
def package_manager_for(package_root)
|
|
1498
|
+
PACKAGE_MANAGER_LOCKFILES.each do |lockfile, package_manager_name|
|
|
1499
|
+
return package_manager_name if File.exist?(package_root.join(lockfile))
|
|
1500
|
+
end
|
|
1501
|
+
|
|
1106
1502
|
nil
|
|
1107
1503
|
end
|
|
1108
1504
|
|
|
1505
|
+
def package_manager_root_paths
|
|
1506
|
+
@package_manager_root_paths ||= [javascript_package_manager_root_path, root_path].uniq
|
|
1507
|
+
end
|
|
1508
|
+
|
|
1509
|
+
def javascript_package_manager_root_path
|
|
1510
|
+
@javascript_package_manager_root_path ||= begin
|
|
1511
|
+
source_path = config.source_path.expand_path
|
|
1512
|
+
app_root = root_path.expand_path
|
|
1513
|
+
|
|
1514
|
+
if path_within?(source_path, app_root)
|
|
1515
|
+
current = source_path
|
|
1516
|
+
loop do
|
|
1517
|
+
break current if package_root_marker?(current)
|
|
1518
|
+
break root_path if current == app_root
|
|
1519
|
+
|
|
1520
|
+
parent = current.dirname
|
|
1521
|
+
break root_path if parent == current
|
|
1522
|
+
|
|
1523
|
+
current = parent
|
|
1524
|
+
end
|
|
1525
|
+
else
|
|
1526
|
+
root_path
|
|
1527
|
+
end
|
|
1528
|
+
rescue StandardError
|
|
1529
|
+
root_path
|
|
1530
|
+
end
|
|
1531
|
+
end
|
|
1532
|
+
|
|
1109
1533
|
def versions_compatible?(gem_version, npm_version)
|
|
1110
1534
|
# Handle pre-release versions and ranges properly
|
|
1111
1535
|
npm_clean = npm_version.gsub(/[\^~]/, "")
|
|
@@ -1176,7 +1600,7 @@ module Shakapacker
|
|
|
1176
1600
|
config_relative_path = doctor.config.config_path.relative_path_from(doctor.root_path)
|
|
1177
1601
|
puts "✓ Configuration file found (#{config_relative_path})"
|
|
1178
1602
|
if verbose?
|
|
1179
|
-
puts " Assets bundler: #{doctor.
|
|
1603
|
+
puts " Assets bundler: #{doctor.send(:assets_bundler)}"
|
|
1180
1604
|
puts " Source path: #{doctor.config.source_path.relative_path_from(doctor.root_path)}"
|
|
1181
1605
|
puts " Public output path: #{doctor.config.public_output_path.relative_path_from(doctor.root_path)}"
|
|
1182
1606
|
end
|
|
@@ -1212,9 +1636,7 @@ module Shakapacker
|
|
|
1212
1636
|
def print_version_info
|
|
1213
1637
|
return unless doctor.send(:package_json_exists?)
|
|
1214
1638
|
|
|
1215
|
-
|
|
1216
|
-
npm_version = package_json.dig("dependencies", "shakapacker") ||
|
|
1217
|
-
package_json.dig("devDependencies", "shakapacker")
|
|
1639
|
+
npm_version = doctor.send(:package_json_dependency_version, "shakapacker")
|
|
1218
1640
|
puts " • Shakapacker gem version: #{Shakapacker::VERSION}"
|
|
1219
1641
|
puts " • Shakapacker npm version: #{npm_version || 'not installed'}"
|
|
1220
1642
|
end
|
|
@@ -1275,7 +1697,7 @@ module Shakapacker
|
|
|
1275
1697
|
end
|
|
1276
1698
|
|
|
1277
1699
|
def print_transpiler_status
|
|
1278
|
-
transpiler = doctor.
|
|
1700
|
+
transpiler = doctor.send(:javascript_transpiler)
|
|
1279
1701
|
return if transpiler.nil? || transpiler == "none"
|
|
1280
1702
|
|
|
1281
1703
|
loader_name = "#{transpiler}-loader"
|
|
@@ -1285,7 +1707,7 @@ module Shakapacker
|
|
|
1285
1707
|
end
|
|
1286
1708
|
|
|
1287
1709
|
def print_bundler_status
|
|
1288
|
-
bundler = doctor.
|
|
1710
|
+
bundler = doctor.send(:assets_bundler)
|
|
1289
1711
|
case bundler
|
|
1290
1712
|
when "webpack"
|
|
1291
1713
|
print_package_status("webpack", "webpack")
|