cable_ready 5.0.0.pre9 → 5.0.0.pre10

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.
Files changed (119) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +4 -1
  3. data/Gemfile.lock +119 -100
  4. data/README.md +2 -5
  5. data/app/assets/javascripts/cable_ready.js +457 -151
  6. data/app/assets/javascripts/cable_ready.min.js +1 -1
  7. data/app/assets/javascripts/cable_ready.min.js.map +1 -1
  8. data/app/assets/javascripts/cable_ready.umd.js +441 -165
  9. data/app/assets/javascripts/cable_ready.umd.min.js +1 -1
  10. data/app/assets/javascripts/cable_ready.umd.min.js.map +1 -1
  11. data/app/channels/cable_ready/stream.rb +7 -5
  12. data/app/helpers/cable_ready/view_helper.rb +58 -0
  13. data/app/jobs/cable_ready_broadcast_job.rb +9 -8
  14. data/app/models/concerns/cable_ready/updatable/collection_updatable_callbacks.rb +2 -0
  15. data/app/models/concerns/cable_ready/updatable/collections_registry.rb +2 -0
  16. data/app/models/concerns/cable_ready/updatable/model_updatable_callbacks.rb +5 -2
  17. data/app/models/concerns/cable_ready/updatable.rb +67 -19
  18. data/app/models/concerns/extend_has_many.rb +2 -0
  19. data/cable_ready.gemspec +4 -6
  20. data/lib/cable_ready/broadcaster.rb +2 -0
  21. data/lib/cable_ready/cable_car.rb +2 -0
  22. data/lib/cable_ready/channel.rb +12 -4
  23. data/lib/cable_ready/channels.rb +3 -1
  24. data/lib/cable_ready/config.rb +16 -2
  25. data/lib/cable_ready/engine.rb +19 -9
  26. data/lib/cable_ready/identifiable.rb +23 -5
  27. data/lib/cable_ready/importmap.rb +2 -0
  28. data/lib/cable_ready/installer.rb +224 -0
  29. data/lib/cable_ready/operation_builder.rb +1 -1
  30. data/lib/cable_ready/sanity_checker.rb +1 -31
  31. data/lib/cable_ready/version.rb +1 -1
  32. data/lib/cable_ready.rb +3 -8
  33. data/lib/cable_ready_helper.rb +13 -0
  34. data/lib/generators/cable_ready/channel_generator.rb +51 -12
  35. data/lib/generators/cable_ready/templates/config/initializers/cable_ready.rb +10 -6
  36. data/lib/install/action_cable.rb +144 -0
  37. data/lib/install/broadcaster.rb +109 -0
  38. data/lib/install/bundle.rb +54 -0
  39. data/lib/install/compression.rb +51 -0
  40. data/lib/install/config.rb +39 -0
  41. data/lib/install/development.rb +34 -0
  42. data/lib/install/esbuild.rb +101 -0
  43. data/lib/install/importmap.rb +96 -0
  44. data/lib/install/initializers.rb +15 -0
  45. data/lib/install/mrujs.rb +121 -0
  46. data/lib/install/npm_packages.rb +13 -0
  47. data/lib/install/shakapacker.rb +61 -0
  48. data/lib/install/spring.rb +54 -0
  49. data/lib/install/updatable.rb +34 -0
  50. data/lib/install/vite.rb +62 -0
  51. data/lib/install/webpacker.rb +81 -0
  52. data/lib/install/yarn.rb +56 -0
  53. data/lib/tasks/cable_ready/cable_ready.rake +249 -0
  54. data/package.json +28 -18
  55. data/{rollup.config.js → rollup.config.mjs} +9 -8
  56. data/web-test-runner.config.mjs +12 -0
  57. data/yarn.lock +2210 -404
  58. metadata +37 -161
  59. data/LATEST +0 -1
  60. data/app/helpers/cable_ready_helper.rb +0 -26
  61. data/lib/generators/cable_ready/helpers_generator.rb +0 -43
  62. data/lib/generators/cable_ready/initializer_generator.rb +0 -14
  63. data/test/dummy/app/channels/application_cable/channel.rb +0 -4
  64. data/test/dummy/app/channels/application_cable/connection.rb +0 -4
  65. data/test/dummy/app/controllers/application_controller.rb +0 -2
  66. data/test/dummy/app/helpers/application_helper.rb +0 -2
  67. data/test/dummy/app/jobs/application_job.rb +0 -7
  68. data/test/dummy/app/mailers/application_mailer.rb +0 -4
  69. data/test/dummy/app/models/application_record.rb +0 -3
  70. data/test/dummy/app/models/dugong.rb +0 -4
  71. data/test/dummy/app/models/global_idable_entity.rb +0 -16
  72. data/test/dummy/app/models/post.rb +0 -4
  73. data/test/dummy/app/models/section.rb +0 -6
  74. data/test/dummy/app/models/team.rb +0 -6
  75. data/test/dummy/app/models/topic.rb +0 -4
  76. data/test/dummy/app/models/user.rb +0 -7
  77. data/test/dummy/config/application.rb +0 -22
  78. data/test/dummy/config/boot.rb +0 -5
  79. data/test/dummy/config/environment.rb +0 -5
  80. data/test/dummy/config/environments/development.rb +0 -76
  81. data/test/dummy/config/environments/production.rb +0 -120
  82. data/test/dummy/config/environments/test.rb +0 -59
  83. data/test/dummy/config/initializers/application_controller_renderer.rb +0 -8
  84. data/test/dummy/config/initializers/assets.rb +0 -12
  85. data/test/dummy/config/initializers/backtrace_silencers.rb +0 -8
  86. data/test/dummy/config/initializers/cable_ready.rb +0 -18
  87. data/test/dummy/config/initializers/content_security_policy.rb +0 -28
  88. data/test/dummy/config/initializers/cookies_serializer.rb +0 -5
  89. data/test/dummy/config/initializers/filter_parameter_logging.rb +0 -6
  90. data/test/dummy/config/initializers/inflections.rb +0 -16
  91. data/test/dummy/config/initializers/mime_types.rb +0 -4
  92. data/test/dummy/config/initializers/permissions_policy.rb +0 -11
  93. data/test/dummy/config/initializers/wrap_parameters.rb +0 -14
  94. data/test/dummy/config/puma.rb +0 -43
  95. data/test/dummy/config/routes.rb +0 -3
  96. data/test/dummy/db/migrate/20210902154139_create_users.rb +0 -9
  97. data/test/dummy/db/migrate/20210902154153_create_posts.rb +0 -10
  98. data/test/dummy/db/migrate/20210904081930_create_topics.rb +0 -9
  99. data/test/dummy/db/migrate/20210904093607_create_sections.rb +0 -9
  100. data/test/dummy/db/migrate/20210913191735_create_teams.rb +0 -8
  101. data/test/dummy/db/migrate/20210913191759_add_team_reference_to_users.rb +0 -5
  102. data/test/dummy/db/migrate/20220329222959_create_dugongs.rb +0 -8
  103. data/test/dummy/db/migrate/20220329230221_create_active_storage_tables.active_storage.rb +0 -36
  104. data/test/dummy/db/schema.rb +0 -84
  105. data/test/dummy/test/models/dugong_test.rb +0 -7
  106. data/test/dummy/test/models/post_test.rb +0 -7
  107. data/test/dummy/test/models/section_test.rb +0 -7
  108. data/test/dummy/test/models/team_test.rb +0 -7
  109. data/test/dummy/test/models/topic_test.rb +0 -7
  110. data/test/dummy/test/models/user_test.rb +0 -7
  111. data/test/lib/cable_ready/cable_car_test.rb +0 -50
  112. data/test/lib/cable_ready/compoundable_test.rb +0 -26
  113. data/test/lib/cable_ready/helper_test.rb +0 -25
  114. data/test/lib/cable_ready/identifiable_test.rb +0 -69
  115. data/test/lib/cable_ready/operation_builder_test.rb +0 -189
  116. data/test/lib/cable_ready/updatable_test.rb +0 -135
  117. data/test/lib/generators/cable_ready/channel_generator_test.rb +0 -157
  118. data/test/support/generator_test_helpers.rb +0 -28
  119. data/test/test_helper.rb +0 -18
@@ -10,8 +10,7 @@ class CableReady::SanityChecker
10
10
  return if called_by_generate_config?
11
11
  return if called_by_rake?
12
12
 
13
- instance = new
14
- instance.check_new_version_available
13
+ new
15
14
  end
16
15
 
17
16
  private
@@ -25,37 +24,8 @@ class CableReady::SanityChecker
25
24
  end
26
25
  end
27
26
 
28
- def check_new_version_available
29
- return if CableReady.config.on_new_version_available == :ignore
30
- return if Rails.env.development? == false
31
- return if using_preview_release?
32
- begin
33
- latest_version = URI.open("https://raw.githubusercontent.com/stimulusreflex/cable_ready/master/LATEST", open_timeout: 1, read_timeout: 1).read.strip
34
- if latest_version != CableReady::VERSION
35
- puts <<~WARN
36
-
37
- 👉 There is a new version of CableReady available!
38
- Current: #{CableReady::VERSION} Latest: #{latest_version}
39
-
40
- If you upgrade, it is very important that you update BOTH Gemfile and package.json
41
- Then, run `bundle install && yarn install` to update to #{latest_version}.
42
-
43
- WARN
44
- exit if CableReady.config.on_new_version_available == :exit
45
- end
46
- rescue
47
- puts "👉 CableReady #{CableReady::VERSION} update check skipped: connection timeout"
48
- end
49
- end
50
-
51
27
  private
52
28
 
53
- def using_preview_release?
54
- preview = CableReady::VERSION.match?(LATEST_VERSION_FORMAT) == false
55
- puts "👉 CableReady #{CableReady::VERSION} update check skipped: pre-release build" if preview
56
- preview
57
- end
58
-
59
29
  def initializer_missing?
60
30
  File.exist?(Rails.root.join("config", "initializers", "cable_ready.rb")) == false
61
31
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module CableReady
4
- VERSION = "5.0.0.pre9"
4
+ VERSION = "5.0.0.pre10"
5
5
  end
data/lib/cable_ready.rb CHANGED
@@ -1,14 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "open-uri"
4
- require "active_record"
5
- require "action_view"
6
- require "active_support/all"
7
- require "thread/local"
8
- require "monitor"
9
- require "observer"
10
- require "singleton"
11
- require "cable_ready/version"
4
+ require "active_support/message_verifier"
12
5
  require "cable_ready/identifiable"
13
6
  require "cable_ready/operation_builder"
14
7
  require "cable_ready/config"
@@ -20,6 +13,8 @@ require "cable_ready/channel"
20
13
  require "cable_ready/channels"
21
14
  require "cable_ready/cable_car"
22
15
  require "cable_ready/stream_identifier"
16
+ require "cable_ready/version"
17
+ require "cable_ready_helper"
23
18
 
24
19
  module CableReady
25
20
  class << self
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TODO: remove me once CableReady 5.0 was released
4
+
5
+ require_relative "../app/helpers/cable_ready/view_helper"
6
+
7
+ module CableReadyHelper
8
+ def self.included(base)
9
+ warn "NOTICE: `CableReadyHelper` was renamed to `CableReady::ViewHelper`. Please update your include accordingly."
10
+
11
+ base.include(::CableReady::ViewHelper)
12
+ end
13
+ end
@@ -7,32 +7,71 @@ class CableReady::ChannelGenerator < Rails::Generators::NamedBase
7
7
  class_option :stream_for, type: :string
8
8
  class_option :stimulus, type: :boolean
9
9
 
10
+ def destroy_not_supported
11
+ if behavior == :revoke
12
+ puts "Sorry, we don't support destroying generated channels.\nDelete the Action Cable channel class, as well as any corresponding JavaScript classes."
13
+ exit
14
+ end
15
+ end
16
+
10
17
  def check_options
11
- raise "Can't specify --stream-from and --stream-for at the same time" if options.key?(:stream_from) && options.key?(:stream_for)
18
+ if options.key?(:stream_from) && options.key?(:stream_for)
19
+ puts "Can't specify --stream-from and --stream-for at the same time"
20
+ exit
21
+ end
12
22
  end
13
23
 
14
24
  def create_channel
15
- generate "channel", file_name
25
+ generate "channel", file_name, "--skip"
16
26
  end
17
27
 
18
28
  def enhance_channels
29
+ @entrypoint = [
30
+ "app/javascript",
31
+ "app/frontend"
32
+ ].find { |path| File.exist?(Rails.root.join(path)) } || "app/javascript"
33
+ puts "Where do JavaScript files live in your app? Our best guess is: \e[1m#{@entrypoint}\e[22m 🤔"
34
+ puts "Press enter to accept this, or type a different path."
35
+ print "> "
36
+ input = Rails.env.test? ? "tmp/app/javascript" : $stdin.gets.chomp
37
+ @entrypoint = input unless input.blank?
38
+ @js_channel = "#{@entrypoint}/channels/#{file_name}_channel.js"
39
+
19
40
  if using_broadcast_to?
20
- gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_for #{resource}.find(params[:id])\n"
21
- template "app/javascript/controllers/%file_name%_controller.js" if using_stimulus?
22
- else
23
- prepend_to_file "app/javascript/channels/#{file_name}_channel.js", "import CableReady from 'cable_ready'\n"
24
- inject_into_file "app/javascript/channels/#{file_name}_channel.js", after: "// Called when there's incoming data on the websocket for this channel\n" do
25
- <<-JS
26
- if (data.cableReady) CableReady.perform(data.operations)
27
- JS
41
+ if using_stimulus?
42
+ template("#{@entrypoint}/controllers/%file_name%_controller.js")
43
+ Rails.root.join(@js_channel).delete
44
+ else
45
+ gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_for #{resource}.find(params[:id])\n", verbose: false
46
+ gsub_file @js_channel, /"#{resource}Channel"/, verbose: false do
47
+ <<-JS
48
+
49
+ {
50
+ channel: "#{resource}Channel",
51
+ id: 1
52
+ }
53
+ JS
54
+ end
55
+ doctor_javascript_channel_class
56
+ puts "\nDon't forget to update the id in the channel subscription: #{@js_channel}\nIt's currently set to 1; you'll want to change that to a dynamic value based on something in your DOM."
28
57
  end
29
-
30
- gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_from \"#{identifier}\"\n"
58
+ else
59
+ gsub_file "app/channels/#{file_name}_channel.rb", /# stream_from.*\n/, "stream_from \"#{identifier}\"\n", verbose: false
60
+ doctor_javascript_channel_class
31
61
  end
32
62
  end
33
63
 
34
64
  private
35
65
 
66
+ def doctor_javascript_channel_class
67
+ prepend_to_file @js_channel, "import CableReady from 'cable_ready'\n", verbose: false
68
+ inject_into_file @js_channel, after: "// Called when there's incoming data on the websocket for this channel\n", verbose: false do
69
+ <<-JS
70
+ if (data.cableReady) CableReady.perform(data.operations)
71
+ JS
72
+ end
73
+ end
74
+
36
75
  def option_given?
37
76
  options.key?(:stream_from) || options.key?(:stream_for)
38
77
  end
@@ -3,16 +3,20 @@
3
3
  CableReady.configure do |config|
4
4
  # Enable/disable exiting / warning when the sanity checks fail options:
5
5
  # `:exit` or `:warn` or `:ignore`
6
-
6
+ #
7
7
  # config.on_failed_sanity_checks = :exit
8
8
 
9
- # Enable/disable exiting / warning when there's a new CableReady release
10
- # `:exit` or `:warn` or `:ignore`
11
-
12
- # config.on_new_version_available = :ignore
9
+ # Enable/disable assets compilation
10
+ # `true` or `false`
11
+ #
12
+ # config.precompile_assets = true
13
13
 
14
14
  # Define your own custom operations
15
15
  # https://cableready.stimulusreflex.com/customization#custom-operations
16
-
16
+ #
17
17
  # config.add_operation_name :jazz_hands
18
+
19
+ # Change the default Active Job queue used for broadcast_later and broadcast_later_to
20
+ #
21
+ # config.broadcast_job_queue = :default
18
22
  end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cable_ready/installer"
4
+
5
+ # verify that Action Cable is installed
6
+ if defined?(ActionCable::Engine)
7
+ say "✅ ActionCable::Engine is loaded and in scope"
8
+ else
9
+ halt "ActionCable::Engine is not loaded, please add or uncomment `require \"action_cable/engine\"` to your `config/application.rb`"
10
+ return
11
+ end
12
+
13
+ return if pack_path_missing?
14
+
15
+ # verify that the Action Cable pubsub config is created
16
+ cable_config = Rails.root.join("config/cable.yml")
17
+
18
+ if cable_config.exist?
19
+ say "✅ config/cable.yml is present"
20
+ else
21
+ inside "config" do
22
+ template "cable.yml"
23
+ end
24
+ end
25
+
26
+ # verify that the Action Cable pubsub is set to use redis in development
27
+ yaml = YAML.safe_load(cable_config.read)
28
+ app_name = Rails.application.class.module_parent.name.underscore
29
+
30
+ if yaml["development"]["adapter"] == "redis"
31
+ say "✅ config/cable.yml is configured to use the redis adapter in development"
32
+ elsif yaml["development"]["adapter"] == "async"
33
+ yaml["development"] = {
34
+ "adapter" => "redis",
35
+ "url" => "<%= ENV.fetch(\"REDIS_URL\") { \"redis://localhost:6379/1\" } %>",
36
+ "channel_prefix" => "#{app_name}_development"
37
+ }
38
+ backup(cable_config) do
39
+ cable_config.write(yaml.to_yaml)
40
+ end
41
+ say "✅ config/cable.yml was updated to use the redis adapter in development"
42
+ else
43
+ say "🤷 config/cable.yml should use the redis adapter - or something like it - in development. You have something else specified, and we trust that you know what you're doing."
44
+ end
45
+
46
+ if Rails::VERSION::MAJOR >= 7
47
+ add_gem "redis@~> 5"
48
+ else
49
+ add_gem "redis@~> 4"
50
+ end
51
+
52
+ # install action-cable-redis-backport gem if using Action Cable < 7.1
53
+ unless ActionCable::VERSION::MAJOR >= 7 && ActionCable::VERSION::MINOR >= 1
54
+ if !gemfile.match?(/gem ['"]action-cable-redis-backport['"]/)
55
+ add_gem "action-cable-redis-backport@~> 1"
56
+ end
57
+ end
58
+
59
+ # verify that the Action Cable channels folder and consumer class is available
60
+ step_path = "/app/javascript/channels/"
61
+ channels_path = Rails.root.join(entrypoint, "channels")
62
+ consumer_src = fetch(step_path, "consumer.js.tt")
63
+ consumer_path = channels_path / "consumer.js"
64
+ index_src = fetch(step_path, "index.js.#{bundler}.tt")
65
+ index_path = channels_path / "index.js"
66
+ friendly_index_path = index_path.relative_path_from(Rails.root).to_s
67
+
68
+ empty_directory channels_path unless channels_path.exist?
69
+
70
+ copy_file(consumer_src, consumer_path) unless consumer_path.exist?
71
+
72
+ if index_path.exist?
73
+ if index_path.read == index_src.read
74
+ say "✅ #{friendly_index_path} is present"
75
+ else
76
+ backup(index_path) do
77
+ copy_file(index_src, index_path, verbose: false)
78
+ end
79
+ say "✅ #{friendly_index_path} has been created"
80
+ end
81
+ else
82
+ copy_file(index_src, index_path)
83
+ end
84
+
85
+ # import Action Cable channels into application pack
86
+ channels_pattern = /import ['"](\.\.\/|\.\/)?channels['"]/
87
+ channels_commented_pattern = /\s*\/\/\s*#{channels_pattern}/
88
+ channel_import = "import \"#{prefix}channels\"\n"
89
+
90
+ if pack.match?(channels_pattern)
91
+ if pack.match?(channels_commented_pattern)
92
+ proceed = if options.key? "uncomment"
93
+ options["uncomment"]
94
+ else
95
+ !no?("✨ Action Cable seems to be commented out in your application.js. Do you want to uncomment it? (Y/n)")
96
+ end
97
+
98
+ if proceed
99
+ # uncomment_lines only works with Ruby comments 🙄
100
+ lines = pack_path.readlines
101
+ matches = lines.select { |line| line =~ channels_commented_pattern }
102
+ lines[lines.index(matches.last).to_i] = channel_import
103
+ pack_path.write lines.join
104
+ say "✅ channels imported in #{friendly_pack_path}"
105
+ else
106
+ say "🤷 your Action Cable channels are not being imported in your application.js. We trust that you have a reason for this."
107
+ end
108
+ else
109
+ say "✅ channels imported in #{friendly_pack_path}"
110
+ end
111
+ else
112
+ lines = pack_path.readlines
113
+ matches = lines.select { |line| line =~ /^import / }
114
+ lines.insert lines.index(matches.last).to_i + 1, channel_import
115
+ pack_path.write lines.join
116
+ say "✅ channels imported in #{friendly_pack_path}"
117
+ end
118
+
119
+ # create working copy of Action Cable initializer in tmp
120
+ if action_cable_initializer_path.exist?
121
+ FileUtils.cp(action_cable_initializer_path, action_cable_initializer_working_path)
122
+ else
123
+ # create Action Cable initializer if it doesn't already exist
124
+ create_file(action_cable_initializer_working_path, verbose: false) do
125
+ <<~RUBY
126
+ # frozen_string_literal: true
127
+
128
+ RUBY
129
+ end
130
+ say "✅ Action Cable initializer created"
131
+ end
132
+
133
+ # silence notoriously chatty Action Cable logs
134
+ if !action_cable_initializer_working_path.read.match?(/^[^#]*ActionCable.server.config.logger/)
135
+ append_file(action_cable_initializer_working_path, verbose: false) do
136
+ <<~RUBY
137
+ ActionCable.server.config.logger = Logger.new(nil)
138
+
139
+ RUBY
140
+ end
141
+ say "✅ Action Cable logger silenced for performance and legibility"
142
+ end
143
+
144
+ complete_step :action_cable
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "cable_ready/installer"
4
+
5
+ proceed = if options.key? "broadcaster"
6
+ options["broadcaster"]
7
+ else
8
+ !no?("✨ Make CableReady::Broadcaster available to channels, controllers, jobs and models? (Y/n)")
9
+ end
10
+
11
+ unless proceed
12
+ complete_step :broadcaster
13
+
14
+ puts "⏩ Skipping."
15
+ return
16
+ end
17
+
18
+ # include CableReady::Broadcaster in Action Cable Channel classes
19
+ channel_path = Rails.root.join("app/channels/application_cable/channel.rb")
20
+ if channel_path.exist?
21
+ lines = channel_path.readlines
22
+ if !lines.index { |line| line =~ /^\s*include CableReady::Broadcaster/ }
23
+ backup(channel_path) do
24
+ index = lines.index { |line| line.include?("class Channel < ActionCable::Channel::Base") }
25
+ lines.insert index + 1, " include CableReady::Broadcaster\n"
26
+ channel_path.write lines.join
27
+ end
28
+
29
+ puts "✅ include CableReady::Broadcaster in Action Cable channels"
30
+ else
31
+ puts "⏩ already included CableReady::Broadcaster in Action Cable channels. Skipping"
32
+ end
33
+ end
34
+
35
+ # include CableReady::Broadcaster in Action Controller classes
36
+ controller_path = Rails.root.join("app/controllers/application_controller.rb")
37
+ if controller_path.exist?
38
+ lines = controller_path.readlines
39
+ if !lines.index { |line| line =~ /^\s*include CableReady::Broadcaster/ }
40
+ backup(controller_path) do
41
+ index = lines.index { |line| line.include?("class ApplicationController < ActionController::Base") }
42
+ lines.insert index + 1, " include CableReady::Broadcaster\n"
43
+ controller_path.write lines.join
44
+ end
45
+
46
+ puts "✅ include CableReady::Broadcaster in Action Controller classes"
47
+ else
48
+ puts "⏩ already included CableReady::Broadcaster in Action Controller classes. Skipping"
49
+ end
50
+ end
51
+
52
+ # include CableReady::Broadcaster in Active Job classes, if present
53
+ if defined?(ActiveJob)
54
+ job_path = Rails.root.join("app/jobs/application_job.rb")
55
+ if job_path.exist?
56
+ lines = job_path.readlines
57
+ if !lines.index { |line| line =~ /^\s*include CableReady::Broadcaster/ }
58
+ backup(job_path) do
59
+ index = lines.index { |line| line.include?("class ApplicationJob < ActiveJob::Base") }
60
+ lines.insert index + 1, " include CableReady::Broadcaster\n"
61
+ job_path.write lines.join
62
+ end
63
+
64
+ puts "✅ include CableReady::Broadcaster in Active Job classes"
65
+ else
66
+ puts "⏩ already included CableReady::Broadcaster in Active Job classes. Skipping"
67
+ end
68
+ end
69
+ else
70
+ puts "⏩ Active Job not available. Skipping."
71
+ end
72
+
73
+ # include CableReady::Broadcaster in StateMachines, if present
74
+ if defined?(StateMachines)
75
+ lines = action_cable_initializer_working_path.read
76
+ if !lines.include?("StateMachines::Machine.prepend(CableReady::Broadcaster)")
77
+ inject_into_file action_cable_initializer_working_path, after: "CableReady.configure do |config|\n", verbose: false do
78
+ <<-RUBY
79
+
80
+ StateMachines::Machine.prepend(CableReady::Broadcaster)
81
+
82
+ RUBY
83
+ end
84
+
85
+ puts "✅ prepend CableReady::Broadcaster into StateMachines::Machine"
86
+ else
87
+ puts "⏩ already prepended CableReady::Broadcaster into StateMachines::Machine. Skipping"
88
+ end
89
+ else
90
+ puts "⏩ StateMachines not available. Skipping."
91
+ end
92
+
93
+ # include CableReady::Broadcaster in Active Record model classes
94
+ if Rails.root.join(application_record_path).exist?
95
+ lines = application_record_path.readlines
96
+ if !lines.index { |line| line =~ /^\s*include CableReady::Broadcaster/ }
97
+ backup(application_record_path) do
98
+ index = lines.index { |line| line.include?("class ApplicationRecord < ActiveRecord::Base") }
99
+ lines.insert index + 1, " include CableReady::Broadcaster\n"
100
+ application_record_path.write lines.join
101
+ end
102
+
103
+ puts "✅ include CableReady::Broadcaster in Active Record model classes"
104
+ else
105
+ puts "⏩ already included CableReady::Broadcaster in Active Record model classes. Skipping"
106
+ end
107
+ end
108
+
109
+ complete_step :broadcaster
@@ -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