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,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class CableReady::ChannelGenerator < Rails::Generators::NamedBase
|
|
4
|
+
source_root File.expand_path("templates", __dir__)
|
|
5
|
+
|
|
6
|
+
class_option :stream_from, type: :string
|
|
7
|
+
class_option :stream_for, type: :string
|
|
8
|
+
class_option :stimulus, type: :boolean
|
|
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
|
+
|
|
17
|
+
def check_options
|
|
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
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def create_channel
|
|
25
|
+
generate "channel", file_name, "--skip"
|
|
26
|
+
end
|
|
27
|
+
|
|
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
|
+
|
|
40
|
+
if using_broadcast_to?
|
|
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."
|
|
57
|
+
end
|
|
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
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
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
|
+
|
|
75
|
+
def option_given?
|
|
76
|
+
options.key?(:stream_from) || options.key?(:stream_for)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def using_broadcast_to?
|
|
80
|
+
@using_broadcast_to ||= option_given? ? options.key?(:stream_for) : yes?("Are you streaming to a resource using broadcast_to? (y/N)")
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def using_stimulus?
|
|
84
|
+
@using_stimulus ||= options.fetch(:stimulus) {
|
|
85
|
+
yes?("Are you going to use a Stimulus controller to subscribe to this channel? (y/N)")
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def resource
|
|
90
|
+
return @resource if @resource
|
|
91
|
+
|
|
92
|
+
stream_for = options.fetch(:stream_for) {
|
|
93
|
+
ask("Which resource are you streaming for?", default: class_name)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
stream_for = file_name if stream_for == "stream_for"
|
|
97
|
+
@resource = stream_for.camelize
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def identifier
|
|
101
|
+
return @identifier if @identifier
|
|
102
|
+
|
|
103
|
+
stream_from = options.fetch(:stream_from) {
|
|
104
|
+
ask("What is the stream identifier that goes into stream_from?", default: file_name)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
stream_from = file_name if stream_from == "stream_from"
|
|
108
|
+
@identifier = stream_from.underscore
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Action Cable provides the framework to deal with WebSockets in Rails.
|
|
2
|
+
// You can generate new channels where WebSocket features live using the `bin/rails generate channel` command.
|
|
3
|
+
|
|
4
|
+
import { createConsumer } from '@rails/actioncable'
|
|
5
|
+
|
|
6
|
+
export default createConsumer()
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
const channels = import.meta.globEager('./**/*_channel.js')
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './cable_ready'
|
data/lib/generators/cable_ready/templates/app/javascript/controllers/%file_name%_controller.js.tt
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Controller } from '@hotwired/stimulus'
|
|
2
|
+
import CableReady from 'cable_ready'
|
|
3
|
+
|
|
4
|
+
export default class extends Controller {
|
|
5
|
+
static values = { id: Number }
|
|
6
|
+
|
|
7
|
+
connect () {
|
|
8
|
+
if (this.preview) return
|
|
9
|
+
if (this.application.consumer) {
|
|
10
|
+
this.channel = this.application.consumer.subscriptions.create(
|
|
11
|
+
{
|
|
12
|
+
channel: '<%= class_name %>Channel',
|
|
13
|
+
id: this.idValue
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
received (data) {
|
|
17
|
+
if (data.cableReady) CableReady.perform(data.operations)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
)
|
|
21
|
+
} else {
|
|
22
|
+
console.error(
|
|
23
|
+
`The "<%= class_name.underscore.dasherize %>" Stimulus controller requires an Action Cable consumer.\nPlease set 'application.consumer = consumer' in your application.js.`
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
disconnect () {
|
|
29
|
+
this.channel.unsubscribe()
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get preview () {
|
|
33
|
+
return (
|
|
34
|
+
document.documentElement.hasAttribute('data-turbolinks-preview') ||
|
|
35
|
+
document.documentElement.hasAttribute('data-turbo-preview')
|
|
36
|
+
)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Application } from "@hotwired/stimulus"
|
|
2
|
+
import consumer from "../channels/consumer"
|
|
3
|
+
|
|
4
|
+
const application = Application.start()
|
|
5
|
+
|
|
6
|
+
// Configure Stimulus development experience
|
|
7
|
+
application.debug = false
|
|
8
|
+
application.consumer = consumer
|
|
9
|
+
window.Stimulus = application
|
|
10
|
+
|
|
11
|
+
export { application }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
CableReady.configure do |config|
|
|
4
|
+
# Enable/disable exiting / warning when the sanity checks fail options:
|
|
5
|
+
# `:exit` or `:warn` or `:ignore`
|
|
6
|
+
#
|
|
7
|
+
# config.on_failed_sanity_checks = :exit
|
|
8
|
+
|
|
9
|
+
# Enable/disable assets compilation
|
|
10
|
+
# `true` or `false`
|
|
11
|
+
#
|
|
12
|
+
# config.precompile_assets = true
|
|
13
|
+
|
|
14
|
+
# Define your own custom operations
|
|
15
|
+
# https://cableready.stimulusreflex.com/customization#custom-operations
|
|
16
|
+
#
|
|
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
|
|
22
|
+
|
|
23
|
+
# Specify a default debounce time for CableReady::Updatable callbacks
|
|
24
|
+
# Doing so is a best practice to avoid heavy ActionCable traffic
|
|
25
|
+
#
|
|
26
|
+
# config.updatable_debounce_time = 0.1.seconds
|
|
27
|
+
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// Esbuild is configured with 3 modes:
|
|
4
|
+
//
|
|
5
|
+
// `yarn build` - Build JavaScript and exit
|
|
6
|
+
// `yarn build --watch` - Rebuild JavaScript on change
|
|
7
|
+
// `yarn build --reload` - Reloads page when views, JavaScript, or stylesheets change
|
|
8
|
+
//
|
|
9
|
+
// Minify is enabled when "RAILS_ENV=production"
|
|
10
|
+
// Sourcemaps are enabled in non-production environments
|
|
11
|
+
|
|
12
|
+
import * as esbuild from "esbuild"
|
|
13
|
+
import path from "path"
|
|
14
|
+
import rails from "esbuild-rails"
|
|
15
|
+
import chokidar from "chokidar"
|
|
16
|
+
import http from "http"
|
|
17
|
+
import { setTimeout } from "timers/promises"
|
|
18
|
+
|
|
19
|
+
const clients = []
|
|
20
|
+
|
|
21
|
+
const entryPoints = [
|
|
22
|
+
"application.js"
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
const watchDirectories = [
|
|
26
|
+
"./app/javascript/**/*.js",
|
|
27
|
+
"./app/views/**/*.html.erb",
|
|
28
|
+
"./app/assets/builds/**/*.css", // Wait for cssbundling changes
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
const config = {
|
|
32
|
+
absWorkingDir: path.join(process.cwd(), "app/javascript"),
|
|
33
|
+
bundle: true,
|
|
34
|
+
entryPoints: entryPoints,
|
|
35
|
+
minify: process.env.RAILS_ENV == "production",
|
|
36
|
+
outdir: path.join(process.cwd(), "app/assets/builds"),
|
|
37
|
+
plugins: [rails()],
|
|
38
|
+
sourcemap: process.env.RAILS_ENV != "production"
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function buildAndReload() {
|
|
42
|
+
// Foreman & Overmind assign a separate PORT for each process
|
|
43
|
+
const port = parseInt(process.env.PORT)
|
|
44
|
+
const context = await esbuild.context({
|
|
45
|
+
...config,
|
|
46
|
+
banner: {
|
|
47
|
+
js: ` (() => new EventSource("http://localhost:${port}").onmessage = () => location.reload())();`,
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
// Reload uses an HTTP server as an even stream to reload the browser
|
|
52
|
+
http.createServer((req, res) => {
|
|
53
|
+
return clients.push(
|
|
54
|
+
res.writeHead(200, {
|
|
55
|
+
"Content-Type": "text/event-stream",
|
|
56
|
+
"Cache-Control": "no-cache",
|
|
57
|
+
"Access-Control-Allow-Origin": "*",
|
|
58
|
+
Connection: "keep-alive",
|
|
59
|
+
})
|
|
60
|
+
)
|
|
61
|
+
}).listen(port)
|
|
62
|
+
|
|
63
|
+
await context.rebuild()
|
|
64
|
+
console.log("[reload] initial build succeeded")
|
|
65
|
+
|
|
66
|
+
let ready = false
|
|
67
|
+
chokidar.watch(watchDirectories).on("ready", () => {
|
|
68
|
+
console.log("[reload] ready")
|
|
69
|
+
ready = true
|
|
70
|
+
}).on("all", async (event, path) => {
|
|
71
|
+
if (ready === false) return
|
|
72
|
+
|
|
73
|
+
if (path.includes("javascript")) {
|
|
74
|
+
try {
|
|
75
|
+
await setTimeout(20)
|
|
76
|
+
await context.rebuild()
|
|
77
|
+
console.log("[reload] build succeeded")
|
|
78
|
+
} catch (error) {
|
|
79
|
+
console.error("[reload] build failed", error)
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
clients.forEach((res) => res.write("data: update\n\n"))
|
|
83
|
+
clients.length = 0
|
|
84
|
+
})
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (process.argv.includes("--reload")) {
|
|
88
|
+
buildAndReload()
|
|
89
|
+
} else if (process.argv.includes("--watch")) {
|
|
90
|
+
let context = await esbuild.context({...config, logLevel: 'info'})
|
|
91
|
+
context.watch()
|
|
92
|
+
} else {
|
|
93
|
+
esbuild.build(config)
|
|
94
|
+
}
|
|
@@ -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
|