Class: StimulusReflex::Installer

Inherits:
Object
  • Object
show all
Includes:
Thor::Actions, Thor::Base
Defined in:
lib/stimulus_reflex/installer.rb

Class Method Summary collapse

Class Method Details

.action_cable_initializer_pathObject



300
301
302
# File 'lib/stimulus_reflex/installer.rb', line 300

def self.action_cable_initializer_path
  @action_cable_initializer_path ||= Rails.root.join("config/initializers/action_cable.rb")
end

.action_cable_initializer_working_pathObject



304
305
306
# File 'lib/stimulus_reflex/installer.rb', line 304

def self.action_cable_initializer_working_path
  @action_cable_initializer_working_path ||= Rails.root.join(working, "action_cable.rb")
end

.add_dev_package(name) ⇒ Object



107
108
109
110
# File 'lib/stimulus_reflex/installer.rb', line 107

def self.add_dev_package(name)
  create_or_append(dev_package_list, "#{name}\n", verbose: false)
  say "☑️  Enqueued #{name} to be added to dev dependencies"
end

.add_gem(name) ⇒ Object



92
93
94
95
# File 'lib/stimulus_reflex/installer.rb', line 92

def self.add_gem(name)
  create_or_append(add_gem_list, "#{name}\n", verbose: false)
  say "☑️  Added #{name} to the Gemfile"
end

.add_gem_listObject



320
321
322
# File 'lib/stimulus_reflex/installer.rb', line 320

def self.add_gem_list
  @add_gem_list ||= Rails.root.join("tmp/stimulus_reflex_installer/add_gem_list")
end

.add_package(name) ⇒ Object



102
103
104
105
# File 'lib/stimulus_reflex/installer.rb', line 102

def self.add_package(name)
  create_or_append(package_list, "#{name}\n", verbose: false)
  say "☑️  Enqueued #{name} to be added to dependencies"
end

.append_fileObject



18
19
20
# File 'lib/stimulus_reflex/installer.rb', line 18

def self.append_file(...)
  new.append_file(...)
end

.application_record_pathObject



296
297
298
# File 'lib/stimulus_reflex/installer.rb', line 296

def self.application_record_path
  @application_record_path ||= Rails.root.join("app/models/application_record.rb")
end

.auto_detect_bundlerObject



176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/stimulus_reflex/installer.rb', line 176

def self.auto_detect_bundler
  # auto-detect build tool based on existing packages and configuration
  if importmap_path.exist?
    bundler = "importmap"
  elsif package_json_path.exist?
    package_json = package_json_path.read

    bundler = "webpacker" if package_json.include?('"@rails/webpacker":')
    bundler = "esbuild" if package_json.include?('"esbuild":')
    bundler = "vite" if package_json.include?('"vite":')
    bundler = "shakapacker" if package_json.include?('"shakapacker":')

    if !bundler
      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."
      exit
    end
  else
    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."
    exit
  end

  puts
  puts "It looks like you're using \e[1m#{bundler}\e[22m as your bundler. Is that correct? (Y/n)"
  print "> "

  input = $stdin.gets.chomp

  if input.downcase == "n"
    puts
    puts "StimulusReflex installation supports: esbuild, webpacker, vite, shakapacker and importmap."
    puts "Please run \e[1;94mrails stimulus_reflex:install[bundler]\e[0m to install StimulusReflex and CableReady."
    exit
  end

  File.write(installer_bundler_path, bundler)

  bundler
end

.auto_detect_entrypointObject



144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/stimulus_reflex/installer.rb', line 144

def self.auto_detect_entrypoint
  entrypoint = [
    "app/javascript",
    "app/frontend",
    "app/client",
    "app/webpack"
  ].find { |path| File.exist?(Rails.root.join(path)) } || "app/javascript"

  puts
  puts "Where do JavaScript files live in your app? Our best guess is: \e[1m#{entrypoint}\e[22m 🤔"
  puts "Press enter to accept this, or type a different path."
  print "> "

  input = Rails.env.test? ? "tmp/app/javascript" : $stdin.gets.chomp
  entrypoint = input unless input.blank?

  File.write(installer_entrypoint_path, entrypoint)

  entrypoint
end

.backup(path, delete: false) ⇒ Object



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
# File 'lib/stimulus_reflex/installer.rb', line 62

def self.backup(path, delete: false)
  if !path.exist?
    yield
    return
  end

  backup_path = Pathname.new("#{path}.bak")
  old_path = path.relative_path_from(Rails.root).to_s
  filename = path.to_path.split("/").last

  if backup_path.exist?
    if backup_path.read == path.read
      path.delete if delete
      yield
      return
    end
    backup_path.delete
  end

  copy_file(path, backup_path, verbose: false)
  path.delete if delete

  yield

  if path.read != backup_path.read
    create_or_append(backups_path, "#{old_path}\n", verbose: false)
  end
  say "📦 #{old_path} backed up as #{filename}.bak"
end

.backups_pathObject



316
317
318
# File 'lib/stimulus_reflex/installer.rb', line 316

def self.backups_path
  @backups_path ||= Rails.root.join("tmp/stimulus_reflex_installer/backups")
end

.bundlerObject



169
170
171
172
173
174
# File 'lib/stimulus_reflex/installer.rb', line 169

def self.bundler
  path = installer_bundler_path
  @bundler ||= File.exist?(path) ? File.read(path) : auto_detect_bundler

  @bundler.inquiry
end

.complete_step(step) ⇒ Object



39
40
41
# File 'lib/stimulus_reflex/installer.rb', line 39

def self.complete_step(step)
  create_file "tmp/stimulus_reflex_installer/#{step}", verbose: false
end

.config_pathObject



228
229
230
# File 'lib/stimulus_reflex/installer.rb', line 228

def self.config_path
  @config_path ||= create_dir_if_not_exists(Rails.root.join(entrypoint, "config"))
end

.controllers_pathObject



272
273
274
# File 'lib/stimulus_reflex/installer.rb', line 272

def self.controllers_path
  @controllers_path ||= Rails.root.join(entrypoint, "controllers")
end

.copy_fileObject



22
23
24
# File 'lib/stimulus_reflex/installer.rb', line 22

def self.copy_file(...)
  new.copy_file(...)
end

.cr_npm_versionObject



127
128
129
# File 'lib/stimulus_reflex/installer.rb', line 127

def self.cr_npm_version
  @cr_npm_version ||= CableReady::VERSION.gsub(".pre", "-pre").gsub(".rc", "-rc")
end

.create_dir_for_file_if_not_exists(file_path) ⇒ Object



221
222
223
224
225
226
# File 'lib/stimulus_reflex/installer.rb', line 221

def self.create_dir_for_file_if_not_exists(file_path)
  dir_path = File.dirname(file_path)
  create_dir_if_not_exists(dir_path)

  Pathname.new(file_path)
end

.create_dir_if_not_exists(dir_path) ⇒ Object



215
216
217
218
219
# File 'lib/stimulus_reflex/installer.rb', line 215

def self.create_dir_if_not_exists(dir_path)
  FileUtils.mkdir_p(dir_path)

  Pathname.new(dir_path)
end

.create_fileObject

Thor wrapper



14
15
16
# File 'lib/stimulus_reflex/installer.rb', line 14

def self.create_file(...)
  new.create_file(...)
end

.create_or_append(path, *args, &block) ⇒ Object



43
44
45
46
# File 'lib/stimulus_reflex/installer.rb', line 43

def self.create_or_append(path, *args, &block)
  FileUtils.touch(path)
  append_file(path, *args, &block)
end

.current_templateObject



48
49
50
# File 'lib/stimulus_reflex/installer.rb', line 48

def self.current_template
  ENV["LOCATION"].split("/").last.gsub(".rb", "")
end

.dev_package_listObject



260
261
262
# File 'lib/stimulus_reflex/installer.rb', line 260

def self.dev_package_list
  @dev_package_list ||= Rails.root.join("tmp/stimulus_reflex_installer/npm_dev_package_list")
end

.development_pathObject



308
309
310
# File 'lib/stimulus_reflex/installer.rb', line 308

def self.development_path
  @development_path ||= Rails.root.join("config/environments/development.rb")
end

.development_working_pathObject



312
313
314
# File 'lib/stimulus_reflex/installer.rb', line 312

def self.development_working_path
  @development_working_path ||= Rails.root.join(working, "development.rb")
end

.drop_package(name) ⇒ Object



112
113
114
115
# File 'lib/stimulus_reflex/installer.rb', line 112

def self.drop_package(name)
  create_or_append(drop_package_list, "#{name}\n", verbose: false)
  say "❎ Enqueued #{name} to be removed from dependencies"
end

.drop_package_listObject



264
265
266
# File 'lib/stimulus_reflex/installer.rb', line 264

def self.drop_package_list
  @drop_package_list ||= Rails.root.join("tmp/stimulus_reflex_installer/drop_npm_package_list")
end

.entrypointObject



139
140
141
142
# File 'lib/stimulus_reflex/installer.rb', line 139

def self.entrypoint
  path = installer_entrypoint_path
  @entrypoint ||= File.exist?(path) ? File.read(path) : auto_detect_entrypoint
end

.fetch(step_path, file) ⇒ Object

general utilities



32
33
34
35
36
37
# File 'lib/stimulus_reflex/installer.rb', line 32

def self.fetch(step_path, file)
  relative_path = step_path + file
  location = template_src + relative_path

  Pathname.new(location)
end

.find_index(lines) ⇒ Object



373
374
375
376
377
378
379
380
381
# File 'lib/stimulus_reflex/installer.rb', line 373

def self.find_index(lines)
  # accomodate devs who modify their development.rb file structure
  if (index = lines.index { |line| line =~ /caching-dev/ })
    index += 3
  else
    index = lines.index { |line| line =~ /^Rails.application.configure do/ } + 1
  end
  index
end

.friendly_importmap_pathObject



236
237
238
# File 'lib/stimulus_reflex/installer.rb', line 236

def self.friendly_importmap_path
  @friendly_importmap_path ||= importmap_path.relative_path_from(Rails.root).to_s
end

.friendly_pack_pathObject



244
245
246
# File 'lib/stimulus_reflex/installer.rb', line 244

def self.friendly_pack_path
  @friendly_pack_path ||= pack_path.relative_path_from(Rails.root).to_s
end

.gemfileObject



280
281
282
# File 'lib/stimulus_reflex/installer.rb', line 280

def self.gemfile
  @gemfile ||= gemfile_path.read
end

.gemfile_hashObject



117
118
119
# File 'lib/stimulus_reflex/installer.rb', line 117

def self.gemfile_hash
  Digest::MD5.hexdigest(gemfile_path.read)
end

.gemfile_pathObject



276
277
278
# File 'lib/stimulus_reflex/installer.rb', line 276

def self.gemfile_path
  @gemfile_path ||= Rails.root.join("Gemfile")
end

.halt(message) ⇒ Object



57
58
59
60
# File 'lib/stimulus_reflex/installer.rb', line 57

def self.halt(message)
  say "#{message}", :red
  create_file "tmp/stimulus_reflex_installer/halt", verbose: false
end

.importmap_pathObject



232
233
234
# File 'lib/stimulus_reflex/installer.rb', line 232

def self.importmap_path
  @importmap_path ||= Rails.root.join("config/importmap.rb")
end

.installer_bundler_pathObject



165
166
167
# File 'lib/stimulus_reflex/installer.rb', line 165

def self.installer_bundler_path
  create_dir_for_file_if_not_exists("tmp/stimulus_reflex_installer/bundler")
end

.installer_entrypoint_pathObject



135
136
137
# File 'lib/stimulus_reflex/installer.rb', line 135

def self.installer_entrypoint_path
  create_dir_for_file_if_not_exists("tmp/stimulus_reflex_installer/entrypoint")
end

.optionsObject



332
333
334
# File 'lib/stimulus_reflex/installer.rb', line 332

def self.options
  @options ||= YAML.safe_load(File.read(options_path))
end

.options_pathObject



328
329
330
# File 'lib/stimulus_reflex/installer.rb', line 328

def self.options_path
  @options_path ||= Rails.root.join("tmp/stimulus_reflex_installer/options")
end

.packObject



240
241
242
# File 'lib/stimulus_reflex/installer.rb', line 240

def self.pack
  @pack ||= pack_path.read
end

.pack_pathObject



248
249
250
251
252
253
254
# File 'lib/stimulus_reflex/installer.rb', line 248

def self.pack_path
  @pack_path ||= [
    Rails.root.join(entrypoint, "application.js"),
    Rails.root.join(entrypoint, "packs/application.js"),
    Rails.root.join(entrypoint, "entrypoints/application.js")
  ].find(&:exist?)
end

.pack_path_missing?Boolean

Returns:



52
53
54
55
# File 'lib/stimulus_reflex/installer.rb', line 52

def self.pack_path_missing?
  return false unless pack_path.nil?
  halt "#{friendly_pack_path} is missing. You need a valid application pack file to proceed."
end

.package_json_pathObject



131
132
133
# File 'lib/stimulus_reflex/installer.rb', line 131

def self.package_json_path
  @package_json_path ||= Rails.root.join("package.json")
end

.package_listObject



256
257
258
# File 'lib/stimulus_reflex/installer.rb', line 256

def self.package_list
  @package_list ||= Rails.root.join("tmp/stimulus_reflex_installer/npm_package_list")
end

.prefixObject



284
285
286
287
288
289
290
291
292
293
294
# File 'lib/stimulus_reflex/installer.rb', line 284

def self.prefix
  # standard:disable Style/RedundantStringEscape
  @prefix ||= {
    "vite" => "..\/",
    "webpacker" => "",
    "shakapacker" => "",
    "importmap" => "",
    "esbuild" => ".\/"
  }[bundler]
  # standard:enable Style/RedundantStringEscape
end

.remove_gem(name) ⇒ Object



97
98
99
100
# File 'lib/stimulus_reflex/installer.rb', line 97

def self.remove_gem(name)
  create_or_append(remove_gem_list, "#{name}\n", verbose: false)
  say "❎ Removed #{name} from Gemfile"
end

.remove_gem_listObject



324
325
326
# File 'lib/stimulus_reflex/installer.rb', line 324

def self.remove_gem_list
  @remove_gem_list ||= Rails.root.join("tmp/stimulus_reflex_installer/remove_gem_list")
end

.sayObject



26
27
28
# File 'lib/stimulus_reflex/installer.rb', line 26

def self.say(...)
  new.say(...)
end

.sr_npm_versionObject

memoized values



123
124
125
# File 'lib/stimulus_reflex/installer.rb', line 123

def self.sr_npm_version
  @sr_npm_version ||= StimulusReflex::VERSION.gsub(".pre", "-pre").gsub(".rc", "-rc")
end

.template_srcObject



268
269
270
# File 'lib/stimulus_reflex/installer.rb', line 268

def self.template_src
  @template_src ||= File.read("tmp/stimulus_reflex_installer/template_src")
end

.workingObject



336
337
338
# File 'lib/stimulus_reflex/installer.rb', line 336

def self.working
  @working ||= Rails.root.join("tmp/stimulus_reflex_installer/working")
end

.write_redis_recommendation(development_working, lines, index, gemfile) ⇒ Object

support for development step



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/stimulus_reflex/installer.rb', line 342

def self.write_redis_recommendation(development_working, lines, index, gemfile)
  # provide a recommendation for using redis-session-store, including commented source code
  if !lines.index { |line| line.include?("StimulusReflex does not support :cookie_store") }
    lines.insert index + 1, <<~RUBY
      # StimulusReflex does not support :cookie_store, and we recommend switching to Redis.
      # To use `redis-session-store`, make sure to add it to your Gemfile and run `bundle install
      # config.session_store :redis_session_store,
      #   serializer: :json,
      #   on_redis_down: ->(*a) { Rails.logger.error("Redis down! \#{a.inspect}") },
      #   redis: {
      #     expire_after: 120.minutes,
      #     key_prefix: "session:",
      #     url: ENV.fetch("REDIS_URL") { "redis://localhost:6379/1" }
      # }
    RUBY

    development_working.write lines.join
    # add redis-session-store to Gemfile, but comment it out
    if !gemfile.match?(/gem ['"]redis-session-store['"]/)
      append_file(gemfile_path, verbose: false) do
        <<~RUBY

          # StimulusReflex recommends using Redis for session storage
          # gem "redis-session-store", "0.11.5"
        RUBY
      end
      say "💡 Added redis-session-store 0.11.5 to the Gemfile, commented out"
    end
  end
end