cable_ready 4.5.0 → 5.0.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 +2 -376
- data/Gemfile +4 -1
- data/Gemfile.lock +146 -144
- data/README.md +54 -20
- data/Rakefile +8 -8
- data/app/assets/javascripts/cable_ready.js +1269 -0
- data/app/assets/javascripts/cable_ready.umd.js +1190 -0
- data/app/channels/cable_ready/stream.rb +14 -0
- data/app/helpers/cable_ready/view_helper.rb +58 -0
- data/app/jobs/cable_ready/broadcast_job.rb +15 -0
- data/app/models/concerns/cable_ready/updatable/collection_updatable_callbacks.rb +21 -0
- data/app/models/concerns/cable_ready/updatable/collections_registry.rb +59 -0
- data/app/models/concerns/cable_ready/updatable/memory_cache_debounce_adapter.rb +24 -0
- data/app/models/concerns/cable_ready/updatable/model_updatable_callbacks.rb +33 -0
- data/app/models/concerns/cable_ready/updatable.rb +211 -0
- data/app/models/concerns/extend_has_many.rb +15 -0
- data/bin/standardize +1 -1
- data/cable_ready.gemspec +20 -6
- data/lib/cable_ready/broadcaster.rb +4 -3
- data/lib/cable_ready/cable_car.rb +19 -0
- data/lib/cable_ready/channel.rb +29 -31
- data/lib/cable_ready/channels.rb +4 -5
- data/lib/cable_ready/compoundable.rb +11 -0
- data/lib/cable_ready/config.rb +28 -1
- data/lib/cable_ready/engine.rb +59 -0
- data/lib/cable_ready/identifiable.rb +48 -0
- data/lib/cable_ready/importmap.rb +4 -0
- data/lib/cable_ready/installer.rb +224 -0
- data/lib/cable_ready/operation_builder.rb +80 -0
- data/lib/cable_ready/sanity_checker.rb +63 -0
- data/lib/cable_ready/stream_identifier.rb +13 -0
- data/lib/cable_ready/version.rb +1 -1
- data/lib/cable_ready.rb +23 -10
- data/lib/cable_ready_helper.rb +13 -0
- data/lib/generators/cable_ready/channel_generator.rb +110 -0
- data/lib/generators/cable_ready/templates/app/javascript/channels/consumer.js.tt +6 -0
- data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.esbuild.tt +4 -0
- data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.importmap.tt +2 -0
- data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.shakapacker.tt +5 -0
- data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.vite.tt +1 -0
- data/lib/generators/cable_ready/templates/app/javascript/channels/index.js.webpacker.tt +5 -0
- data/lib/generators/cable_ready/templates/app/javascript/config/cable_ready.js.tt +4 -0
- data/lib/generators/cable_ready/templates/app/javascript/config/index.js.tt +1 -0
- data/lib/generators/cable_ready/templates/app/javascript/config/mrujs.js.tt +9 -0
- data/lib/generators/cable_ready/templates/app/javascript/controllers/%file_name%_controller.js.tt +38 -0
- data/lib/generators/cable_ready/templates/app/javascript/controllers/application.js.tt +11 -0
- data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.esbuild.tt +7 -0
- data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.importmap.tt +5 -0
- data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.shakapacker.tt +5 -0
- data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.vite.tt +5 -0
- data/lib/generators/cable_ready/templates/app/javascript/controllers/index.js.webpacker.tt +5 -0
- data/lib/generators/cable_ready/templates/config/initializers/cable_ready.rb +27 -0
- data/lib/generators/cable_ready/templates/esbuild.config.mjs.tt +94 -0
- data/lib/install/action_cable.rb +144 -0
- data/lib/install/broadcaster.rb +109 -0
- data/lib/install/bundle.rb +54 -0
- data/lib/install/compression.rb +51 -0
- data/lib/install/config.rb +39 -0
- data/lib/install/development.rb +34 -0
- data/lib/install/esbuild.rb +101 -0
- data/lib/install/importmap.rb +96 -0
- data/lib/install/initializers.rb +15 -0
- data/lib/install/mrujs.rb +121 -0
- data/lib/install/npm_packages.rb +13 -0
- data/lib/install/shakapacker.rb +65 -0
- data/lib/install/spring.rb +54 -0
- data/lib/install/updatable.rb +34 -0
- data/lib/install/vite.rb +66 -0
- data/lib/install/webpacker.rb +93 -0
- data/lib/install/yarn.rb +56 -0
- data/lib/tasks/cable_ready/cable_ready.rake +247 -0
- data/package.json +42 -13
- data/rollup.config.mjs +57 -0
- data/web-test-runner.config.mjs +12 -0
- data/yarn.lock +3252 -327
- metadata +138 -9
- data/tags +0 -62
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
hash = gemfile_hash
|
|
6
|
+
|
|
7
|
+
# run bundle only when gems are waiting to be added or removed
|
|
8
|
+
add = add_gem_list.exist? ? add_gem_list.readlines.map(&:chomp) : []
|
|
9
|
+
remove = remove_gem_list.exist? ? remove_gem_list.readlines.map(&:chomp) : []
|
|
10
|
+
|
|
11
|
+
if add.present? || remove.present?
|
|
12
|
+
lines = gemfile_path.readlines
|
|
13
|
+
|
|
14
|
+
remove.each do |name|
|
|
15
|
+
index = lines.index { |line| line =~ /gem ['"]#{name}['"]/ }
|
|
16
|
+
if index
|
|
17
|
+
if /^[^#]*gem ['"]#{name}['"]/.match?(lines[index])
|
|
18
|
+
lines[index] = "# #{lines[index]}"
|
|
19
|
+
say "✅ #{name} gem has been disabled"
|
|
20
|
+
else
|
|
21
|
+
say "⏩ #{name} gem is already disabled. Skipping."
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
add.each do |package|
|
|
27
|
+
matches = package.match(/(.+)@(.+)/)
|
|
28
|
+
name, version = matches[1], matches[2]
|
|
29
|
+
|
|
30
|
+
index = lines.index { |line| line =~ /gem ['"]#{name}['"]/ }
|
|
31
|
+
if index
|
|
32
|
+
if !lines[index].match(/^[^#]*gem ['"]#{name}['"].*#{version}['"]/)
|
|
33
|
+
lines[index] = "\ngem \"#{name}\", \"#{version}\"\n"
|
|
34
|
+
say "✅ #{name} gem has been installed"
|
|
35
|
+
else
|
|
36
|
+
say "⏩ #{name} gem is already installed. Skipping."
|
|
37
|
+
end
|
|
38
|
+
else
|
|
39
|
+
lines << "\ngem \"#{name}\", \"#{version}\"\n"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
gemfile_path.write lines.join
|
|
44
|
+
|
|
45
|
+
bundle_command("install --quiet", "BUNDLE_IGNORE_MESSAGES" => "1") if hash != gemfile_hash
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
FileUtils.cp(development_working_path, development_path)
|
|
49
|
+
say "✅ development environment configuration installed"
|
|
50
|
+
|
|
51
|
+
FileUtils.cp(action_cable_initializer_working_path, action_cable_initializer_path)
|
|
52
|
+
say "✅ Action Cable initializer installed"
|
|
53
|
+
|
|
54
|
+
complete_step :bundle
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
initializer = action_cable_initializer_working_path.read
|
|
6
|
+
|
|
7
|
+
proceed = false
|
|
8
|
+
|
|
9
|
+
if initializer.exclude? "PermessageDeflate.configure"
|
|
10
|
+
proceed = if options.key? "compression"
|
|
11
|
+
options["compression"]
|
|
12
|
+
else
|
|
13
|
+
!no?("✨ Configure Action Cable to compress your WebSocket traffic with gzip? (Y/n)")
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if proceed
|
|
18
|
+
if !gemfile.match?(/gem ['"]permessage_deflate['"]/)
|
|
19
|
+
add_gem "permessage_deflate@>= 0.1"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# add permessage_deflate config to Action Cable initializer
|
|
23
|
+
if initializer.exclude? "PermessageDeflate.configure"
|
|
24
|
+
create_or_append(action_cable_initializer_working_path, verbose: false) do
|
|
25
|
+
<<~RUBY
|
|
26
|
+
module ActionCable
|
|
27
|
+
module Connection
|
|
28
|
+
class ClientSocket
|
|
29
|
+
alias_method :old_initialize, :initialize
|
|
30
|
+
def initialize(env, event_target, event_loop, protocols)
|
|
31
|
+
old_initialize(env, event_target, event_loop, protocols)
|
|
32
|
+
@driver.add_extension(
|
|
33
|
+
PermessageDeflate.configure(
|
|
34
|
+
level: Zlib::BEST_COMPRESSION,
|
|
35
|
+
max_window_bits: 13
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
RUBY
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
say "✅ Action Cable initializer patched to deflate WS traffic"
|
|
46
|
+
else
|
|
47
|
+
say "⏩ Action Cable initializer is already patched to deflate WS traffic. Skipping."
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
complete_step :compression
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
return if pack_path_missing?
|
|
6
|
+
|
|
7
|
+
step_path = "/app/javascript/config/"
|
|
8
|
+
index_src = fetch(step_path, "index.js.tt")
|
|
9
|
+
index_path = config_path / "index.js"
|
|
10
|
+
cable_ready_src = fetch(step_path, "cable_ready.js.tt")
|
|
11
|
+
cable_ready_path = config_path / "cable_ready.js"
|
|
12
|
+
|
|
13
|
+
empty_directory config_path unless config_path.exist?
|
|
14
|
+
|
|
15
|
+
copy_file(index_src, index_path) unless index_path.exist?
|
|
16
|
+
|
|
17
|
+
index_pattern = /import ['"](\.\.\/|\.\/)?config['"]/
|
|
18
|
+
index_commented_pattern = /\s*\/\/\s*#{index_pattern}/
|
|
19
|
+
index_import = "import \"#{prefix}config\"\n"
|
|
20
|
+
|
|
21
|
+
if pack.match?(index_pattern)
|
|
22
|
+
if pack.match?(index_commented_pattern)
|
|
23
|
+
lines = pack_path.readlines
|
|
24
|
+
matches = lines.select { |line| line =~ index_commented_pattern }
|
|
25
|
+
lines[lines.index(matches.last).to_i] = index_import
|
|
26
|
+
pack_path.write lines.join
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
lines = pack_path.readlines
|
|
30
|
+
matches = lines.select { |line| line =~ /^import / }
|
|
31
|
+
lines.insert lines.index(matches.last).to_i + 1, index_import
|
|
32
|
+
pack_path.write lines.join
|
|
33
|
+
end
|
|
34
|
+
say "✅ CableReady configs will be imported in #{friendly_pack_path}"
|
|
35
|
+
|
|
36
|
+
# create entrypoint/config/cable_ready.js and make sure it's imported in application.js
|
|
37
|
+
copy_file(cable_ready_src, cable_ready_path) unless cable_ready_path.exist?
|
|
38
|
+
|
|
39
|
+
complete_step :config
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
# mutate working copy of development.rb to avoid bundle alerts
|
|
6
|
+
FileUtils.cp(development_path, development_working_path)
|
|
7
|
+
|
|
8
|
+
# add default_url_options to development.rb for Action Mailer
|
|
9
|
+
if defined?(ActionMailer)
|
|
10
|
+
lines = development_working_path.readlines
|
|
11
|
+
if lines.find { |line| line.include?("config.action_mailer.default_url_options") }
|
|
12
|
+
say "⏩ Action Mailer default_url_options already defined. Skipping."
|
|
13
|
+
else
|
|
14
|
+
index = lines.index { |line| line =~ /^Rails.application.configure do/ }
|
|
15
|
+
lines.insert index + 1, " config.action_mailer.default_url_options = {host: \"localhost\", port: 3000}\n\n"
|
|
16
|
+
development_working_path.write lines.join
|
|
17
|
+
|
|
18
|
+
say "✅ Action Mailer default_url_options defined"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# add default_url_options to development.rb for Action Controller
|
|
23
|
+
lines = development_working_path.readlines
|
|
24
|
+
if lines.find { |line| line.include?("config.action_controller.default_url_options") }
|
|
25
|
+
say "⏩ Action Controller default_url_options already defined. Skipping."
|
|
26
|
+
else
|
|
27
|
+
index = lines.index { |line| line =~ /^Rails.application.configure do/ }
|
|
28
|
+
lines.insert index + 1, " config.action_controller.default_url_options = {host: \"localhost\", port: 3000}\n"
|
|
29
|
+
development_working_path.write lines.join
|
|
30
|
+
|
|
31
|
+
say "✅ Action Controller default_url_options defined"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
complete_step :development
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
return if pack_path_missing?
|
|
6
|
+
|
|
7
|
+
# verify that all critical dependencies are up to date; if not, queue for later
|
|
8
|
+
lines = package_json.readlines
|
|
9
|
+
|
|
10
|
+
if !lines.index { |line| line =~ /^\s*["']esbuild-rails["']: ["']\^1.0.3["']/ }
|
|
11
|
+
add_package "esbuild-rails@^1.0.3"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
if !lines.index { |line| line =~ /^\s*["']@hotwired\/stimulus["']:/ }
|
|
15
|
+
add_package "@hotwired/stimulus@^3.2"
|
|
16
|
+
end
|
|
17
|
+
# copy esbuild.config.mjs to app root
|
|
18
|
+
esbuild_src = fetch("/", "esbuild.config.mjs.tt")
|
|
19
|
+
esbuild_path = Rails.root.join("esbuild.config.mjs")
|
|
20
|
+
if esbuild_path.exist?
|
|
21
|
+
if esbuild_path.read == esbuild_src.read
|
|
22
|
+
say "✅ esbuild.config.mjs present in app root"
|
|
23
|
+
else
|
|
24
|
+
backup(esbuild_path) do
|
|
25
|
+
template(esbuild_src, esbuild_path, verbose: false, entrypoint: entrypoint)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
template(esbuild_src, esbuild_path, entrypoint: entrypoint)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
step_path = "/app/javascript/controllers/"
|
|
33
|
+
application_js_src = fetch(step_path, "application.js.tt")
|
|
34
|
+
application_js_path = controllers_path / "application.js"
|
|
35
|
+
index_src = fetch(step_path, "index.js.esbuild.tt")
|
|
36
|
+
index_path = controllers_path / "index.js"
|
|
37
|
+
friendly_index_path = index_path.relative_path_from(Rails.root).to_s
|
|
38
|
+
|
|
39
|
+
# create entrypoint/controllers, if necessary
|
|
40
|
+
empty_directory controllers_path unless controllers_path.exist?
|
|
41
|
+
|
|
42
|
+
# configure Stimulus application superclass to import Action Cable consumer
|
|
43
|
+
friendly_application_js_path = application_js_path.relative_path_from(Rails.root).to_s
|
|
44
|
+
if application_js_path.exist?
|
|
45
|
+
backup(application_js_path) do
|
|
46
|
+
if application_js_path.read.include?("import consumer")
|
|
47
|
+
say "✅ #{friendly_application_js_path} is present"
|
|
48
|
+
else
|
|
49
|
+
inject_into_file application_js_path, "import consumer from \"../channels/consumer\"\n", after: "import { Application } from \"@hotwired/stimulus\"\n", verbose: false
|
|
50
|
+
inject_into_file application_js_path, "application.consumer = consumer\n", after: "application.debug = false\n", verbose: false
|
|
51
|
+
say "✅ #{friendly_application_js_path} has been updated to import the Action Cable consumer"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
copy_file(application_js_src, application_js_path)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
if index_path.exist?
|
|
59
|
+
if index_path.read != index_src.read
|
|
60
|
+
backup(index_path, delete: true) do
|
|
61
|
+
copy_file(index_src, index_path, verbose: false)
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
else
|
|
65
|
+
copy_file(index_src, index_path)
|
|
66
|
+
end
|
|
67
|
+
say "✅ #{friendly_index_path} has been created"
|
|
68
|
+
|
|
69
|
+
controllers_pattern = /import ['"].\/controllers['"]/
|
|
70
|
+
controllers_commented_pattern = /\s*\/\/\s*#{controllers_pattern}/
|
|
71
|
+
|
|
72
|
+
if pack.match?(controllers_pattern)
|
|
73
|
+
if pack.match?(controllers_commented_pattern)
|
|
74
|
+
proceed = if options.key? "uncomment"
|
|
75
|
+
options["uncomment"]
|
|
76
|
+
else
|
|
77
|
+
!no?("✨ Stimulus seems to be commented out in your application.js. Do you want to import your controllers? (Y/n)")
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
if proceed
|
|
81
|
+
# uncomment_lines only works with Ruby comments 🙄
|
|
82
|
+
lines = pack_path.readlines
|
|
83
|
+
matches = lines.select { |line| line =~ controllers_commented_pattern }
|
|
84
|
+
lines[lines.index(matches.last).to_i] = "import \".\/controllers\"\n" # standard:disable Style/RedundantStringEscape
|
|
85
|
+
pack_path.write lines.join
|
|
86
|
+
say "✅ Stimulus controllers imported in #{friendly_pack_path}"
|
|
87
|
+
else
|
|
88
|
+
say "🤷 your Stimulus controllers are not being imported in your application.js. We trust that you have a reason for this."
|
|
89
|
+
end
|
|
90
|
+
else
|
|
91
|
+
say "✅ Stimulus controllers imported in #{friendly_pack_path}"
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
lines = pack_path.readlines
|
|
95
|
+
matches = lines.select { |line| line =~ /^import / }
|
|
96
|
+
lines.insert lines.index(matches.last).to_i + 1, "import \".\/controllers\"\n" # standard:disable Style/RedundantStringEscape
|
|
97
|
+
pack_path.write lines.join
|
|
98
|
+
say "✅ Stimulus controllers imported in #{friendly_pack_path}"
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
complete_step :esbuild
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
return if pack_path_missing?
|
|
6
|
+
|
|
7
|
+
if !importmap_path.exist?
|
|
8
|
+
halt "#{friendly_importmap_path} is missing. You need a valid importmap config file to proceed."
|
|
9
|
+
return
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
importmap = importmap_path.read
|
|
13
|
+
|
|
14
|
+
backup(importmap_path) do
|
|
15
|
+
if !importmap.include?("pin_all_from \"#{entrypoint}/controllers\"")
|
|
16
|
+
append_file(importmap_path, <<~RUBY, verbose: false)
|
|
17
|
+
pin_all_from "#{entrypoint}/controllers", under: "controllers"
|
|
18
|
+
RUBY
|
|
19
|
+
say "✅ pin controllers folder"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
if !importmap.include?("pin_all_from \"#{entrypoint}/channels\"")
|
|
23
|
+
append_file(importmap_path, <<~RUBY, verbose: false)
|
|
24
|
+
pin_all_from "#{entrypoint}/channels", under: "channels"
|
|
25
|
+
RUBY
|
|
26
|
+
say "✅ pin channels folder"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
if !importmap.include?("pin_all_from \"#{entrypoint}/config\"")
|
|
30
|
+
append_file(importmap_path, <<~RUBY, verbose: false)
|
|
31
|
+
pin_all_from "#{entrypoint}/config", under: "config"
|
|
32
|
+
RUBY
|
|
33
|
+
say "✅ pin config folder"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if !importmap.include?("pin \"@rails/actioncable\"")
|
|
37
|
+
append_file(importmap_path, <<~RUBY, verbose: false)
|
|
38
|
+
pin "@rails/actioncable", to: "actioncable.esm.js", preload: true
|
|
39
|
+
RUBY
|
|
40
|
+
say "✅ pin Action Cable"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
if !importmap.include?("pin \"cable_ready\"")
|
|
44
|
+
append_file(importmap_path, <<~RUBY, verbose: false)
|
|
45
|
+
pin "cable_ready", to: "cable_ready.js", preload: true
|
|
46
|
+
RUBY
|
|
47
|
+
say "✅ pin CableReady"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if !importmap.include?("pin \"morphdom\"")
|
|
51
|
+
append_file(importmap_path, <<~RUBY, verbose: false)
|
|
52
|
+
pin "morphdom", to: "https://ga.jspm.io/npm:morphdom@2.6.1/dist/morphdom.js", preload: true
|
|
53
|
+
RUBY
|
|
54
|
+
say "✅ pin morphdom"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
application_js_src = fetch("/", "app/javascript/controllers/application.js.tt")
|
|
59
|
+
application_js_path = controllers_path / "application.js"
|
|
60
|
+
index_src = fetch("/", "app/javascript/controllers/index.js.importmap.tt")
|
|
61
|
+
index_path = controllers_path / "index.js"
|
|
62
|
+
|
|
63
|
+
# create entrypoint/controllers, as well as the index, application and application_controller
|
|
64
|
+
empty_directory controllers_path unless controllers_path.exist?
|
|
65
|
+
|
|
66
|
+
# configure Stimulus application superclass to import Action Cable consumer
|
|
67
|
+
backup(application_js_path) do
|
|
68
|
+
if application_js_path.exist?
|
|
69
|
+
friendly_application_js_path = application_js_path.relative_path_from(Rails.root).to_s
|
|
70
|
+
if application_js_path.read.include?("import consumer")
|
|
71
|
+
say "✅ #{friendly_application_js_path} is present"
|
|
72
|
+
else
|
|
73
|
+
inject_into_file application_js_path, "import consumer from \"../channels/consumer\"\n", after: "import { Application } from \"@hotwired/stimulus\"\n", verbose: false
|
|
74
|
+
inject_into_file application_js_path, "application.consumer = consumer\n", after: "application.debug = false\n", verbose: false
|
|
75
|
+
say "✅ #{friendly_application_js_path} has been updated to import the Action Cable consumer"
|
|
76
|
+
end
|
|
77
|
+
else
|
|
78
|
+
copy_file(application_js_src, application_js_path)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
if index_path.exist?
|
|
83
|
+
friendly_index_path = index_path.relative_path_from(Rails.root).to_s
|
|
84
|
+
if index_path.read == index_src.read
|
|
85
|
+
say "✅ #{friendly_index_path} is present"
|
|
86
|
+
else
|
|
87
|
+
backup(index_path, delete: true) do
|
|
88
|
+
copy_file(index_src, index_path, verbose: false)
|
|
89
|
+
end
|
|
90
|
+
say "✅ #{friendly_index_path} has been created"
|
|
91
|
+
end
|
|
92
|
+
else
|
|
93
|
+
copy_file(index_src, index_path)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
complete_step :importmap
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
cr_initializer_src = fetch("/", "config/initializers/cable_ready.rb")
|
|
6
|
+
cr_initializer_path = Rails.root.join("config/initializers/cable_ready.rb")
|
|
7
|
+
|
|
8
|
+
if !cr_initializer_path.exist?
|
|
9
|
+
copy_file(cr_initializer_src, cr_initializer_path, verbose: false)
|
|
10
|
+
say "✅ CableReady initializer created at config/initializers/cable_ready.rb"
|
|
11
|
+
else
|
|
12
|
+
say "⏩ config/initializers/cable_ready.rb already exists. Skipping."
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
complete_step :initializers
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
return if pack_path_missing?
|
|
6
|
+
|
|
7
|
+
mrujs_path = config_path / "mrujs.js"
|
|
8
|
+
|
|
9
|
+
proceed = false
|
|
10
|
+
|
|
11
|
+
if !File.exist?(mrujs_path)
|
|
12
|
+
proceed = if options.key? "mrujs"
|
|
13
|
+
options["mrujs"]
|
|
14
|
+
else
|
|
15
|
+
!no?("✨ Would you like to install and enable mrujs? It's a modern, drop-in replacement for rails-ujs (Y/n)")
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
if proceed
|
|
20
|
+
if bundler == "importmap"
|
|
21
|
+
|
|
22
|
+
if !importmap_path.exist?
|
|
23
|
+
halt "#{friendly_importmap_path} is missing. You need a valid importmap config file to proceed."
|
|
24
|
+
return
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
importmap = importmap_path.read
|
|
28
|
+
|
|
29
|
+
if !importmap.include?("pin \"mrujs\"")
|
|
30
|
+
append_file(importmap_path, <<~RUBY, verbose: false)
|
|
31
|
+
pin "mrujs", to: "https://ga.jspm.io/npm:mrujs@0.10.1/dist/index.module.js"
|
|
32
|
+
RUBY
|
|
33
|
+
say "✅ pin mrujs"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if !importmap.include?("pin \"mrujs/plugins\"")
|
|
37
|
+
append_file(importmap_path, <<~RUBY, verbose: false)
|
|
38
|
+
pin "mrujs/plugins", to: "https://ga.jspm.io/npm:mrujs@0.10.1/plugins/dist/plugins.module.js"
|
|
39
|
+
RUBY
|
|
40
|
+
say "✅ pin mrujs plugins"
|
|
41
|
+
end
|
|
42
|
+
else
|
|
43
|
+
# queue mrujs for installation
|
|
44
|
+
if !package_json.read.include?('"mrujs":')
|
|
45
|
+
add_package "mrujs@^0.10.1"
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# queue @rails/ujs for removal
|
|
49
|
+
if package_json.read.include?('"@rails/ujs":')
|
|
50
|
+
drop_package "@rails/ujs"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
step_path = "/app/javascript/config/"
|
|
55
|
+
mrujs_src = fetch(step_path, "mrujs.js.tt")
|
|
56
|
+
|
|
57
|
+
# create entrypoint/config/mrujs.js if necessary
|
|
58
|
+
copy_file(mrujs_src, mrujs_path) unless mrujs_path.exist?
|
|
59
|
+
|
|
60
|
+
# import mrujs config in entrypoint/config/index.js
|
|
61
|
+
index_path = config_path / "index.js"
|
|
62
|
+
index = index_path.read
|
|
63
|
+
friendly_index_path = index_path.relative_path_from(Rails.root).to_s
|
|
64
|
+
mrujs_pattern = /import ['"].\/mrujs['"]/
|
|
65
|
+
mrujs_import = "import '.\/mrujs'\n" # standard:disable Style/RedundantStringEscape
|
|
66
|
+
|
|
67
|
+
if !index.match?(mrujs_pattern)
|
|
68
|
+
append_file(index_path, mrujs_import, verbose: false)
|
|
69
|
+
end
|
|
70
|
+
say "✅ mrujs imported in #{friendly_index_path}"
|
|
71
|
+
|
|
72
|
+
# remove @rails/ujs from application.js
|
|
73
|
+
rails_ujs_pattern = /import Rails from ['"]@rails\/ujs['"]/
|
|
74
|
+
|
|
75
|
+
lines = pack_path.readlines
|
|
76
|
+
if lines.index { |line| line =~ rails_ujs_pattern }
|
|
77
|
+
gsub_file pack_path, rails_ujs_pattern, "", verbose: false
|
|
78
|
+
say "✅ @rails/ujs removed from #{friendly_pack_path}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# set Action View to generate remote forms when using form_with
|
|
82
|
+
application_path = Rails.root.join("config/application.rb")
|
|
83
|
+
application_pattern = /^[^#]*config\.action_view\.form_with_generates_remote_forms = true/
|
|
84
|
+
defaults_pattern = /config\.load_defaults \d\.\d/
|
|
85
|
+
|
|
86
|
+
lines = application_path.readlines
|
|
87
|
+
backup(application_path) do
|
|
88
|
+
if !lines.index { |line| line =~ application_pattern }
|
|
89
|
+
if (index = lines.index { |line| line =~ /^[^#]*#{defaults_pattern}/ })
|
|
90
|
+
gsub_file application_path, /\s*#{defaults_pattern}\n/, verbose: false do
|
|
91
|
+
<<-RUBY
|
|
92
|
+
\n#{lines[index]}
|
|
93
|
+
# form_with helper will generate remote forms by default (mrujs)
|
|
94
|
+
config.action_view.form_with_generates_remote_forms = true
|
|
95
|
+
RUBY
|
|
96
|
+
end
|
|
97
|
+
else
|
|
98
|
+
insert_into_file application_path, after: "class Application < Rails::Application" do
|
|
99
|
+
<<-RUBY
|
|
100
|
+
|
|
101
|
+
# form_with helper will generate remote forms by default (mrujs)
|
|
102
|
+
config.action_view.form_with_generates_remote_forms = true
|
|
103
|
+
RUBY
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
say "✅ form_with_generates_remote_forms set to true in config/application.rb"
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# remove turbolinks from Gemfile because it's incompatible with mrujs (and unnecessary)
|
|
111
|
+
turbolinks_pattern = /^[^#]*gem ["']turbolinks["']/
|
|
112
|
+
|
|
113
|
+
lines = gemfile_path.readlines
|
|
114
|
+
if lines.index { |line| line =~ turbolinks_pattern }
|
|
115
|
+
remove_gem :turbolinks
|
|
116
|
+
else
|
|
117
|
+
say "✅ turbolinks is not present in Gemfile"
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
complete_step :mrujs
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
lines = package_json.readlines
|
|
6
|
+
|
|
7
|
+
if !lines.index { |line| line =~ /^\s*["']cable_ready["']: ["'].*#{cr_npm_version}["']/ }
|
|
8
|
+
add_package "cable_ready@#{cr_npm_version}"
|
|
9
|
+
else
|
|
10
|
+
say "⏩ cable_ready npm package is already present"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
complete_step :npm_packages
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
return if pack_path_missing?
|
|
6
|
+
|
|
7
|
+
# verify that all critical dependencies are up to date; if not, queue for later
|
|
8
|
+
lines = package_json.readlines
|
|
9
|
+
if !lines.index { |line| line =~ /^\s*["']@hotwired\/stimulus["']:/ }
|
|
10
|
+
add_package "@hotwired/stimulus@^3.2"
|
|
11
|
+
else
|
|
12
|
+
say "⏩ @hotwired/stimulus npm package is already present. Skipping."
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
if !lines.index { |line| line =~ /^\s*["']@hotwired\/stimulus-webpack-helpers["']: ["']\^1.0.1["']/ }
|
|
16
|
+
add_package "@hotwired/stimulus-webpack-helpers@^1.0.1"
|
|
17
|
+
else
|
|
18
|
+
say "⏩ @hotwired/stimulus-webpack-helpers npm package is already present. Skipping."
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
step_path = "/app/javascript/controllers/"
|
|
22
|
+
application_js_src = fetch(step_path, "application.js.tt")
|
|
23
|
+
application_js_path = controllers_path / "application.js"
|
|
24
|
+
index_src = fetch(step_path, "index.js.shakapacker.tt")
|
|
25
|
+
index_path = controllers_path / "index.js"
|
|
26
|
+
|
|
27
|
+
# create entrypoint/controllers, as well as the index, application and application_controller
|
|
28
|
+
empty_directory controllers_path unless controllers_path.exist?
|
|
29
|
+
|
|
30
|
+
copy_file(application_js_src, application_js_path) unless application_js_path.exist?
|
|
31
|
+
copy_file(index_src, index_path) unless index_path.exist?
|
|
32
|
+
|
|
33
|
+
controllers_pattern = /import ['"]controllers['"]/
|
|
34
|
+
controllers_commented_pattern = /\s*\/\/\s*#{controllers_pattern}/
|
|
35
|
+
|
|
36
|
+
if pack.match?(controllers_pattern)
|
|
37
|
+
if pack.match?(controllers_commented_pattern)
|
|
38
|
+
proceed = if options.key? "uncomment"
|
|
39
|
+
options["uncomment"]
|
|
40
|
+
else
|
|
41
|
+
!no?("✨ Do you want to import your Stimulus controllers in application.js? (Y/n)")
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
if proceed
|
|
45
|
+
# uncomment_lines only works with Ruby comments 🙄
|
|
46
|
+
lines = pack_path.readlines
|
|
47
|
+
matches = lines.select { |line| line =~ controllers_commented_pattern }
|
|
48
|
+
lines[lines.index(matches.last).to_i] = "import \"controllers\"\n"
|
|
49
|
+
pack_path.write lines.join
|
|
50
|
+
say "✅ Stimulus controllers imported in #{friendly_pack_path}"
|
|
51
|
+
else
|
|
52
|
+
say "🤷 your Stimulus controllers are not being imported in your application.js. We trust that you have a reason for this."
|
|
53
|
+
end
|
|
54
|
+
else
|
|
55
|
+
say "✅ Stimulus controllers imported in #{friendly_pack_path}"
|
|
56
|
+
end
|
|
57
|
+
else
|
|
58
|
+
lines = pack_path.readlines
|
|
59
|
+
matches = lines.select { |line| line =~ /^import / }
|
|
60
|
+
lines.insert lines.index(matches.last).to_i + 1, "import \"controllers\"\n"
|
|
61
|
+
pack_path.write lines.join
|
|
62
|
+
say "✅ Stimulus controllers imported in #{friendly_pack_path}"
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
complete_step :shakapacker
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
spring_pattern = /^[^#]*gem ["']spring["']/
|
|
6
|
+
|
|
7
|
+
proceed = false
|
|
8
|
+
lines = gemfile_path.readlines
|
|
9
|
+
|
|
10
|
+
if lines.index { |line| line =~ spring_pattern }
|
|
11
|
+
proceed = if options.key? "spring"
|
|
12
|
+
options["spring"]
|
|
13
|
+
else
|
|
14
|
+
!no?("✨ Would you like to disable the spring gem? \nIt's been removed from Rails 7, and is the frequent culprit behind countless mystery bugs. (Y/n)")
|
|
15
|
+
end
|
|
16
|
+
else
|
|
17
|
+
say "⏩ Spring is not installed."
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
if proceed
|
|
21
|
+
spring_watcher_pattern = /^[^#]*gem ["']spring-watcher-listen["']/
|
|
22
|
+
bin_rails_pattern = /^[^#]*load File.expand_path\("spring", __dir__\)/
|
|
23
|
+
|
|
24
|
+
if (index = lines.index { |line| line =~ spring_pattern })
|
|
25
|
+
remove_gem :spring
|
|
26
|
+
|
|
27
|
+
bin_spring = Rails.root.join("bin/spring")
|
|
28
|
+
if bin_spring.exist?
|
|
29
|
+
run "bin/spring binstub --remove --all"
|
|
30
|
+
say "✅ Removed spring binstubs"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
bin_rails = Rails.root.join("bin/rails")
|
|
34
|
+
bin_rails_content = bin_rails.readlines
|
|
35
|
+
if (index = bin_rails_content.index { |line| line =~ bin_rails_pattern })
|
|
36
|
+
backup(bin_rails) do
|
|
37
|
+
bin_rails_content[index] = "# #{bin_rails_content[index]}"
|
|
38
|
+
bin_rails.write bin_rails_content.join
|
|
39
|
+
end
|
|
40
|
+
say "✅ Removed spring from bin/rails"
|
|
41
|
+
end
|
|
42
|
+
create_file "tmp/cable_ready_installer/kill_spring", verbose: false
|
|
43
|
+
else
|
|
44
|
+
say "✅ spring has been successfully removed"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
if lines.index { |line| line =~ spring_watcher_pattern }
|
|
48
|
+
remove_gem "spring-watcher-listen"
|
|
49
|
+
end
|
|
50
|
+
else
|
|
51
|
+
say "⏩ Skipping."
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
complete_step :spring
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "cable_ready/installer"
|
|
4
|
+
|
|
5
|
+
if application_record_path.exist?
|
|
6
|
+
lines = application_record_path.readlines
|
|
7
|
+
|
|
8
|
+
if !lines.index { |line| line =~ /^\s*include CableReady::Updatable/ }
|
|
9
|
+
proceed = if options.key? "updatable"
|
|
10
|
+
options["updatable"]
|
|
11
|
+
else
|
|
12
|
+
!no?("✨ Include CableReady::Updatable in Active Record model classes? (Y/n)")
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
unless proceed
|
|
16
|
+
complete_step :updatable
|
|
17
|
+
|
|
18
|
+
puts "⏩ Skipping."
|
|
19
|
+
return
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
index = lines.index { |line| line.include?("class ApplicationRecord < ActiveRecord::Base") }
|
|
23
|
+
lines.insert index + 1, " include CableReady::Updatable\n"
|
|
24
|
+
application_record_path.write lines.join
|
|
25
|
+
|
|
26
|
+
say "✅ included CableReady::Updatable in ApplicationRecord"
|
|
27
|
+
else
|
|
28
|
+
say "⏩ CableReady::Updatable has already been included in Active Record model classes. Skipping."
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
say "⏩ ApplicationRecord doesn't exist. Skipping."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
complete_step :updatable
|