rails_spotlight 0.2.5 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +58 -5
  3. data/chrome_ext_private_policy.md +7 -3
  4. data/lib/rails_spotlight/app_notifications.rb +3 -1
  5. data/lib/rails_spotlight/channels/handlers/live_console_handler.rb +64 -0
  6. data/lib/rails_spotlight/channels/handlers/logs_handler.rb +38 -0
  7. data/lib/rails_spotlight/channels/handlers.rb +29 -0
  8. data/lib/rails_spotlight/channels/silence_action_cable_broadcaster_logging.rb +26 -0
  9. data/lib/rails_spotlight/channels/spotlight_channel.rb +71 -0
  10. data/lib/rails_spotlight/channels.rb +2 -2
  11. data/lib/rails_spotlight/configuration.rb +21 -4
  12. data/lib/rails_spotlight/event.rb +2 -1
  13. data/lib/rails_spotlight/log_interceptor.rb +37 -29
  14. data/lib/rails_spotlight/middlewares/handlers/base_action_handler.rb +34 -2
  15. data/lib/rails_spotlight/middlewares/handlers/code_analysis_action_handler.rb +89 -0
  16. data/lib/rails_spotlight/middlewares/handlers/console_action_handler.rb +5 -23
  17. data/lib/rails_spotlight/middlewares/handlers/directory_index_action_handler.rb +112 -0
  18. data/lib/rails_spotlight/middlewares/handlers/file_action_handler.rb +18 -4
  19. data/lib/rails_spotlight/middlewares/handlers/meta_action_handler.rb +0 -1
  20. data/lib/rails_spotlight/middlewares/handlers/sql_action_handler.rb +6 -15
  21. data/lib/rails_spotlight/middlewares/handlers/verify_action_handler.rb +6 -2
  22. data/lib/rails_spotlight/middlewares/request_completed.rb +14 -10
  23. data/lib/rails_spotlight/middlewares/request_handler.rb +5 -1
  24. data/lib/rails_spotlight/railtie.rb +12 -4
  25. data/lib/rails_spotlight/render_view_reporter.rb +3 -1
  26. data/lib/rails_spotlight/utils.rb +2 -0
  27. data/lib/rails_spotlight/version.rb +1 -1
  28. data/lib/tasks/init.rake +75 -6
  29. metadata +9 -4
  30. data/lib/rails_spotlight/channels/live_console_channel.rb +0 -62
  31. data/lib/rails_spotlight/channels/request_completed_channel.rb +0 -15
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'tempfile'
5
+
6
+ module RailsSpotlight
7
+ module Middlewares
8
+ module Handlers
9
+ class CodeAnalysisActionHandler < BaseActionHandler
10
+ def skip_project_validation?
11
+ true
12
+ end
13
+
14
+ def execute
15
+ raise UnprocessableEntity, 'Please add rubocop to your project' unless rubocop_installed?
16
+ end
17
+
18
+ private
19
+
20
+ def rubocop_installed?
21
+ Gem::Specification.find_all_by_name('rubocop').any?
22
+ rescue Gem::LoadError
23
+ false
24
+ end
25
+
26
+ def json_response_body # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
27
+ require 'rubocop'
28
+
29
+ # Create a temporary file to hold the source code
30
+ tempfile = Tempfile.new(['source', '.rb'])
31
+ tempfile.write(source)
32
+ tempfile.rewind
33
+
34
+ # Create a temporary file to capture the RuboCop output
35
+ output_file = Tempfile.new(['rubocop_output', '.json'])
36
+
37
+ # Define RuboCop options
38
+ options = {
39
+ formatters: [['json', output_file.path]],
40
+ cache: false,
41
+ safe: true
42
+ }
43
+
44
+ options[:autocorrect] = variant == :autofix
45
+
46
+ # Check for custom .rubocop.yml configuration
47
+ config_file = ::RailsSpotlight.config.rubocop_config_path || File.join(RailsSpotlight::Configuration.rails_root, '.rubocop.yml')
48
+
49
+ # Run RuboCop with appropriate options
50
+ config_store = RuboCop::ConfigStore.new
51
+ config_store.options_config = config_file if File.exist?(config_file)
52
+ runner = RuboCop::Runner.new(options, config_store)
53
+
54
+ begin
55
+ runner.run([tempfile.path])
56
+
57
+ # Read the corrected source
58
+ tempfile.rewind
59
+ corrected_source = File.read(tempfile.path)
60
+
61
+ # Read the RuboCop analysis result from the output file
62
+ output_file.rewind
63
+ analysis_result = JSON.parse(output_file.read)
64
+
65
+ {
66
+ source: variant == :autofix ? corrected_source : source,
67
+ analysis_result: analysis_result,
68
+ variant: variant
69
+ }
70
+ ensure
71
+ # Close and unlink the tempfile
72
+ tempfile.close
73
+ tempfile.unlink
74
+ output_file.close
75
+ output_file.unlink
76
+ end
77
+ end
78
+
79
+ def source
80
+ @source ||= body_fetch('source')
81
+ end
82
+
83
+ def variant
84
+ @variant ||= body_fetch('variant', 'check').to_sym
85
+ end
86
+ end
87
+ end
88
+ end
89
+ end
@@ -6,46 +6,28 @@ module RailsSpotlight
6
6
  module Handlers
7
7
  class ConsoleActionHandler < BaseActionHandler
8
8
  def execute
9
- validate_project!
10
-
11
9
  RailsSpotlight.config.logger && RailsSpotlight.config.logger.info("Executing command: #{command}") # rubocop:disable Style/SafeNavigation
12
10
  executor.execute(command)
13
11
  end
14
12
 
15
13
  private
16
14
 
17
- def validate_project!
18
- return if for_project.blank?
19
- return if for_project.include?(::RailsSpotlight.config.project_name)
20
-
21
- raise UnprocessableEntity, "Check your connection settings the current command is not allowed to be executed on the #{::RailsSpotlight.config.project_name} project"
22
- end
23
-
24
15
  def executor
25
16
  @executor ||= ::RailsSpotlight::RailsCommandExecutor.new
26
17
  end
27
18
 
28
19
  def inspect_types
29
- @inspect_types ||= json_request_body.fetch('inspect_types')
20
+ @inspect_types ||= body_fetch('inspect_types')
30
21
  end
31
22
 
32
23
  def command
33
- @command ||= json_request_body.fetch('command')
34
- end
35
-
36
- def for_project
37
- @for_project ||= json_request_body['project']
24
+ @command ||= body_fetch('command')
38
25
  end
39
26
 
40
27
  def json_response_body
41
- if executor.execution_successful?
42
- {
43
- result: executor.result_as_json(inspect_types: inspect_types),
44
- project: ::RailsSpotlight.config.project_name
45
- }
46
- else
47
- executor.result_as_json.merge(project: ::RailsSpotlight.config.project_name)
48
- end
28
+ return executor.result_as_json unless executor.execution_successful?
29
+
30
+ { result: executor.result_as_json(inspect_types: inspect_types) }
49
31
  end
50
32
  end
51
33
  end
@@ -0,0 +1,112 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'find'
5
+ require 'pathname'
6
+
7
+ module RailsSpotlight
8
+ module Middlewares
9
+ module Handlers
10
+ class DirectoryIndexActionHandler < BaseActionHandler
11
+ def execute
12
+ @result = directory_to_json(::RailsSpotlight.config.rails_root)
13
+ rescue => e # rubocop:disable Style/RescueStandardError
14
+ raise UnprocessableEntity, e.message
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :result
20
+
21
+ def directory_to_json(path) # rubocop:disable Metrics/CyclomaticComplexity
22
+ relative_path = Pathname.new(path).relative_path_from(::RailsSpotlight.config.rails_root).to_s
23
+ return nil if ignored?(relative_path)
24
+
25
+ entry = {
26
+ name: File.basename(path),
27
+ path: relative_path,
28
+ dir: File.directory?(path),
29
+ children: []
30
+ }
31
+
32
+ if entry[:dir]
33
+ children = sort_children(Dir.children(path), path)
34
+ children.each do |child|
35
+ child_path = File.join(path, child)
36
+ child_entry = directory_to_json(child_path)
37
+ entry[:children] << child_entry if child_entry
38
+ end
39
+ end
40
+
41
+ return entry unless entry[:dir]
42
+ return entry unless entry[:children].empty?
43
+
44
+ show_empty_directories ? entry : nil
45
+ end
46
+
47
+ def sort_children(children, parent_path)
48
+ if sort_folders_first
49
+ children.sort_by do |child|
50
+ child_path = File.join(parent_path, child)
51
+ [File.directory?(child_path) ? 0 : 1, child.downcase]
52
+ end
53
+ else
54
+ children.sort_by(&:downcase)
55
+ end
56
+ end
57
+
58
+ def ignore
59
+ @ignore ||= body_fetch('ignore', [])
60
+ end
61
+
62
+ def sort_folders_first
63
+ @sort_folders_first ||= body_fetch('sort_folders_first', true)
64
+ end
65
+
66
+ def omnit_gitignore
67
+ @omnit_gitignore ||= body_fetch('omnit_gitignore', false)
68
+ end
69
+
70
+ def ignore_patterns
71
+ @ignore_patterns ||= ignore + ::RailsSpotlight.config.directory_index_ignore + (omnit_gitignore ? [] : gitignore_patterns)
72
+ end
73
+
74
+ def gitignore_file
75
+ @gitignore_file ||= File.join(::RailsSpotlight.config.rails_root, '.gitignore')
76
+ end
77
+
78
+ def show_empty_directories
79
+ @show_empty_directories ||= body_fetch('show_empty_directories', false)
80
+ end
81
+
82
+ def gitignore_patterns
83
+ @gitignore_patterns ||= if File.exist?(gitignore_file)
84
+ File.readlines(gitignore_file).map do |line|
85
+ line.strip!
86
+ next if line.empty? || line.start_with?('#') || line.start_with?('!')
87
+
88
+ line
89
+ end.compact
90
+ else
91
+ []
92
+ end
93
+ end
94
+
95
+ def ignored?(path)
96
+ return false if path == '.'
97
+
98
+ ignore_patterns.any? do |pattern|
99
+ File.fnmatch?(pattern, "/#{path}", File::FNM_PATHNAME | File::FNM_DOTMATCH)
100
+ end
101
+ end
102
+
103
+ def json_response_body
104
+ {
105
+ root_path: ::RailsSpotlight.config.rails_root,
106
+ result: result
107
+ }
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
@@ -23,14 +23,16 @@ module RailsSpotlight
23
23
  end
24
24
 
25
25
  def new_content
26
- json_request_body.fetch('content')
26
+ body_fetch('content')
27
27
  end
28
28
 
29
29
  def json_response_body
30
30
  {
31
31
  source: text_response_body,
32
32
  changed: write_mode?,
33
- project: ::RailsSpotlight.config.project_name
33
+ in_project: file_in_project?,
34
+ relative_path: Pathname.new(file_path).relative_path_from(::RailsSpotlight.config.rails_root).to_s,
35
+ root_path: ::RailsSpotlight.config.rails_root
34
36
  }.merge(write_mode? ? { new_content: new_content } : {})
35
37
  end
36
38
 
@@ -55,7 +57,7 @@ module RailsSpotlight
55
57
  end
56
58
 
57
59
  def request_mode
58
- @request_mode ||= json_request_body.fetch('mode', 'read')
60
+ @request_mode ||= body_fetch('mode', 'read')
59
61
  end
60
62
 
61
63
  def path_valid?
@@ -67,13 +69,17 @@ module RailsSpotlight
67
69
  original_file_path
68
70
  elsif file_in_project?
69
71
  File.join(::RailsSpotlight.config.rails_root, original_file_path)
72
+ elsif file_in_project_app_dir?
73
+ File.join(::RailsSpotlight.config.rails_root, 'app', original_file_path)
74
+ elsif file_in_project_views_dir?
75
+ File.join(::RailsSpotlight.config.rails_root, 'app', 'views', original_file_path)
70
76
  else # rubocop:disable Lint/DuplicateBranch
71
77
  original_file_path
72
78
  end
73
79
  end
74
80
 
75
81
  def original_file_path
76
- @original_file_path ||= json_request_body.fetch('file')
82
+ @original_file_path ||= body_fetch('file')
77
83
  end
78
84
 
79
85
  def path_file_in_project?
@@ -84,6 +90,14 @@ module RailsSpotlight
84
90
  File.exist?(File.join(::RailsSpotlight.config.rails_root, original_file_path))
85
91
  end
86
92
 
93
+ def file_in_project_app_dir?
94
+ File.exist?(File.join(::RailsSpotlight.config.rails_root, 'app', original_file_path))
95
+ end
96
+
97
+ def file_in_project_views_dir?
98
+ File.exist?(File.join(::RailsSpotlight.config.rails_root, 'app', 'views', original_file_path))
99
+ end
100
+
87
101
  def file_outside_project?
88
102
  !file_in_project? && File.exist?(original_file_path)
89
103
  end
@@ -11,7 +11,6 @@ module RailsSpotlight
11
11
  def json_response_body
12
12
  {
13
13
  events: events,
14
- project: ::RailsSpotlight.config.project_name,
15
14
  root_path: ::RailsSpotlight.config.rails_root
16
15
  }
17
16
  end
@@ -5,7 +5,6 @@ module RailsSpotlight
5
5
  module Handlers
6
6
  class SqlActionHandler < BaseActionHandler
7
7
  def execute
8
- validate_project!
9
8
  return transaction unless ActiveSupport.const_defined?('ExecutionContext')
10
9
 
11
10
  ActiveSupport::ExecutionContext.set(rails_spotlight: request_id) do
@@ -15,13 +14,6 @@ module RailsSpotlight
15
14
 
16
15
  private
17
16
 
18
- def validate_project!
19
- return if required_projects.blank?
20
- return if required_projects.include?(::RailsSpotlight.config.project_name)
21
-
22
- raise UnprocessableEntity, "Check your connection settings the current query is not allowed to be executed on the #{::RailsSpotlight.config.project_name} project"
23
- end
24
-
25
17
  def transaction
26
18
  ActiveRecord::Base.transaction do
27
19
  begin # rubocop:disable Style/RedundantBegin
@@ -56,8 +48,7 @@ module RailsSpotlight
56
48
  result: result,
57
49
  logs: logs,
58
50
  error: error.present? ? error.inspect : nil,
59
- query_mode: force_execution? ? 'force' : 'default',
60
- project: ::RailsSpotlight.config.project_name
51
+ query_mode: force_execution? ? 'force' : 'default'
61
52
  }
62
53
  end
63
54
 
@@ -72,15 +63,15 @@ module RailsSpotlight
72
63
  end
73
64
 
74
65
  def query
75
- @query ||= json_request_body.fetch('query')
66
+ @query ||= body_fetch('query')
76
67
  end
77
68
 
78
69
  def raw_options
79
- @raw_options ||= json_request_body.fetch('options', {}) || {}
70
+ @raw_options ||= body_fetch('options', {}) || {}
80
71
  end
81
72
 
82
- def required_projects
83
- @required_projects ||= raw_options.fetch('projects', [])
73
+ def mode
74
+ @mode ||= body_fetch('mode', 'default')
84
75
  end
85
76
 
86
77
  def use
@@ -95,7 +86,7 @@ module RailsSpotlight
95
86
  end
96
87
 
97
88
  def force_execution?
98
- @force_execution ||= json_request_body['mode'] == 'force'
89
+ @force_execution ||= mode == 'force'
99
90
  end
100
91
  end
101
92
  end
@@ -12,15 +12,19 @@ module RailsSpotlight
12
12
  "Rails Spotlight is working!\nRails version: #{Rails.version}\nRails environment: #{Rails.env}"
13
13
  end
14
14
 
15
+ def skip_project_validation?
16
+ true
17
+ end
18
+
15
19
  def json_response_body
16
20
  {
17
21
  params: request.params,
18
22
  body: request.body.read,
19
23
  content_type: request.content_type,
20
24
  request_method: request.request_method,
21
- version: request.get_header('HTTP_X_RAILS_SPOTLIGHT'),
25
+ version: request_spotlight_version,
26
+ for_projects: request_for_projects,
22
27
  current_gem_version: ::RailsSpotlight::VERSION,
23
- project: ::RailsSpotlight.config.project_name,
24
28
  action_cable_path: defined?(ActionCable) ? ActionCable&.server&.config&.mount_path : nil
25
29
  }
26
30
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'concerns/skip_request_paths'
4
+ require_relative '../channels/spotlight_channel'
4
5
 
5
6
  module RailsSpotlight
6
7
  module Middlewares
@@ -13,6 +14,8 @@ module RailsSpotlight
13
14
  end
14
15
 
15
16
  def call(env)
17
+ return app.call(env) unless ::RailsSpotlight.config.request_completed_broadcast_enabled?
18
+
16
19
  if skip?(env['PATH_INFO']) || (env['HTTP_CONNECTION'] == 'Upgrade' && env['HTTP_UPGRADE'] == 'websocket')
17
20
  app.call(env)
18
21
  else
@@ -40,17 +43,18 @@ module RailsSpotlight
40
43
  request = ActionDispatch::Request.new(env)
41
44
 
42
45
  host, url = host_and_url(env)
43
- ActionCable.server.broadcast(
44
- 'rails_spotlight_request_completed_channel',
46
+ RailsSpotlight::Channels::SpotlightChannel.broadcast(
45
47
  {
46
- rails_spotlight_version: RailsSpotlight::VERSION,
47
- id: rails_spotlight_request_id,
48
- http_method: env['REQUEST_METHOD'],
49
- host: host,
50
- url: url,
51
- format: request.format.symbol,
52
- controller: request.path_parameters[:controller],
53
- action: request.path_parameters[:action]
48
+ type: 'request_completed',
49
+ payload: {
50
+ id: rails_spotlight_request_id,
51
+ http_method: env['REQUEST_METHOD'],
52
+ host: host,
53
+ url: url,
54
+ format: request.format.symbol,
55
+ controller: request.path_parameters[:controller],
56
+ action: request.path_parameters[:action]
57
+ }
54
58
  }
55
59
  )
56
60
  end
@@ -2,11 +2,13 @@
2
2
 
3
3
  require_relative 'handlers/base_action_handler'
4
4
  require_relative 'handlers/file_action_handler'
5
+ require_relative 'handlers/directory_index_action_handler'
5
6
  require_relative 'handlers/sql_action_handler'
6
7
  require_relative 'handlers/verify_action_handler'
7
8
  require_relative 'handlers/not_found_action_handler'
8
9
  require_relative 'handlers/meta_action_handler'
9
10
  require_relative 'handlers/console_action_handler'
11
+ require_relative 'handlers/code_analysis_action_handler'
10
12
 
11
13
  module RailsSpotlight
12
14
  module Middlewares
@@ -27,14 +29,16 @@ module RailsSpotlight
27
29
 
28
30
  attr_reader :app
29
31
 
30
- def handle(request, action, content_type = :json)
32
+ def handle(request, action, content_type = :json) # rubocop:disable Metrics/CyclomaticComplexity
31
33
  args = [SecureRandom.uuid, request, content_type]
32
34
  case action
33
35
  when 'file' then Handlers::FileActionHandler.new(*args).call
36
+ when 'directory_index' then Handlers::DirectoryIndexActionHandler.new(*args).call
34
37
  when 'sql' then Handlers::SqlActionHandler.new(*args).call
35
38
  when 'verify' then Handlers::VerifyActionHandler.new(*args).call
36
39
  when 'meta' then Handlers::MetaActionHandler.new(*args).call
37
40
  when 'console' then Handlers::ConsoleActionHandler.new(*args).call
41
+ when 'code_analysis' then Handlers::CodeAnalysisActionHandler.new(*args).call
38
42
  else
39
43
  Handlers::NotFoundActionHandler.new(*args).call
40
44
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rails/railtie'
4
+ require_relative 'log_interceptor'
4
5
 
5
6
  module RailsSpotlight
6
7
  class Railtie < ::Rails::Railtie
@@ -9,7 +10,10 @@ module RailsSpotlight
9
10
  end
10
11
 
11
12
  initializer 'rails_spotlight.log_interceptor' do
12
- Rails.logger&.extend(LogInterceptor) unless Rails.env.production?
13
+ unless Rails.env.production?
14
+ Rails.logger&.extend(LogInterceptor)
15
+ defined?(Sidekiq::Logger) && Sidekiq.logger&.extend(LogInterceptor)
16
+ end
13
17
  end
14
18
 
15
19
  initializer 'rails_spotlight.subscribe_to_notifications' do
@@ -26,8 +30,7 @@ module RailsSpotlight
26
30
  app.config.after_initialize do
27
31
  update_actioncable_allowed_request_origins!
28
32
 
29
- require 'rails_spotlight/channels/request_completed_channel' if ::RailsSpotlight.config.request_completed_broadcast_enabled?
30
- require 'rails_spotlight/channels/live_console_channel' if ::RailsSpotlight.config.live_console_enabled?
33
+ require 'rails_spotlight/channels/spotlight_channel' if ::RailsSpotlight.config.request_completed_broadcast_enabled?
31
34
 
32
35
  app.routes.draw { mount ActionCable.server => '/cable' } if ::RailsSpotlight.config.auto_mount_action_cable?
33
36
  end
@@ -51,7 +54,12 @@ module RailsSpotlight
51
54
 
52
55
  return unless ::RailsSpotlight.config.request_completed_broadcast_enabled?
53
56
 
54
- app.middleware.insert_after ::RailsSpotlight::Middlewares::HeaderMarker, RailsSpotlight::Middlewares::RequestCompleted, app.config
57
+ # app.middleware.insert_after ::RailsSpotlight::Middlewares::HeaderMarker, RailsSpotlight::Middlewares::RequestCompleted, app.config
58
+ if defined? ActionDispatch::Executor
59
+ app.middleware.insert_after ActionDispatch::Executor, ::RailsSpotlight::Middlewares::RequestCompleted, app.config
60
+ else
61
+ app.middleware.use ::RailsSpotlight::Middlewares::RequestCompleted
62
+ end
55
63
  end
56
64
 
57
65
  def app
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module RailsSpotlight
2
4
  class RenderViewReporter
3
5
  def self.report_rendered_view_locals(view, locals: nil, params: nil, show_devise: false, skip_vars: [], metadata: {})
@@ -12,7 +14,7 @@ module RailsSpotlight
12
14
 
13
15
  def self.serialize_as_json(value)
14
16
  value.respond_to?(:as_json) ? value.as_json : nil
15
- rescue => e
17
+ rescue => e # rubocop:disable Style/RescueStandardError
16
18
  {
17
19
  __serialization_error: e.message,
18
20
  __source: value.inspect
@@ -15,6 +15,8 @@ module RailsSpotlight
15
15
  line: line.to_i,
16
16
  method: method
17
17
  }
18
+ rescue
19
+ nil
18
20
  end
19
21
 
20
22
  def sub_source_path(path)
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSpotlight
4
- VERSION = '0.2.5'
4
+ VERSION = '0.3.1'
5
5
  end
data/lib/tasks/init.rake CHANGED
@@ -4,7 +4,7 @@ require 'rake'
4
4
 
5
5
  namespace :rails_spotlight do # rubocop:disable Metrics/BlockLength
6
6
  desc 'Generate rails_spotlight configuration file'
7
- task generate_config: :environment do
7
+ task generate_config: :environment do # rubocop:disable Metrics/BlockLength
8
8
  require 'fileutils'
9
9
 
10
10
  config_path = Rails.root.join('config', 'rails_spotlight.yml')
@@ -19,15 +19,21 @@ namespace :rails_spotlight do # rubocop:disable Metrics/BlockLength
19
19
  MIDDLEWARE_SKIPPED_PATHS: []
20
20
  NOT_ENCODABLE_EVENT_VALUES:
21
21
  SKIP_RENDERED_IVARS: []
22
+ BLOCK_EDITING_FILES: false
23
+ BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
24
+ DIRECTORY_INDEX_IGNORE: ['/.git', '**/*.lock', '**/.DS_Store', '/app/assets/images/**', '/app/assets/fonts/**', '/app/assets/builds/**']
25
+ RUBOCOP_CONFIG_PATH: '.rubocop.yml'
22
26
  # Rest of the configuration is required for ActionCable. It will be disabled automatically in when ActionCable is not available.
23
- # LIVE_CONSOLE_ENABLED from version 0.2.3 do not require ActionCable to be enabled.
27
+ AUTO_MOUNT_ACTION_CABLE: false
28
+ ACTION_CABLE_MOUNT_PATH: /cable
29
+ # Required for all action cable features
30
+ USE_ACTION_CABLE: false
24
31
  LIVE_CONSOLE_ENABLED: false
25
32
  # Experimental feature.
26
33
  REQUEST_COMPLETED_BROADCAST_ENABLED: false
27
- AUTO_MOUNT_ACTION_CABLE: false
28
- ACTION_CABLE_MOUNT_PATH: /cable
29
- BLOCK_EDITING_FILES: false
30
- BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
34
+ LIVE_LOGS_ENABLED: false
35
+ DEFAULT_RS_SRC: default
36
+ FORM_JS_EXECUTION_TOKEN: <%= Digest::MD5.hexdigest(Rails.application.class.respond_to?(:module_parent_name) ? Rails.application.class.module_parent_name : Rails.application.class.parent_name)%>
31
37
  YAML
32
38
 
33
39
  if File.exist?(config_path)
@@ -38,4 +44,67 @@ namespace :rails_spotlight do # rubocop:disable Metrics/BlockLength
38
44
  puts 'Created config file: config/rails_spotlight.yml'
39
45
  end
40
46
  end
47
+
48
+ desc "Generate rails_spotlight JavaScript ERB partial for application layout to allow injecting JS code from the extension"
49
+ task inject_js_partial: :environment do
50
+ # Define the partial name and path
51
+ partial_name = "_rails_spotlight_extension_js.html.erb"
52
+ partial_path = "app/views/layouts/#{partial_name}"
53
+
54
+ # Define the JavaScript code
55
+ js_code = <<~JS
56
+ <script>
57
+ try {
58
+ function executeScriptFromMessage(msg) {
59
+ if (msg.token === "<%=::RailsSpotlight.config.form_js_execution_token%>") {
60
+ try {
61
+ const func = new Function(msg.code);
62
+ func();
63
+ return {status: 'Executed'};
64
+ } catch (e) {
65
+ return {status: 'Error', error: e};
66
+ }
67
+ } else {
68
+ return {status: 'Error', error: 'Invalid token provided, script execution aborted.'};
69
+ }
70
+ }
71
+
72
+ window.addEventListener('message', (event) => {
73
+ if (event.data && event.data.type === 'RAILS_SPOTLIGHT_EXTENSION_JS_EXECUTION') {
74
+ var result = executeScriptFromMessage(event.data);
75
+ if (event.data.debug) {
76
+ console.log('Script execution result:', result);
77
+ }
78
+ }
79
+ });
80
+ } catch (e) {
81
+ console.error('Error initializing the RAILS_SPOTLIGHT_EXTENSION_JS_EXECUTION script listener:', e);
82
+ }
83
+ </script>
84
+ JS
85
+
86
+ # Generate the ERB partial
87
+ File.write(partial_path, js_code)
88
+ puts "Partial created: #{partial_path}"
89
+
90
+ layout_file = Dir.glob('app/views/layouts/application.html.{erb,slim,haml}').first
91
+ layout_format = layout_file ? layout_file.split('.').last : 'erb'
92
+
93
+ if layout_file
94
+ puts "Detected layout file: #{layout_file}"
95
+ puts 'Please add the following line to your layout file at the appropriate place:'
96
+ else
97
+ puts 'No application layout file detected.'
98
+ puts 'Please manually add the following line to your application layout file:'
99
+ end
100
+
101
+ case layout_format
102
+ when 'slim'
103
+ puts "- if Rails.env.development?\n = render 'layouts/#{partial_name.split('.').first}'"
104
+ when 'haml'
105
+ puts "- if Rails.env.development?\n = render 'layouts/#{partial_name.split('.').first}'"
106
+ else
107
+ puts "<% if Rails.env.development? %>\n <%= render 'layouts/#{partial_name.split('.').first}' %>\n<% end %>"
108
+ end
109
+ end
41
110
  end