rails_spotlight 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (30) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +60 -5
  3. data/lib/rails_spotlight/app_notifications.rb +3 -1
  4. data/lib/rails_spotlight/channels/handlers/live_console_handler.rb +64 -0
  5. data/lib/rails_spotlight/channels/handlers/logs_handler.rb +38 -0
  6. data/lib/rails_spotlight/channels/handlers.rb +29 -0
  7. data/lib/rails_spotlight/channels/silence_action_cable_broadcaster_logging.rb +26 -0
  8. data/lib/rails_spotlight/channels/spotlight_channel.rb +71 -0
  9. data/lib/rails_spotlight/channels.rb +2 -2
  10. data/lib/rails_spotlight/configuration.rb +21 -4
  11. data/lib/rails_spotlight/event.rb +2 -1
  12. data/lib/rails_spotlight/log_interceptor.rb +37 -29
  13. data/lib/rails_spotlight/middlewares/handlers/base_action_handler.rb +34 -2
  14. data/lib/rails_spotlight/middlewares/handlers/code_analysis_action_handler.rb +89 -0
  15. data/lib/rails_spotlight/middlewares/handlers/console_action_handler.rb +5 -23
  16. data/lib/rails_spotlight/middlewares/handlers/directory_index_action_handler.rb +112 -0
  17. data/lib/rails_spotlight/middlewares/handlers/file_action_handler.rb +18 -4
  18. data/lib/rails_spotlight/middlewares/handlers/meta_action_handler.rb +0 -1
  19. data/lib/rails_spotlight/middlewares/handlers/sql_action_handler.rb +6 -15
  20. data/lib/rails_spotlight/middlewares/handlers/verify_action_handler.rb +6 -2
  21. data/lib/rails_spotlight/middlewares/request_completed.rb +14 -10
  22. data/lib/rails_spotlight/middlewares/request_handler.rb +5 -1
  23. data/lib/rails_spotlight/railtie.rb +12 -4
  24. data/lib/rails_spotlight/render_view_reporter.rb +3 -1
  25. data/lib/rails_spotlight/utils.rb +2 -0
  26. data/lib/rails_spotlight/version.rb +1 -1
  27. data/lib/tasks/init.rake +75 -6
  28. metadata +9 -4
  29. data/lib/rails_spotlight/channels/live_console_channel.rb +0 -62
  30. 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.0'
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.split('.').last
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