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.
- checksums.yaml +4 -4
- data/README.md +60 -5
- data/lib/rails_spotlight/app_notifications.rb +3 -1
- data/lib/rails_spotlight/channels/handlers/live_console_handler.rb +64 -0
- data/lib/rails_spotlight/channels/handlers/logs_handler.rb +38 -0
- data/lib/rails_spotlight/channels/handlers.rb +29 -0
- data/lib/rails_spotlight/channels/silence_action_cable_broadcaster_logging.rb +26 -0
- data/lib/rails_spotlight/channels/spotlight_channel.rb +71 -0
- data/lib/rails_spotlight/channels.rb +2 -2
- data/lib/rails_spotlight/configuration.rb +21 -4
- data/lib/rails_spotlight/event.rb +2 -1
- data/lib/rails_spotlight/log_interceptor.rb +37 -29
- data/lib/rails_spotlight/middlewares/handlers/base_action_handler.rb +34 -2
- data/lib/rails_spotlight/middlewares/handlers/code_analysis_action_handler.rb +89 -0
- data/lib/rails_spotlight/middlewares/handlers/console_action_handler.rb +5 -23
- data/lib/rails_spotlight/middlewares/handlers/directory_index_action_handler.rb +112 -0
- data/lib/rails_spotlight/middlewares/handlers/file_action_handler.rb +18 -4
- data/lib/rails_spotlight/middlewares/handlers/meta_action_handler.rb +0 -1
- data/lib/rails_spotlight/middlewares/handlers/sql_action_handler.rb +6 -15
- data/lib/rails_spotlight/middlewares/handlers/verify_action_handler.rb +6 -2
- data/lib/rails_spotlight/middlewares/request_completed.rb +14 -10
- data/lib/rails_spotlight/middlewares/request_handler.rb +5 -1
- data/lib/rails_spotlight/railtie.rb +12 -4
- data/lib/rails_spotlight/render_view_reporter.rb +3 -1
- data/lib/rails_spotlight/utils.rb +2 -0
- data/lib/rails_spotlight/version.rb +1 -1
- data/lib/tasks/init.rake +75 -6
- metadata +9 -4
- data/lib/rails_spotlight/channels/live_console_channel.rb +0 -62
- 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 ||=
|
20
|
+
@inspect_types ||= body_fetch('inspect_types')
|
30
21
|
end
|
31
22
|
|
32
23
|
def command
|
33
|
-
@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
|
-
|
42
|
-
|
43
|
-
|
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
|
-
|
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
|
-
|
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 ||=
|
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 ||=
|
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
|
@@ -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
|
-
|
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 ||=
|
66
|
+
@query ||= body_fetch('query')
|
76
67
|
end
|
77
68
|
|
78
69
|
def raw_options
|
79
|
-
@raw_options ||=
|
70
|
+
@raw_options ||= body_fetch('options', {}) || {}
|
80
71
|
end
|
81
72
|
|
82
|
-
def
|
83
|
-
@
|
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 ||=
|
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:
|
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
|
-
|
44
|
-
'rails_spotlight_request_completed_channel',
|
46
|
+
RailsSpotlight::Channels::SpotlightChannel.broadcast(
|
45
47
|
{
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
-
|
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/
|
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
|
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
|
-
|
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
|
-
|
28
|
-
|
29
|
-
|
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
|