anycable-rails 1.0.0.preview2 → 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +14 -125
  3. data/README.md +14 -34
  4. data/lib/anycable/rails.rb +36 -2
  5. data/lib/anycable/rails/actioncable/channel.rb +4 -0
  6. data/lib/anycable/rails/actioncable/connection.rb +24 -35
  7. data/lib/anycable/rails/actioncable/connection/serializable_identification.rb +42 -0
  8. data/lib/anycable/rails/actioncable/remote_connections.rb +11 -0
  9. data/lib/anycable/rails/channel_state.rb +48 -0
  10. data/lib/anycable/rails/compatibility.rb +4 -7
  11. data/lib/anycable/rails/compatibility/rubocop.rb +0 -1
  12. data/lib/anycable/rails/compatibility/rubocop/config/default.yml +3 -0
  13. data/lib/anycable/rails/compatibility/rubocop/cops/anycable/instance_vars.rb +1 -1
  14. data/lib/anycable/rails/compatibility/rubocop/cops/anycable/stream_from.rb +4 -4
  15. data/lib/anycable/rails/railtie.rb +10 -10
  16. data/lib/anycable/rails/refinements/subscriptions.rb +5 -0
  17. data/lib/anycable/rails/session_proxy.rb +19 -2
  18. data/lib/anycable/rails/version.rb +1 -1
  19. data/lib/generators/anycable/download/USAGE +14 -0
  20. data/lib/generators/anycable/download/download_generator.rb +77 -0
  21. data/lib/generators/anycable/setup/USAGE +2 -0
  22. data/lib/{rails/generators → generators}/anycable/setup/setup_generator.rb +67 -89
  23. data/lib/{rails/generators → generators}/anycable/setup/templates/Procfile.dev +0 -0
  24. data/lib/{rails/generators → generators}/anycable/setup/templates/config/anycable.yml.tt +12 -1
  25. data/lib/generators/anycable/setup/templates/config/cable.yml.tt +11 -0
  26. data/lib/{rails/generators/anycable/setup/templates/config/initializers/anycable.rb → generators/anycable/setup/templates/config/initializers/anycable.rb.tt} +1 -1
  27. data/lib/generators/anycable/with_os_helpers.rb +55 -0
  28. metadata +23 -47
  29. data/lib/anycable/rails/compatibility/rubocop/cops/anycable/remote_disconnect.rb +0 -31
  30. data/lib/rails/generators/anycable/setup/templates/Procfile +0 -2
  31. data/lib/rails/generators/anycable/setup/templates/bin/heroku-web +0 -7
  32. data/lib/rails/generators/anycable/setup/templates/config/cable.yml.tt +0 -11
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "action_cable/remote_connections"
4
+
5
+ ActionCable::RemoteConnections::RemoteConnection.include(ActionCable::Connection::SerializableIdentification)
6
+
7
+ ActionCable::RemoteConnections::RemoteConnection.prepend(Module.new do
8
+ def disconnect(reconnect: true)
9
+ ::AnyCable.broadcast_adapter.broadcast_command("disconnect", identifier: identifiers_json, reconnect: reconnect)
10
+ end
11
+ end)
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ module AnyCable
4
+ module Rails
5
+ module ChannelState
6
+ module ClassMethods
7
+ def state_attr_accessor(*names)
8
+ return attr_accessor(*names) unless AnyCable::Rails.enabled?
9
+
10
+ names.each do |name|
11
+ channel_state_attributes << name
12
+ class_eval <<~RUBY, __FILE__, __LINE__ + 1
13
+ def #{name}
14
+ return @#{name} if instance_variable_defined?(:@#{name})
15
+ @#{name} = AnyCable::Rails.deserialize(connection.socket.istate["#{name}"], json: true)
16
+ end
17
+
18
+ def #{name}=(val)
19
+ connection.socket.istate["#{name}"] = AnyCable::Rails.serialize(val, json: true)
20
+ instance_variable_set(:@#{name}, val)
21
+ end
22
+ RUBY
23
+ end
24
+ end
25
+
26
+ def channel_state_attributes
27
+ return @channel_state_attributes if instance_variable_defined?(:@channel_state_attributes)
28
+
29
+ @channel_state_attributes =
30
+ if superclass.respond_to?(:channel_state_attributes)
31
+ superclass.channel_state_attributes.dup
32
+ else
33
+ []
34
+ end
35
+ end
36
+ end
37
+
38
+ def self.included(base)
39
+ base.extend ClassMethods
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ ActiveSupport.on_load(:action_cable) do
46
+ # `state_attr_accessor` must be available in Action Cable
47
+ ::ActionCable::Channel::Base.include(AnyCable::Rails::ChannelState)
48
+ end
@@ -33,6 +33,10 @@ module AnyCable
33
33
  res = yield
34
34
  diff = instance_variables - was_ivars
35
35
 
36
+ if self.class.respond_to?(:channel_state_attributes)
37
+ diff.delete_if { |ivar| self.class.channel_state_attributes.include?(:"#{ivar.to_s.sub(/^@/, "")}") }
38
+ end
39
+
36
40
  unless diff.empty?
37
41
  raise AnyCable::CompatibilityError,
38
42
  "Channel instance variables are not supported by AnyCable, " \
@@ -48,12 +52,5 @@ module AnyCable
48
52
  raise AnyCable::CompatibilityError, "Periodical timers are not supported by AnyCable"
49
53
  end
50
54
  end)
51
-
52
- ActionCable::RemoteConnections::RemoteConnection.prepend(Module.new do
53
- def disconnect
54
- raise AnyCable::CompatibilityError,
55
- "Disconnecting remote clients is not supported by AnyCable yet"
56
- end
57
- end)
58
55
  end
59
56
  end
@@ -4,7 +4,6 @@ require "rubocop"
4
4
  require "pathname"
5
5
 
6
6
  require_relative "rubocop/cops/anycable/stream_from"
7
- require_relative "rubocop/cops/anycable/remote_disconnect"
8
7
  require_relative "rubocop/cops/anycable/periodical_timers"
9
8
  require_relative "rubocop/cops/anycable/instance_vars"
10
9
 
@@ -1,11 +1,14 @@
1
1
  AnyCable/InstanceVars:
2
+ Enabled: true
2
3
  Include:
3
4
  - "**/channels/**/*.rb"
4
5
 
5
6
  AnyCable/StreamFrom:
7
+ Enabled: true
6
8
  Include:
7
9
  - "**/channels/**/*.rb"
8
10
 
9
11
  AnyCable/PeriodicalTimers:
12
+ Enabled: true
10
13
  Include:
11
14
  - "**/channels/**/*.rb"
@@ -25,7 +25,7 @@ module RuboCop
25
25
  # end
26
26
  #
27
27
  class InstanceVars < RuboCop::Cop::Cop
28
- MSG = "Channel instance variables are not supported in AnyCable"
28
+ MSG = "Channel instance variables are not supported in AnyCable. Use `state_attr_accessor` instead"
29
29
 
30
30
  def on_class(node)
31
31
  find_nested_ivars(node) do |nested_ivar|
@@ -11,7 +11,7 @@ module RuboCop
11
11
  # # bad
12
12
  # class MyChannel < ApplicationCable::Channel
13
13
  # def follow
14
- # stream_from("all") {}
14
+ # stream_for(room) {}
15
15
  # end
16
16
  # end
17
17
  #
@@ -36,15 +36,15 @@ module RuboCop
36
36
  #
37
37
  class StreamFrom < RuboCop::Cop::Cop
38
38
  def_node_matcher :stream_from_with_block?, <<-PATTERN
39
- (block (send _ :stream_from ...) ...)
39
+ (block {(send _ :stream_from ...) (send _ :stream_for ...)} ...)
40
40
  PATTERN
41
41
 
42
42
  def_node_matcher :stream_from_with_callback?, <<-PATTERN
43
- (send _ :stream_from str_type? (block (send nil? :lambda) ...))
43
+ {(send _ :stream_from str_type? (block (send nil? :lambda) ...)) (send _ :stream_for ... (block (send nil? :lambda) ...))}
44
44
  PATTERN
45
45
 
46
46
  def_node_matcher :args_of_stream_from, <<-PATTERN
47
- (send _ :stream_from str_type? $...)
47
+ {(send _ :stream_from str_type? $...) (send _ :stream_for $...)}
48
48
  PATTERN
49
49
 
50
50
  def_node_matcher :coder_symbol?, "(pair (sym :coder) ...)"
@@ -1,23 +1,24 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "anycable/rails/channel_state"
4
+
3
5
  module AnyCable
4
6
  module Rails
5
7
  class Railtie < ::Rails::Railtie # :nodoc:
6
8
  initializer "anycable.disable_action_cable_mount", after: "action_cable.set_configs" do |app|
7
- # Disable Action Cable default route when AnyCable adapter is used
8
- adapter = ::ActionCable.server.config.cable&.fetch("adapter", nil)
9
- next unless AnyCable::Rails.compatible_adapter?(adapter)
9
+ next unless AnyCable::Rails.enabled?
10
10
 
11
11
  app.config.action_cable.mount_path = nil
12
12
  end
13
13
 
14
14
  initializer "anycable.logger", after: "action_cable.logger" do |_app|
15
- AnyCable.logger = ActiveSupport::TaggedLogging.new(::ActionCable.server.config.logger)
15
+ AnyCable.logger = ::ActionCable.server.config.logger
16
16
 
17
- # Broadcast server logs to STDOUT in development
18
- if ::Rails.env.development? &&
19
- !ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, STDOUT)
20
- AnyCable.configure_server do
17
+ AnyCable.configure_server do
18
+ AnyCable.logger = ActiveSupport::TaggedLogging.new(::ActionCable.server.config.logger)
19
+ # Broadcast server logs to STDOUT in development
20
+ if ::Rails.env.development? &&
21
+ !ActiveSupport::Logger.logger_outputs_to?(::Rails.logger, STDOUT)
21
22
  console = ActiveSupport::Logger.new(STDOUT)
22
23
  console.formatter = ::Rails.logger.formatter
23
24
  console.level = ::Rails.logger.level
@@ -42,8 +43,7 @@ module AnyCable
42
43
 
43
44
  initializer "anycable.connection_factory", after: "action_cable.set_configs" do |app|
44
45
  ActiveSupport.on_load(:action_cable) do
45
- adapter = ::ActionCable.server.config.cable&.fetch("adapter", nil)
46
- if AnyCable::Rails.compatible_adapter?(adapter)
46
+ if AnyCable::Rails.enabled?
47
47
  require "anycable/rails/actioncable/connection"
48
48
 
49
49
  if AnyCable.config.persistent_session_enabled
@@ -7,6 +7,11 @@ module AnyCable
7
7
  # Find or add a subscription to the list
8
8
  def fetch(identifier)
9
9
  add("identifier" => identifier) unless subscriptions[identifier]
10
+
11
+ unless subscriptions[identifier]
12
+ raise "Channel not found: #{ActiveSupport::JSON.decode(identifier).fetch("channel")}"
13
+ end
14
+
10
15
  subscriptions[identifier]
11
16
  end
12
17
  end
@@ -5,8 +5,6 @@ module AnyCable
5
5
  # Wrap `request.session` to lazily load values provided
6
6
  # in the RPC call (set by the previous calls)
7
7
  class SessionProxy
8
- delegate_missing_to :@rack_session
9
-
10
8
  attr_reader :rack_session, :socket_session
11
9
 
12
10
  def initialize(rack_session, socket_session)
@@ -41,6 +39,25 @@ module AnyCable
41
39
  rack_session.keys + socket_session.keys
42
40
  end
43
41
 
42
+ # Delegate both publuc and private methods to rack_session
43
+ def respond_to_missing?(name, include_private = false)
44
+ return false if name == :marshal_dump || name == :_dump
45
+ rack_session.respond_to?(name, include_private) || super
46
+ end
47
+
48
+ def method_missing(method, *args, &block)
49
+ if rack_session.respond_to?(method, true)
50
+ rack_session.send(method, *args, &block)
51
+ else
52
+ super
53
+ end
54
+ end
55
+
56
+ # This method is used by StimulusReflex to obtain `@by`
57
+ def instance_variable_get(name)
58
+ super || rack_session.instance_variable_get(name)
59
+ end
60
+
44
61
  private
45
62
 
46
63
  def restore!
@@ -2,6 +2,6 @@
2
2
 
3
3
  module AnyCable
4
4
  module Rails
5
- VERSION = "1.0.0.preview2"
5
+ VERSION = "1.0.0.rc1"
6
6
  end
7
7
  end
@@ -0,0 +1,14 @@
1
+ Description:
2
+ Install AnyCable-Go web server.
3
+
4
+ Example:
5
+ rails generate anycable:download
6
+
7
+ This will ask:
8
+ Where to store a binary file.
9
+ This will create:
10
+ `<bin_path>/anycable-go`.
11
+
12
+ rails generate anycable:download --bin-path=/usr/local/bin
13
+
14
+ rails generate anycable:download --version=1.0.0
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "generators/anycable/with_os_helpers"
4
+
5
+ module AnyCableRailsGenerators
6
+ # Downloads anycable-go binary
7
+ class DownloadGenerator < ::Rails::Generators::Base
8
+ namespace "anycable:download"
9
+
10
+ include WithOSHelpers
11
+
12
+ # TODO: change to latest release
13
+ VERSION = "1.0.0.preview1"
14
+
15
+ class_option :bin_path,
16
+ type: :string,
17
+ desc: "Where to download AnyCable-Go server binary (default: #{DEFAULT_BIN_PATH})"
18
+ class_option :version,
19
+ type: :string,
20
+ desc: "Specify the AnyCable-Go version (defaults to latest release)"
21
+
22
+ def download_bin
23
+ out = options[:bin_path] || DEFAULT_BIN_PATH
24
+ version = options[:version] || VERSION
25
+
26
+ download_exe(
27
+ release_url(version),
28
+ to: out,
29
+ file_name: "anycable-go"
30
+ )
31
+
32
+ true
33
+ end
34
+
35
+ private
36
+
37
+ def release_url(version)
38
+ if Gem::Version.new(version).segments.first >= 1
39
+ new_release_url(version)
40
+ else
41
+ legacy_release_url(version)
42
+ end
43
+ end
44
+
45
+ def legacy_release_url(version)
46
+ "https://github.com/anycable/anycable-go/releases/download/v#{version}/" \
47
+ "anycable-go-v#{version}-#{os_name}-#{cpu_name}"
48
+ end
49
+
50
+ def new_release_url(version)
51
+ "https://github.com/anycable/anycable-go/releases/download/v#{version}/" \
52
+ "anycable-go-#{os_name}-#{cpu_name}"
53
+ end
54
+
55
+ def download_exe(url, to:, file_name:)
56
+ file_path = File.join(to, file_name)
57
+
58
+ run "#{sudo(to)}curl -L #{url} -o #{file_path}", abort_on_failure: true
59
+ run "#{sudo(to)}chmod +x #{file_path}", abort_on_failure: true
60
+ run "#{file_path} -v", abort_on_failure: true
61
+ end
62
+
63
+ def sudo(path)
64
+ sudo = ""
65
+ unless File.writable?(path)
66
+ if yes? "Path is not writable 😕. Do you have sudo privileges?"
67
+ sudo = "sudo "
68
+ else
69
+ say_status :error, "❌ Failed to install AnyCable-Go WebSocket server", :red
70
+ raise StandardError, "Path #{path} is not writable!"
71
+ end
72
+ end
73
+
74
+ sudo
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,2 @@
1
+ Description:
2
+ Configures your application to work with AnyCable interactively.
@@ -1,34 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "generators/anycable/with_os_helpers"
4
+
3
5
  module AnyCableRailsGenerators
4
6
  # Entry point for interactive installation
5
7
  class SetupGenerator < ::Rails::Generators::Base
6
8
  namespace "anycable:setup"
7
9
  source_root File.expand_path("templates", __dir__)
8
10
 
9
- METHODS = %w[skip local docker].freeze
10
- # TODO(release): change to latest release
11
- SERVER_VERSION = "v1.0.0.preview1"
12
- OS_NAMES = %w[linux darwin freebsd win].freeze
13
- CPU_NAMES = %w[amd64 arm64 386 arm].freeze
11
+ DOCS_ROOT = "https://docs.anycable.io/v1/#"
12
+ DEVELOPMENT_METHODS = %w[skip local docker].freeze
14
13
  SERVER_SOURCES = %w[skip brew binary].freeze
15
- DEFAULT_BIN_PATH = "/usr/local/bin"
16
14
 
17
- class_option :method,
15
+ class_option :devenv,
18
16
  type: :string,
19
- desc: "Select your development environment (options: #{METHODS.join(", ")})"
17
+ desc: "Select your development environment (options: #{DEVELOPMENT_METHODS.join(", ")})"
20
18
  class_option :source,
21
19
  type: :string,
22
20
  desc: "Choose a way of installing AnyCable-Go server (options: #{SERVER_SOURCES.join(", ")})"
23
- class_option :bin_path,
24
- type: :string,
25
- desc: "Where to download AnyCable-Go server binary (default: #{DEFAULT_BIN_PATH})"
26
- class_option :os,
27
- type: :string,
28
- desc: "Specify the OS for AnyCable-Go server binary (options: #{OS_NAMES.join(", ")})"
29
- class_option :cpu,
30
- type: :string,
31
- desc: "Specify the CPU architecturefor AnyCable-Go server binary (options: #{CPU_NAMES.join(", ")})"
32
21
  class_option :skip_heroku,
33
22
  type: :boolean,
34
23
  desc: "Do not copy Heroku configs"
@@ -36,6 +25,15 @@ module AnyCableRailsGenerators
36
25
  type: :boolean,
37
26
  desc: "Do not create Procfile.dev"
38
27
 
28
+ include WithOSHelpers
29
+
30
+ class_option :bin_path,
31
+ type: :string,
32
+ desc: "Where to download AnyCable-Go server binary (default: #{DEFAULT_BIN_PATH})"
33
+ class_option :version,
34
+ type: :string,
35
+ desc: "Specify the AnyCable-Go version (defaults to latest release)"
36
+
39
37
  def welcome
40
38
  say "👋 Welcome to AnyCable interactive installer."
41
39
  end
@@ -51,32 +49,32 @@ module AnyCableRailsGenerators
51
49
  environment(nil, env: :development) do
52
50
  <<~SNIPPET
53
51
  # Specify AnyCable WebSocket server URL to use by JS client
54
- config.action_cable.url = ENV.fetch("CABLE_URL", "ws://localhost:3334/cable").presence
52
+ config.action_cable.url = ENV.fetch("CABLE_URL", "ws://localhost:8080/cable") if AnyCable::Rails.enabled?
55
53
  SNIPPET
56
54
  end
57
55
 
58
56
  environment(nil, env: :production) do
59
57
  <<~SNIPPET
60
58
  # Specify AnyCable WebSocket server URL to use by JS client
61
- config.action_cable.url = ENV["CABLE_URL"].presence
59
+ config.action_cable.url = ENV.fetch("CABLE_URL") if AnyCable::Rails.enabled?
62
60
  SNIPPET
63
61
  end
64
62
 
65
63
  say_status :info, "✅ 'config.action_cable.url' has been configured"
66
- say_status :help, "⚠️ If you're using JS client make sure you have " \
64
+ say_status :help, "⚠️ If you're using JS client make sure you have " \
67
65
  "`action_cable_meta_tag` included before any <script> tag in your application.html"
68
66
  end
69
67
 
70
68
  def development_method
71
- answer = METHODS.index(options[:method]) || 99
69
+ answer = DEVELOPMENT_METHODS.index(options[:devenv]) || 99
72
70
 
73
- until METHODS[answer.to_i]
71
+ until DEVELOPMENT_METHODS[answer.to_i]
74
72
  answer = ask "Which environment do you use for development? (1) Local, (2) Docker, (0) Skip"
75
73
  end
76
74
 
77
- case env = METHODS[answer.to_i]
75
+ case env = DEVELOPMENT_METHODS[answer.to_i]
78
76
  when "skip"
79
- say_status :help, "⚠️ Please, read this guide on how to install AnyCable-Go server 👉 https://docs.anycable.io/#/anycable-go/getting_started", :yellow
77
+ say_status :help, "⚠️ Please, read this guide on how to install AnyCable-Go server 👉 #{DOCS_ROOT}/anycable-go/getting_started", :yellow
80
78
  else
81
79
  send "install_for_#{env}"
82
80
  end
@@ -84,15 +82,21 @@ module AnyCableRailsGenerators
84
82
 
85
83
  def heroku
86
84
  if options[:skip_heroku].nil?
87
- return unless yes? "Do you use Heroku for deployment?"
85
+ return unless yes? "Do you use Heroku for deployment? [Yn]"
88
86
  elsif options[:skip_heroku]
89
87
  return
90
88
  end
91
89
 
92
- template "Procfile"
93
- inside("bin") { template "heroku-web" }
90
+ in_root do
91
+ next unless File.file?("Procfile")
92
+
93
+ contents = File.read("Procfile")
94
+ contents.sub!(/^web: (.*)$/, %q(web: [[ "$ANYCABLE_DEPLOYMENT" == "true" ]] && bundle exec anycable --server-command="anycable-go" || \1))
95
+ File.write("Procfile", contents)
96
+ say_status :info, "✅ Procfile updated"
97
+ end
94
98
 
95
- say_status :help, "️️⚠️ Please, read the required steps to configure Heroku applications 👉 https://docs.anycable.io/#/deployment/heroku", :yellow
99
+ say_status :help, "️️⚠️ Please, read the required steps to configure Heroku applications 👉 #{DOCS_ROOT}/deployment/heroku", :yellow
96
100
  end
97
101
 
98
102
  def devise
@@ -107,6 +111,14 @@ module AnyCableRailsGenerators
107
111
  say_status :info, "✅ config/initializers/anycable.rb with Devise configuration has been added"
108
112
  end
109
113
 
114
+ def rubocop_compatibility
115
+ return unless rubocop?
116
+
117
+ say_status :info, "🤖 Running static compatibility checks with RuboCop"
118
+ res = run "bundle exec rubocop -r 'anycable/rails/compatibility/rubocop' --only AnyCable/InstanceVars,AnyCable/PeriodicalTimers,AnyCable/InstanceVars"
119
+ say_status :help, "⚠️ Please, take a look at the icompatibilities above and fix them. See https://docs.anycable.io/v1/#/ruby/compatibility" unless res
120
+ end
121
+
110
122
  def finish
111
123
  say_status :info, "✅ AnyCable has been configured successfully!"
112
124
  end
@@ -114,7 +126,15 @@ module AnyCableRailsGenerators
114
126
  private
115
127
 
116
128
  def stimulus_reflex?
117
- !gemfile_lock&.match?(/^\s+stimulus_reflex\b/).nil?
129
+ !!gemfile_lock&.match?(/^\s+stimulus_reflex\b/)
130
+ end
131
+
132
+ def redis?
133
+ !!gemfile_lock&.match?(/^\s+redis\b/)
134
+ end
135
+
136
+ def rubocop?
137
+ !!gemfile_lock&.match?(/^\s+rubocop\b/)
118
138
  end
119
139
 
120
140
  def gemfile_lock
@@ -129,24 +149,23 @@ module AnyCableRailsGenerators
129
149
  end
130
150
 
131
151
  def install_for_docker
132
- say_status :help, "️️⚠️ Docker development configuration could vary", :yellow
152
+ say_status :help, "️️⚠️ Docker development configuration could vary", :yellow
133
153
 
134
154
  say "Here is an example snippet for docker-compose.yml:"
135
155
  say <<~YML
136
156
  ─────────────────────────────────────────
137
- anycable-ws:
138
- image: anycable/anycable-go:v0.6.4
157
+ ws:
158
+ image: anycable/anycable-go:1.0.0.preview1
139
159
  ports:
140
- - '3334:3334'
160
+ - '8080:8080'
141
161
  environment:
142
- PORT: 3334
143
162
  ANYCABLE_REDIS_URL: redis://redis:6379/0
144
- ANYCABLE_RPC_HOST: anycable-rpc:50051
163
+ ANYCABLE_RPC_HOST: anycable:50051
145
164
  depends_on:
146
165
  - anycable-rpc
147
166
  - redis
148
167
 
149
- anycable-rpc:
168
+ anycable:
150
169
  <<: *backend
151
170
  command: bundle exec anycable
152
171
  environment:
@@ -173,7 +192,7 @@ module AnyCableRailsGenerators
173
192
 
174
193
  case answer.to_i
175
194
  when 0
176
- say_status :help, "⚠️ Please, read this guide on how to install AnyCable-Go server 👉 https://docs.anycable.io/#/anycable-go/getting_started", :yellow
195
+ say_status :help, "⚠️ Please, read this guide on how to install AnyCable-Go server 👉 #{DOCS_ROOT}/anycable-go/getting_started", :yellow
177
196
  return
178
197
  else
179
198
  return unless send("install_from_#{SERVER_SOURCES[answer.to_i]}")
@@ -206,63 +225,22 @@ module AnyCableRailsGenerators
206
225
  end
207
226
 
208
227
  def install_from_binary
209
- out = options[:bin_path] if options[:bin_path]
210
- out ||= "/usr/local/bin" if options[:method] # User don't want interactive mode
211
- out ||= ask "Please, enter the path to download the AnyCable-Go binary to", default: DEFAULT_BIN_PATH, path: true
228
+ bin_path ||= DEFAULT_BIN_PATH if options[:devenv] # User don't want interactive mode
229
+ bin_path ||= ask "Please, enter the path to download the AnyCable-Go binary to", default: DEFAULT_BIN_PATH, path: true
212
230
 
213
- os_name = options[:os] ||
214
- OS_NAMES.find(&Gem::Platform.local.os.method(:==)) ||
215
- ask("What is your OS name?", limited_to: OS_NAMES)
216
-
217
- cpu_name = options[:cpu] ||
218
- CPU_NAMES.find(&current_cpu.method(:==)) ||
219
- ask("What is your CPU architecture?", limited_to: CPU_NAMES)
220
-
221
- download_exe(
222
- "https://github.com/anycable/anycable-go/releases/download/#{SERVER_VERSION}/" \
223
- "anycable-go-#{os_name}-#{cpu_name}",
224
- to: out,
225
- file_name: "anycable-go"
226
- )
231
+ generate "anycable:download", download_options(bin_path: bin_path)
227
232
 
228
233
  true
229
234
  end
230
235
 
231
- def download_exe(url, to:, file_name:)
232
- file_path = File.join(to, file_name)
233
-
234
- run "#{sudo(to)}curl -L #{url} -o #{file_path}", abort_on_failure: true
235
- run "#{sudo(to)}chmod +x #{file_path}", abort_on_failure: true
236
- run "#{file_path} -v", abort_on_failure: true
237
- end
238
-
239
- def sudo!(path)
240
- sudo = ""
241
- unless File.writable?(path)
242
- if yes? "Path is not writable 😕. Do you have sudo privileges?"
243
- sudo = "sudo "
244
- else
245
- say_status :error, "❌ Failed to install AnyCable-Go WebSocket server", :red
246
- raise StandardError, "Path #{path} is not writable!"
247
- end
248
- end
249
-
250
- sudo
251
- end
252
-
253
- def current_cpu
254
- case Gem::Platform.local.cpu
255
- when "x86_64", "x64"
256
- "amd64"
257
- when "x86_32", "x86", "i386", "i486", "i686"
258
- "i386"
259
- when "aarch64", "aarch64_be", /armv8/
260
- "arm64"
261
- when "arm", /armv7/, /armv6/
262
- "arm"
263
- else
264
- "unknown"
265
- end
236
+ def download_options(**params)
237
+ opts = options.merge(params)
238
+ [].tap do |args|
239
+ args << "--os #{opts[:os]}" if opts[:os]
240
+ args << "--cpu #{opts[:cpu]}" if opts[:cpu]
241
+ args << "--bin-path=#{opts[:bin_path]}" if opts[:bin_path]
242
+ args << "--version #{opts[:version]}" if opts[:version]
243
+ end.join(" ")
266
244
  end
267
245
  end
268
246
  end