stimulus_reflex 3.5.0.pre9 → 3.5.0.rc1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of stimulus_reflex might be problematic. Click here for more details.

Files changed (104) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +0 -1
  3. data/Gemfile.lock +122 -127
  4. data/README.md +13 -19
  5. data/app/assets/javascripts/stimulus_reflex.js +1017 -523
  6. data/app/assets/javascripts/stimulus_reflex.umd.js +940 -496
  7. data/app/channels/stimulus_reflex/channel.rb +9 -24
  8. data/bin/console +0 -2
  9. data/bin/standardize +2 -1
  10. data/lib/generators/stimulus_reflex/stimulus_reflex_generator.rb +68 -9
  11. data/lib/generators/stimulus_reflex/templates/app/controllers/examples_controller.rb.tt +9 -0
  12. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/consumer.js.tt +6 -0
  13. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.esbuild.tt +4 -0
  14. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.importmap.tt +2 -0
  15. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.shakapacker.tt +5 -0
  16. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.vite.tt +1 -0
  17. data/lib/generators/stimulus_reflex/templates/app/javascript/channels/index.js.webpacker.tt +5 -0
  18. data/lib/generators/stimulus_reflex/templates/app/javascript/config/cable_ready.js.tt +4 -0
  19. data/lib/generators/stimulus_reflex/templates/app/javascript/config/index.js.tt +2 -0
  20. data/lib/generators/stimulus_reflex/templates/app/javascript/config/mrujs.js.tt +9 -0
  21. data/lib/generators/stimulus_reflex/templates/app/javascript/config/stimulus_reflex.js.tt +5 -0
  22. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/%file_name%_controller.js.tt +141 -0
  23. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/application.js.tt +11 -0
  24. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/application_controller.js.tt +74 -0
  25. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.esbuild.tt +7 -0
  26. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.importmap.tt +5 -0
  27. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.shakapacker.tt +5 -0
  28. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.vite.tt +5 -0
  29. data/lib/generators/stimulus_reflex/templates/app/javascript/controllers/index.js.webpacker.tt +5 -0
  30. data/{test/tmp/app/reflexes/user_reflex.rb → lib/generators/stimulus_reflex/templates/app/reflexes/%file_name%_reflex.rb.tt} +38 -9
  31. data/lib/generators/stimulus_reflex/templates/app/reflexes/application_reflex.rb.tt +27 -0
  32. data/lib/generators/stimulus_reflex/templates/app/views/examples/show.html.erb.tt +207 -0
  33. data/lib/generators/stimulus_reflex/templates/config/initializers/cable_ready.rb +27 -0
  34. data/lib/generators/stimulus_reflex/templates/config/initializers/stimulus_reflex.rb +18 -13
  35. data/lib/generators/stimulus_reflex/templates/esbuild.config.mjs.tt +94 -0
  36. data/lib/install/action_cable.rb +155 -0
  37. data/lib/install/broadcaster.rb +90 -0
  38. data/lib/install/bundle.rb +56 -0
  39. data/lib/install/compression.rb +41 -0
  40. data/lib/install/config.rb +87 -0
  41. data/lib/install/development.rb +110 -0
  42. data/lib/install/esbuild.rb +114 -0
  43. data/lib/install/example.rb +22 -0
  44. data/lib/install/importmap.rb +133 -0
  45. data/lib/install/initializers.rb +25 -0
  46. data/lib/install/mrujs.rb +133 -0
  47. data/lib/install/npm_packages.rb +25 -0
  48. data/lib/install/reflexes.rb +25 -0
  49. data/lib/install/shakapacker.rb +64 -0
  50. data/lib/install/spring.rb +54 -0
  51. data/lib/install/updatable.rb +34 -0
  52. data/lib/install/vite.rb +64 -0
  53. data/lib/install/webpacker.rb +90 -0
  54. data/lib/install/yarn.rb +55 -0
  55. data/lib/stimulus_reflex/broadcasters/broadcaster.rb +15 -8
  56. data/lib/stimulus_reflex/broadcasters/page_broadcaster.rb +7 -8
  57. data/lib/stimulus_reflex/broadcasters/selector_broadcaster.rb +10 -10
  58. data/lib/stimulus_reflex/broadcasters/update.rb +3 -0
  59. data/lib/stimulus_reflex/cable_readiness.rb +29 -0
  60. data/lib/stimulus_reflex/cable_ready_channels.rb +6 -5
  61. data/lib/stimulus_reflex/callbacks.rb +17 -1
  62. data/lib/stimulus_reflex/concern_enhancer.rb +6 -4
  63. data/lib/stimulus_reflex/configuration.rb +12 -2
  64. data/lib/stimulus_reflex/dataset.rb +11 -1
  65. data/lib/stimulus_reflex/engine.rb +16 -9
  66. data/lib/stimulus_reflex/html/document.rb +59 -0
  67. data/lib/stimulus_reflex/html/document_fragment.rb +13 -0
  68. data/lib/stimulus_reflex/importmap.rb +6 -3
  69. data/lib/stimulus_reflex/installer.rb +274 -0
  70. data/lib/stimulus_reflex/open_struct_fix.rb +2 -0
  71. data/lib/stimulus_reflex/reflex.rb +40 -31
  72. data/lib/stimulus_reflex/reflex_data.rb +19 -3
  73. data/lib/stimulus_reflex/reflex_factory.rb +6 -3
  74. data/lib/stimulus_reflex/request_parameters.rb +2 -0
  75. data/lib/stimulus_reflex/utils/logger.rb +10 -0
  76. data/lib/stimulus_reflex/utils/sanity_checker.rb +8 -48
  77. data/lib/stimulus_reflex/version.rb +1 -1
  78. data/lib/stimulus_reflex/version_checker.rb +54 -0
  79. data/lib/stimulus_reflex.rb +2 -0
  80. data/lib/tasks/stimulus_reflex/stimulus_reflex.rake +250 -0
  81. data/package.json +36 -28
  82. data/{rollup.config.js → rollup.config.mjs} +6 -24
  83. data/stimulus_reflex.gemspec +16 -19
  84. data/yarn.lock +1331 -748
  85. metadata +129 -79
  86. data/LATEST +0 -1
  87. data/app/assets/javascripts/stimulus_reflex.min.js +0 -2
  88. data/app/assets/javascripts/stimulus_reflex.min.js.map +0 -1
  89. data/app/assets/javascripts/stimulus_reflex.umd.min.js +0 -905
  90. data/app/assets/javascripts/stimulus_reflex.umd.min.js.map +0 -1
  91. data/lib/generators/stimulus_reflex/initializer_generator.rb +0 -14
  92. data/test/broadcasters/broadcaster_test.rb +0 -11
  93. data/test/broadcasters/broadcaster_test_case.rb +0 -39
  94. data/test/broadcasters/nothing_broadcaster_test.rb +0 -31
  95. data/test/broadcasters/page_broadcaster_test.rb +0 -79
  96. data/test/broadcasters/selector_broadcaster_test.rb +0 -173
  97. data/test/callbacks_test.rb +0 -652
  98. data/test/concern_enhancer_test.rb +0 -54
  99. data/test/element_test.rb +0 -254
  100. data/test/generators/stimulus_reflex_generator_test.rb +0 -58
  101. data/test/reflex_test.rb +0 -43
  102. data/test/test_helper.rb +0 -71
  103. data/test/tmp/app/reflexes/application_reflex.rb +0 -12
  104. data/yarn-error.log +0 -4964
@@ -1,57 +1,66 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- ClientAttributes = Struct.new(:reflex_id, :tab_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, :version, :suppress_logging, keyword_init: true)
3
+ require "stimulus_reflex/cable_readiness"
4
+ require "stimulus_reflex/version_checker"
5
+
6
+ # TODO remove xpath_controller and xpath_element for v4
7
+ ClientAttributes = Struct.new(
8
+ :id,
9
+ :tab_id,
10
+ :reflex_controller,
11
+ :xpath_controller,
12
+ :xpath_element,
13
+ :permanent_attribute_name,
14
+ :version,
15
+ :npm_version,
16
+ :suppress_logging,
17
+ keyword_init: true
18
+ )
4
19
 
5
20
  class StimulusReflex::Reflex
6
- class VersionMismatchError < StandardError; end
7
-
8
- include ActiveSupport::Rescuable
21
+ prepend StimulusReflex::CableReadiness
22
+ include StimulusReflex::VersionChecker
9
23
  include StimulusReflex::Callbacks
24
+ include ActiveSupport::Rescuable
10
25
  include ActionView::Helpers::TagHelper
11
26
  include CableReady::Identifiable
12
27
 
13
28
  attr_accessor :payload, :headers
14
- attr_reader :cable_ready, :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
29
+ attr_reader :channel, :url, :element, :selectors, :method_name, :broadcaster, :client_attributes, :logger
15
30
 
16
31
  alias_method :action_name, :method_name # for compatibility with controller libraries like Pundit that expect an action name
17
32
 
18
33
  delegate :connection, :stream_name, to: :channel
19
34
  delegate :controller_class, :flash, :session, to: :request
20
- delegate :broadcast, :halted, :error, to: :broadcaster
21
- delegate :reflex_id, :tab_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, :version, :suppress_logging, to: :client_attributes
35
+ delegate :broadcast, :broadcast_halt, :broadcast_forbid, :broadcast_error, to: :broadcaster
36
+ # TODO remove xpath_controller and xpath_element for v4
37
+ delegate :id, :tab_id, :reflex_controller, :xpath_controller, :xpath_element, :permanent_attribute_name, :version, :npm_version, :suppress_logging, to: :client_attributes
22
38
 
23
39
  def initialize(channel, url: nil, element: nil, selectors: [], method_name: nil, params: {}, client_attributes: {})
24
- if is_a? CableReady::Broadcaster
25
- message = <<~MSG
26
-
27
- #{self.class.name} includes CableReady::Broadcaster, and you need to remove it.
28
- Reflexes have their own CableReady interface. You can just assume that it's present.
29
- See https://docs.stimulusreflex.com/rtfm/cableready#using-cableready-inside-a-reflex-action for more details.
30
-
31
- MSG
32
- raise TypeError.new(message.strip)
33
- end
34
-
35
40
  @channel = channel
36
41
  @url = url
37
42
  @element = element
38
43
  @selectors = selectors
39
44
  @method_name = method_name
40
45
  @params = params
41
- @broadcaster = StimulusReflex::PageBroadcaster.new(self)
42
46
  @client_attributes = ClientAttributes.new(client_attributes)
47
+ @broadcaster = StimulusReflex::PageBroadcaster.new(self)
43
48
  @logger = suppress_logging ? nil : StimulusReflex::Logger.new(self)
44
- @cable_ready = StimulusReflex::CableReadyChannels.new(stream_name, reflex_id)
45
49
  @payload = {}
46
50
  @headers = {}
47
51
 
48
- if version != StimulusReflex::VERSION && StimulusReflex.config.on_failed_sanity_checks != :ignore
49
- raise VersionMismatchError.new("stimulus_reflex gem / NPM package version mismatch")
50
- end
52
+ check_version!
51
53
 
52
54
  self.params
53
55
  end
54
56
 
57
+ # TODO: remove this for v4
58
+ def reflex_id
59
+ warn "Deprecation warning: reflex_id will be removed in v4. Use id instead!" if Rails.env.development?
60
+ id
61
+ end
62
+ # END TODO: remove
63
+
55
64
  def request
56
65
  @request ||= begin
57
66
  uri = URI.parse(url)
@@ -94,10 +103,10 @@ class StimulusReflex::Reflex
94
103
  when :page
95
104
  raise StandardError.new("Cannot call :page morph after :#{broadcaster.to_sym} morph") unless broadcaster.page?
96
105
  when :nothing
97
- raise StandardError.new("Cannot call :nothing morph after :selector morph") if broadcaster.selector?
106
+ raise StandardError.new("#{broadcaster.to_sym} morph type has already been set") if broadcaster.selector?
98
107
  @broadcaster = StimulusReflex::NothingBroadcaster.new(self) unless broadcaster.nothing?
99
108
  else
100
- raise StandardError.new("Cannot call :selector morph after :nothing morph") if broadcaster.nothing?
109
+ raise StandardError.new("#{broadcaster.to_sym} morph type has already been set") if broadcaster.nothing?
101
110
  @broadcaster = StimulusReflex::SelectorBroadcaster.new(self) unless broadcaster.selector?
102
111
  broadcaster.append_morph(selectors, html)
103
112
  end
@@ -128,12 +137,7 @@ class StimulusReflex::Reflex
128
137
 
129
138
  # Invoke the reflex action specified by `name` and run all callbacks
130
139
  def process(name, *args)
131
- reflex_invoked = false
132
- result = run_callbacks(:process) {
133
- public_send(name, *args).tap { reflex_invoked = true }
134
- }
135
- @halted ||= result == false && !reflex_invoked
136
- result
140
+ run_callbacks(:process) { public_send(name, *args) }
137
141
  end
138
142
 
139
143
  # Indicates if the callback chain was halted via a throw(:abort) in a before_reflex callback.
@@ -143,6 +147,11 @@ class StimulusReflex::Reflex
143
147
  !!@halted
144
148
  end
145
149
 
150
+ # Indicates if the callback chain was halted via a throw(:forbidden) in a before_reflex callback.
151
+ def forbidden?
152
+ !!@forbidden
153
+ end
154
+
146
155
  def default_reflex
147
156
  # noop default reflex to force page reloads
148
157
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class StimulusReflex::ReflexData
2
4
  attr_reader :data
3
5
 
@@ -49,18 +51,27 @@ class StimulusReflex::ReflexData
49
51
  Rack::Utils.parse_nested_query(data["formData"])
50
52
  end
51
53
 
54
+ def params
55
+ form_params.deep_merge(url_params)
56
+ end
57
+
52
58
  def form_params
53
59
  form_data.deep_merge(data["params"] || {})
54
60
  end
55
61
 
56
- def reflex_id
57
- data["reflexId"]
62
+ def url_params
63
+ Rack::Utils.parse_nested_query(URI.parse(url).query)
64
+ end
65
+
66
+ def id
67
+ data["id"]
58
68
  end
59
69
 
60
70
  def tab_id
61
71
  data["tabId"]
62
72
  end
63
73
 
74
+ # TODO: remove this in v4
64
75
  def xpath_controller
65
76
  data["xpathController"]
66
77
  end
@@ -68,13 +79,18 @@ class StimulusReflex::ReflexData
68
79
  def xpath_element
69
80
  data["xpathElement"]
70
81
  end
82
+ # END TODO remove
71
83
 
72
84
  def reflex_controller
73
85
  data["reflexController"]
74
86
  end
75
87
 
88
+ def npm_version
89
+ data["version"].to_s
90
+ end
91
+
76
92
  def version
77
- data["version"].gsub("-pre", ".pre")
93
+ data["version"].to_s.gsub("-pre", ".pre")
78
94
  end
79
95
 
80
96
  private
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  class StimulusReflex::ReflexFactory
2
4
  class << self
3
5
  attr_reader :reflex_data
@@ -9,16 +11,17 @@ class StimulusReflex::ReflexFactory
9
11
  element: reflex_data.element,
10
12
  selectors: reflex_data.selectors,
11
13
  method_name: reflex_data.method_name,
12
- params: reflex_data.form_params,
14
+ params: reflex_data.params,
13
15
  client_attributes: {
14
- reflex_id: reflex_data.reflex_id,
16
+ id: reflex_data.id,
15
17
  tab_id: reflex_data.tab_id,
16
18
  xpath_controller: reflex_data.xpath_controller,
17
19
  xpath_element: reflex_data.xpath_element,
18
20
  reflex_controller: reflex_data.reflex_controller,
19
21
  permanent_attribute_name: reflex_data.permanent_attribute_name,
20
22
  suppress_logging: reflex_data.suppress_logging,
21
- version: reflex_data.version
23
+ version: reflex_data.version,
24
+ npm_version: reflex_data.npm_version
22
25
  })
23
26
  end
24
27
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module StimulusReflex
2
4
  class RequestParameters
3
5
  def initialize(params:, req:, url:)
@@ -46,6 +46,15 @@ module StimulusReflex
46
46
  reflex.class.to_s + "#" + reflex.method_name
47
47
  end
48
48
 
49
+ def id_full
50
+ reflex.id
51
+ end
52
+
53
+ def id
54
+ id_full[0..7]
55
+ end
56
+
57
+ # TODO: remove for v4
49
58
  def reflex_id_full
50
59
  reflex.reflex_id
51
60
  end
@@ -53,6 +62,7 @@ module StimulusReflex
53
62
  def reflex_id
54
63
  reflex_id_full[0..7]
55
64
  end
65
+ # END TODO remove
56
66
 
57
67
  def mode
58
68
  reflex.broadcaster.to_s
@@ -1,8 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class StimulusReflex::SanityChecker
4
- LATEST_VERSION_FORMAT = /^(\d+\.\d+\.\d+)$/
5
-
6
4
  class << self
7
5
  def check!
8
6
  return if ENV["SKIP_SANITY_CHECK"]
@@ -14,7 +12,6 @@ class StimulusReflex::SanityChecker
14
12
  instance = new
15
13
  instance.check_caching_enabled
16
14
  # instance.check_default_url_config
17
- instance.check_new_version_available
18
15
  end
19
16
 
20
17
  private
@@ -48,7 +45,7 @@ class StimulusReflex::SanityChecker
48
45
  if using_null_store?
49
46
  warn_and_exit <<~WARN
50
47
  👉 StimulusReflex requires caching to be enabled.
51
-
48
+
52
49
  Caching allows the session to be modified during ActionCable requests. Your config.cache_store is set to :null_store, so it won't work.
53
50
  WARN
54
51
  end
@@ -56,7 +53,7 @@ class StimulusReflex::SanityChecker
56
53
 
57
54
  def check_default_url_config
58
55
  return if StimulusReflex.config.on_missing_default_urls == :ignore
59
- if default_url_config_set? == false
56
+ if default_url_config_missing?
60
57
  puts <<~WARN
61
58
  👉 StimulusReflex strongly suggests that you set default_url_options in your environment files. Otherwise, ActionController #{"and ActionMailer " if defined?(ActionMailer)}will default to example.com when rendering route helpers.
62
59
 
@@ -70,29 +67,6 @@ class StimulusReflex::SanityChecker
70
67
  end
71
68
  end
72
69
 
73
- def check_new_version_available
74
- return if StimulusReflex.config.on_new_version_available == :ignore
75
- return if Rails.env.development? == false
76
- return if using_preview_release?
77
- begin
78
- latest_version = URI.open("https://raw.githubusercontent.com/stimulusreflex/stimulus_reflex/master/LATEST", open_timeout: 1, read_timeout: 1).read.strip
79
- if latest_version != StimulusReflex::VERSION
80
- puts <<~WARN
81
-
82
- 👉 There is a new version of StimulusReflex available!
83
- Current: #{StimulusReflex::VERSION} Latest: #{latest_version}
84
-
85
- If you upgrade, it is very important that you update BOTH Gemfile and package.json
86
- Then, run `bundle install && yarn install` to update to #{latest_version}.
87
-
88
- WARN
89
- exit if StimulusReflex.config.on_new_version_available == :exit
90
- end
91
- rescue
92
- puts "👉 StimulusReflex #{StimulusReflex::VERSION} update check skipped: connection timeout"
93
- end
94
- end
95
-
96
70
  def caching_not_enabled?
97
71
  Rails.application.config.action_controller.perform_caching == false
98
72
  end
@@ -101,24 +75,18 @@ class StimulusReflex::SanityChecker
101
75
  Rails.application.config.cache_store == :null_store
102
76
  end
103
77
 
78
+ def initializer_missing?
79
+ File.exist?(Rails.root.join("config", "initializers", "stimulus_reflex.rb")) == false
80
+ end
81
+
104
82
  def default_url_config_set?
105
83
  if defined?(ActionMailer)
106
- Rails.application.config.action_controller.default_url_options.blank? && Rails.application.config.action_mailer.default_url_options.blank?
84
+ Rails.application.config.action_controller.default_url_options.blank? || Rails.application.config.action_mailer.default_url_options.blank?
107
85
  else
108
86
  Rails.application.config.action_controller.default_url_options.blank?
109
87
  end
110
88
  end
111
89
 
112
- def using_preview_release?
113
- preview = StimulusReflex::VERSION.match?(LATEST_VERSION_FORMAT) == false
114
- puts "👉 StimulusReflex #{StimulusReflex::VERSION} update check skipped: pre-release build" if preview
115
- preview
116
- end
117
-
118
- def initializer_missing?
119
- File.exist?(Rails.root.join("config", "initializers", "stimulus_reflex.rb")) == false
120
- end
121
-
122
90
  def warn_and_exit(text)
123
91
  puts
124
92
  puts "Heads up! 🔥"
@@ -138,15 +106,7 @@ class StimulusReflex::SanityChecker
138
106
  end
139
107
 
140
108
  INFO
141
- if initializer_missing?
142
- puts <<~INFO
143
- You can create a StimulusReflex initializer with the command:
144
-
145
- bundle exec rails generate stimulus_reflex:initializer
146
-
147
- INFO
148
- end
149
- exit false if Rails.env.test? == false
109
+ exit false unless Rails.env.test?
150
110
  end
151
111
  end
152
112
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module StimulusReflex
4
- VERSION = "3.5.0.pre9"
4
+ VERSION = "3.5.0.rc1"
5
5
  end
@@ -0,0 +1,54 @@
1
+ # frozen_string_literal: true
2
+
3
+ module StimulusReflex
4
+ class VersionMismatchError < StandardError
5
+ end
6
+
7
+ module VersionChecker
8
+ def check_version!
9
+ return if StimulusReflex.config.on_failed_sanity_checks == :ignore
10
+ return if version == StimulusReflex::VERSION
11
+
12
+ level = (StimulusReflex.config.on_failed_sanity_checks == :exit) ? "error" : "warn"
13
+ reason = (level == "error") ? "failed to execute your reflex action due to" : "noticed"
14
+
15
+ mismatch = "StimulusReflex #{reason} a version mismatch between your gem and JavaScript version. Package versions must match exactly.\n\nstimulus_reflex gem: #{StimulusReflex::VERSION}\nstimulus_reflex npm: #{npm_version}"
16
+
17
+ StimulusReflex.config.logger.error("\n\e[31m#{mismatch}\e[0m")
18
+
19
+ log = {
20
+ message: mismatch,
21
+ level: level,
22
+ reflexId: id
23
+ }
24
+
25
+ event = {
26
+ name: "stimulus-reflex:version-mismatch",
27
+ reflexId: id,
28
+ detail: {
29
+ message: mismatch,
30
+ gem: StimulusReflex::VERSION,
31
+ npm: npm_version,
32
+ level: level
33
+ }
34
+ }
35
+
36
+ toast = {
37
+ text: mismatch.to_s,
38
+ destination: "https://docs.stimulusreflex.com/hello-world/setup#upgrading-package-versions-and-sanity",
39
+ reflexId: id,
40
+ level: level
41
+ }
42
+
43
+ CableReady::Channels.instance[@channel.stream_name].tap { |channel|
44
+ channel.console_log(log)
45
+ channel.dispatch_event(event)
46
+ channel.stimulus_reflex_version_mismatch(toast) if Rails.env.development?
47
+ }.broadcast
48
+
49
+ return if level == "warn"
50
+
51
+ raise VersionMismatchError.new(mismatch)
52
+ end
53
+ end
54
+ end
@@ -15,6 +15,8 @@ require "stimulus_reflex/cable_ready_channels"
15
15
  require "stimulus_reflex/concern_enhancer"
16
16
  require "stimulus_reflex/configuration"
17
17
  require "stimulus_reflex/callbacks"
18
+ require "stimulus_reflex/html/document"
19
+ require "stimulus_reflex/html/document_fragment"
18
20
  require "stimulus_reflex/request_parameters"
19
21
  require "stimulus_reflex/reflex"
20
22
  require "stimulus_reflex/reflex_data"
@@ -0,0 +1,250 @@
1
+ # frozen_string_literal: true
2
+
3
+ SR_STEPS = {
4
+ "action_cable" => "Action Cable",
5
+ "webpacker" => "Install StimulusReflex using Webpacker",
6
+ "shakapacker" => "Install StimulusReflex using Shakapacker",
7
+ "npm_packages" => "StimulusReflex and CableReady and related npm packages",
8
+ "reflexes" => "Create app/reflexes and related files",
9
+ "importmap" => "Install StimulusReflex using importmaps",
10
+ "esbuild" => "Install StimulusReflex using esbuild",
11
+ "config" => "Client initialization",
12
+ "initializers" => "StimulusReflex and CableReady initializers",
13
+ "example" => "Create an Example Reflex",
14
+ "development" => "development environment configuration",
15
+ "spring" => "Disable spring gem. Spring has been removed from Rails 7",
16
+ "mrujs" => "Swap out Rails UJS for mrujs",
17
+ "broadcaster" => "Make CableReady available to channels, controllers, jobs and models",
18
+ "updatable" => "Include CableReady::Updatable in Active Record model classes",
19
+ "yarn" => "Resolve npm dependency changes",
20
+ "bundle" => "Resolve gem dependency changes and install configuration changes",
21
+ "vite" => "Install StimulusReflex using Vite",
22
+ "compression" => "Compress WebSocket traffic with gzip"
23
+ }
24
+
25
+ SR_BUNDLERS = {
26
+ "webpacker" => ["npm_packages", "webpacker", "config", "action_cable", "reflexes", "development", "initializers", "broadcaster", "updatable", "example", "spring", "yarn", "bundle"],
27
+ "esbuild" => ["npm_packages", "esbuild", "config", "action_cable", "reflexes", "development", "initializers", "broadcaster", "updatable", "example", "spring", "yarn", "bundle"],
28
+ "vite" => ["npm_packages", "vite", "config", "action_cable", "reflexes", "development", "initializers", "broadcaster", "updatable", "example", "spring", "yarn", "bundle"],
29
+ "shakapacker" => ["npm_packages", "shakapacker", "config", "action_cable", "reflexes", "development", "initializers", "broadcaster", "updatable", "example", "spring", "yarn", "bundle"],
30
+ "importmap" => ["config", "action_cable", "importmap", "reflexes", "development", "initializers", "broadcaster", "updatable", "example", "spring", "bundle"]
31
+ }
32
+
33
+ def run_install_template(template, force: false, trace: false)
34
+ puts "--- [#{template}] ----"
35
+
36
+ if Rails.root.join("tmp/stimulus_reflex_installer/halt").exist?
37
+ FileUtils.rm(Rails.root.join("tmp/stimulus_reflex_installer/halt"))
38
+ puts "StimulusReflex installation halted. Please fix the issues above and try again."
39
+ exit
40
+ end
41
+ if Rails.root.join("tmp/stimulus_reflex_installer/#{template}").exist? && !force
42
+ puts "👍 #{SR_STEPS[template]}"
43
+ return
44
+ end
45
+
46
+ system "#{RbConfig.ruby} ./bin/rails app:template LOCATION=#{File.expand_path("../../install/#{template}.rb", __dir__)} SKIP_SANITY_CHECK=true #{"--trace" if trace}"
47
+
48
+ puts
49
+ end
50
+
51
+ namespace :stimulus_reflex do
52
+ desc "✨ Install StimulusReflex and CableReady ✨"
53
+ task :install do
54
+ FileUtils.mkdir_p(Rails.root.join("tmp/stimulus_reflex_installer/templates"))
55
+ FileUtils.mkdir_p(Rails.root.join("tmp/stimulus_reflex_installer/working"))
56
+ install_complete = Rails.root.join("tmp/stimulus_reflex_installer/complete")
57
+
58
+ bundler = nil
59
+ options = {}
60
+
61
+ ARGV.each do |arg|
62
+ # make sure we have a valid build tool specified, or proceed to automatic detection
63
+ if ["webpacker", "esbuild", "vite", "shakapacker", "importmap"].include?(arg)
64
+ bundler = arg
65
+ else
66
+ kv = arg.split("=")
67
+ if kv.length == 2
68
+ kv[1] = if kv[1] == "true"
69
+ true
70
+ else
71
+ (kv[1] == "false") ? false : kv[1]
72
+ end
73
+ options[kv[0]] = kv[1]
74
+ end
75
+ end
76
+ end
77
+
78
+ options_path = Rails.root.join("tmp/stimulus_reflex_installer/options")
79
+ options_path.write(options.to_yaml)
80
+
81
+ if install_complete.exist?
82
+ puts "✨ \e[38;5;220mStimulusReflex\e[0m and \e[38;5;220mCableReady\e[0m are already installed ✨"
83
+ puts
84
+ puts "To restart the installation process, run: \e[38;5;231mrails stimulus_reflex:install:restart\e[0m"
85
+ puts
86
+ puts "To get started, check out \e[4;97mhttps://docs.stimulusreflex.com/hello-world/quickstart\e[0m"
87
+ puts "or get help on Discord: \e[4;97mhttps://discord.gg/stimulus-reflex\e[0m. \e[38;5;196mWe are here for you.\e[0m 💙"
88
+ puts
89
+ exit
90
+ end
91
+
92
+ # if there is an installation in progress, continue where we left off
93
+ cached_entrypoint = Rails.root.join("tmp/stimulus_reflex_installer/entrypoint")
94
+ if cached_entrypoint.exist?
95
+ entrypoint = File.read(cached_entrypoint)
96
+ puts "✨ Resuming \e[38;5;220mStimulusReflex\e[0m and \e[38;5;220mCableReady\e[0m installation ✨"
97
+ puts
98
+ puts "If you have any setup issues, please consult \e[4;97mhttps://docs.stimulusreflex.com/hello-world/setup\e[0m"
99
+ puts "or get help on Discord: \e[4;97mhttps://discord.gg/stimulus-reflex\e[0m. \e[38;5;196mWe are here for you.\e[0m 💙"
100
+ puts
101
+ puts "Resuming installation into \e[1m#{entrypoint}\e[22m"
102
+ puts "Run \e[1;94mrails stimulus_reflex:install:restart\e[0m to restart the installation process"
103
+ puts
104
+ else
105
+ puts "✨ Installing \e[38;5;220mStimulusReflex\e[0m and \e[38;5;220mCableReady\e[0m ✨"
106
+ puts
107
+ puts "If you have any setup issues, please consult \e[4;97mhttps://docs.stimulusreflex.com/hello-world/setup\e[0m"
108
+ puts "or get help on Discord: \e[4;97mhttps://discord.gg/stimulus-reflex\e[0m. \e[38;5;196mWe are here for you.\e[0m 💙"
109
+ if Rails.root.join(".git").exist?
110
+ puts
111
+ puts "We recommend running \e[1;94mgit commit\e[0m before proceeding. A diff will be generated at the end."
112
+ end
113
+
114
+ if options.key? "entrypoint"
115
+ entrypoint = options["entrypoint"]
116
+ else
117
+ entrypoint = [
118
+ "app/javascript",
119
+ "app/frontend"
120
+ ].find { |path| File.exist?(Rails.root.join(path)) } || "app/javascript"
121
+
122
+ puts
123
+ puts "Where do JavaScript files live in your app? Our best guess is: \e[1m#{entrypoint}\e[22m 🤔"
124
+ puts "Press enter to accept this, or type a different path."
125
+ print "> "
126
+ input = $stdin.gets.chomp
127
+ entrypoint = input unless input.blank?
128
+ end
129
+ File.write(cached_entrypoint, entrypoint)
130
+ end
131
+
132
+ # verify their bundler before starting, unless they explicitly specified on CLI
133
+ if !bundler
134
+ # auto-detect build tool based on existing packages and configuration
135
+ if Rails.root.join("config/importmap.rb").exist?
136
+ bundler = "importmap"
137
+ elsif Rails.root.join("package.json").exist?
138
+ package_json = File.read(Rails.root.join("package.json"))
139
+ bundler = "webpacker" if package_json.include?('"@rails/webpacker":')
140
+ bundler = "esbuild" if package_json.include?('"esbuild":')
141
+ bundler = "vite" if package_json.include?('"vite":')
142
+ bundler = "shakapacker" if package_json.include?('"shakapacker":')
143
+ if !bundler
144
+ puts "❌ You must be using a node-based bundler such as esbuild, webpacker, vite or shakapacker (package.json) or importmap (config/importmap.rb) to use StimulusReflex."
145
+ exit
146
+ end
147
+ else
148
+ puts "❌ You must be using a node-based bundler such as esbuild, webpacker, vite or shakapacker (package.json) or importmap (config/importmap.rb) to use StimulusReflex."
149
+ exit
150
+ end
151
+
152
+ puts
153
+ puts "It looks like you're using \e[1m#{bundler}\e[22m as your bundler. Is that correct? (Y/n)"
154
+ print "> "
155
+ input = $stdin.gets.chomp
156
+ if input.downcase == "n"
157
+ puts
158
+ puts "StimulusReflex installation supports: esbuild, webpacker, vite, shakapacker and importmap."
159
+ puts "Please run \e[1;94mrails stimulus_reflex:install [bundler]\e[0m to install StimulusReflex and CableReady."
160
+ exit
161
+ end
162
+ end
163
+
164
+ File.write("tmp/stimulus_reflex_installer/bundler", bundler)
165
+ FileUtils.touch("tmp/stimulus_reflex_installer/backups")
166
+ File.write("tmp/stimulus_reflex_installer/template_src", File.expand_path("../../generators/stimulus_reflex/templates/", __dir__))
167
+
168
+ `bin/spring stop` if defined?(Spring)
169
+
170
+ # do the things
171
+ SR_BUNDLERS[bundler].each do |template|
172
+ run_install_template(template, trace: !!options["trace"])
173
+ end
174
+
175
+ puts
176
+ puts "🎉 \e[1;92mStimulusReflex and CableReady have been successfully installed!\e[22m 🎉"
177
+ puts
178
+ puts "👉 \e[4;97mhttps://docs.stimulusreflex.com/hello-world/quickstart\e[0m"
179
+ puts
180
+ puts "Join over 2000 StimulusReflex developers on Discord: \e[4;97mhttps://discord.gg/stimulus-reflex\e[0m"
181
+ puts
182
+
183
+ backups = File.readlines("tmp/stimulus_reflex_installer/backups").map(&:chomp)
184
+
185
+ if backups.any?
186
+ puts "🙆 The following files were modified during installation:"
187
+ puts
188
+ backups.each { |backup| puts " #{backup}" }
189
+ puts
190
+ puts "Each of these files has been backed up with a .bak extension. Please review the changes carefully."
191
+ puts "If you're happy with the changes, you can delete the .bak files."
192
+ puts
193
+ end
194
+
195
+ if Rails.root.join(".git").exist?
196
+ system "git diff > tmp/stimulus_reflex_installer.diff"
197
+ puts "🏮 A diff of all changes has been saved to \e[1mtmp/stimulus_reflex_installer.diff\e[22m"
198
+ puts
199
+ end
200
+
201
+ if Rails.root.join("app/reflexes/example_reflex.rb").exist?
202
+ launch = Rails.root.join("bin/dev").exist? ? "bin/dev" : "rails s"
203
+ puts "🚀 Launch \e[1;94m#{launch}\e[0m to access the example at ⚡ \e[4;97mhttp://localhost:3000/example\e[0m ⚡"
204
+ puts "Once you're finished with the example, you can remove it with \e[1;94mrails destroy stimulus_reflex example\e[0m"
205
+ puts
206
+ end
207
+
208
+ FileUtils.touch(install_complete)
209
+ `pkill -f spring` if Rails.root.join("tmp/stimulus_reflex_installer/kill_spring").exist?
210
+ exit
211
+ end
212
+
213
+ namespace :install do
214
+ desc "Restart StimulusReflex and CableReady installation"
215
+ task :restart do
216
+ FileUtils.rm_rf Rails.root.join("tmp/stimulus_reflex_installer")
217
+ system "rails stimulus_reflex:install #{ARGV.join(" ")}"
218
+ exit
219
+ end
220
+
221
+ desc <<~DESC
222
+ Run specific StimulusReflex install steps
223
+
224
+ #{SR_STEPS.sort.map { |step, description| "#{step.ljust(20)} #{description}" }.join("\n")}
225
+ DESC
226
+ task :step do
227
+ def warning(step = nil, force_exit = true)
228
+ return if step.to_s.include?("=")
229
+ if step
230
+ puts "⚠️ #{step} is not a valid step. Valid steps are: #{SR_STEPS.keys.join(", ")}"
231
+ else
232
+ puts "❌ You must specify a step to re-run. Valid steps are: #{SR_STEPS.keys.join(", ")}"
233
+ puts "Example: \e[1;94mrails stimulus_reflex:install:step initializers\e[0m"
234
+ end
235
+ exit if force_exit
236
+ end
237
+
238
+ warning if ARGV.empty?
239
+
240
+ ARGV.each do |step|
241
+ SR_STEPS.include?(step) ? run_install_template(step, force: true) : warning(step, false)
242
+ end
243
+
244
+ run_install_template(:bundle, force: true)
245
+ run_install_template(:yarn, force: true)
246
+
247
+ exit
248
+ end
249
+ end
250
+ end