rails_spotlight 0.2.1 → 0.2.2
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.
- checksums.yaml +4 -4
- data/README.md +11 -1
- data/lib/rails_spotlight/channels/live_console_channel.rb +15 -44
- data/lib/rails_spotlight/configuration.rb +10 -7
- data/lib/rails_spotlight/middlewares/handlers/console_action_handler.rb +47 -0
- data/lib/rails_spotlight/middlewares/handlers/file_action_handler.rb +58 -5
- data/lib/rails_spotlight/middlewares/handlers/meta_action_handler.rb +2 -1
- data/lib/rails_spotlight/middlewares/handlers/sql_action_handler.rb +3 -3
- data/lib/rails_spotlight/middlewares/request_handler.rb +2 -0
- data/lib/rails_spotlight/rails_command_executor.rb +83 -0
- data/lib/rails_spotlight/version.rb +1 -1
- data/lib/tasks/init.rake +4 -2
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 811024ca98d1eff1d90ec498134b41d547c19f0a731074f0f76b85f42d34ae62
|
4
|
+
data.tar.gz: fde226acfcae18f43435d64cccd3fae6b0651b0a606d3d196cd08bdaffa60403
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b4d436897c9ede10af86c7878abb46cccc119c0c2de60d68bab667ab88b58891108a875d259e64461dbcad36822ac6d5cb9793351a5ac63e88ee3aa7dd98cd52
|
7
|
+
data.tar.gz: ad6cea7c5217d8be6e9487aaa927ef9e90214a890d241000d89602ad9c1339186a90a5d1ee15578d9a09825e8e5e557e308243c3131170a4ea13ca6429892473
|
data/README.md
CHANGED
@@ -41,8 +41,10 @@ file will be created in `config/rails_spotlight.yml`
|
|
41
41
|
# Rest of the configuration is required for ActionCable. It will be disabled automatically in when ActionCable is not available.
|
42
42
|
LIVE_CONSOLE_ENABLED: true
|
43
43
|
REQUEST_COMPLETED_BROADCAST_ENABLED: false
|
44
|
-
AUTO_MOUNT_ACTION_CABLE:
|
44
|
+
AUTO_MOUNT_ACTION_CABLE: false
|
45
45
|
ACTION_CABLE_MOUNT_PATH: /cable
|
46
|
+
BLOCK_EDITING_FILES: false
|
47
|
+
BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
|
46
48
|
```
|
47
49
|
|
48
50
|
## Troubleshooting
|
@@ -57,6 +59,14 @@ Solution:
|
|
57
59
|
- Set AUTO_MOUNT_ACTION_CABLE: false
|
58
60
|
- Add manually `mount ActionCable.server => '/cable'` to `config/routes.rb` with proper authentication method
|
59
61
|
|
62
|
+
---
|
63
|
+
|
64
|
+
Requests crash when **ActionCable settings** -> **Use action cable for meta requests (required for Safari)** is on
|
65
|
+
|
66
|
+
Solution:
|
67
|
+
- Switch flag off
|
68
|
+
- REQUEST_COMPLETED_BROADCAST_ENABLED: false
|
69
|
+
|
60
70
|
## Testing
|
61
71
|
|
62
72
|
To run tests for all versions of Rails and Ruby, run:
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../rails_command_executor'
|
3
4
|
module RailsSpotlight
|
4
5
|
module Channels
|
5
6
|
class LiveConsoleChannel < ActionCable::Channel::Base
|
@@ -18,7 +19,7 @@ module RailsSpotlight
|
|
18
19
|
for_project = data['project']
|
19
20
|
return publish({ error: project_mismatch_message(for_project) }) if for_project.present? && for_project != project
|
20
21
|
|
21
|
-
output = execute_command(command,
|
22
|
+
output = execute_command(command, inspect_types)
|
22
23
|
publish(output)
|
23
24
|
end
|
24
25
|
|
@@ -37,54 +38,24 @@ module RailsSpotlight
|
|
37
38
|
::RailsSpotlight.config.project_name
|
38
39
|
end
|
39
40
|
|
40
|
-
def
|
41
|
-
|
42
|
-
inspect_types = opts[:inspect_types]
|
43
|
-
result = nil
|
44
|
-
|
45
|
-
begin
|
46
|
-
original_stdout = $stdout
|
47
|
-
$stdout = output_stream
|
48
|
-
result = eval(command) # rubocop:disable Security/Eval
|
49
|
-
ensure
|
50
|
-
$stdout = original_stdout
|
51
|
-
end
|
52
|
-
|
53
|
-
# result = eval(command)
|
54
|
-
{
|
55
|
-
result: {
|
56
|
-
inspect: result.inspect,
|
57
|
-
raw: result,
|
58
|
-
type: result.class.name,
|
59
|
-
types: result_inspect_types(inspect_types, result),
|
60
|
-
console: output_stream.string
|
61
|
-
}
|
62
|
-
}
|
63
|
-
rescue StandardError => e
|
64
|
-
{ error: e.message }
|
41
|
+
def executor
|
42
|
+
@executor ||= ::RailsSpotlight::RailsCommandExecutor.new
|
65
43
|
end
|
66
44
|
|
67
|
-
def
|
68
|
-
|
45
|
+
def execute_command(command, inspect_types)
|
46
|
+
RailsSpotlight.config.logger && RailsSpotlight.config.logger.info("Executing command: #{command}") # rubocop:disable Style/SafeNavigation
|
69
47
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
def result_types_items(result)
|
77
|
-
case result
|
78
|
-
when Array
|
79
|
-
# Create a hash with indices as keys and class names as values
|
80
|
-
result.each_with_index.to_h { |element, index| [index.to_s, element.class.name] }
|
81
|
-
when Hash
|
82
|
-
# Create a hash with string keys and class names as values
|
83
|
-
result.transform_keys(&:to_s).transform_values { |value| value.class.name }
|
48
|
+
executor.execute(command)
|
49
|
+
if executor.execution_successful?
|
50
|
+
{
|
51
|
+
result: executor.result_as_json(inspect_types: inspect_types),
|
52
|
+
project: ::RailsSpotlight.config.project_name
|
53
|
+
}
|
84
54
|
else
|
85
|
-
|
86
|
-
{}
|
55
|
+
executor.result_as_json
|
87
56
|
end
|
57
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
58
|
+
{ error: e.message }
|
88
59
|
end
|
89
60
|
end
|
90
61
|
end
|
@@ -11,20 +11,23 @@ module RailsSpotlight
|
|
11
11
|
}.freeze
|
12
12
|
|
13
13
|
attr_reader :project_name, :source_path, :logger, :storage_path, :storage_pool_size, :middleware_skipped_paths,
|
14
|
-
:not_encodable_event_values, :action_cable_mount_path
|
14
|
+
:not_encodable_event_values, :action_cable_mount_path,
|
15
|
+
:block_editing_files, :block_editing_files_outside_of_the_project
|
15
16
|
|
16
|
-
def initialize(opts = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
17
|
+
def initialize(opts = {}) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/AbcSize
|
17
18
|
@project_name = opts[:project_name] || detect_project_name
|
18
19
|
@source_path = opts[:source_path] || self.class.rails_root
|
19
20
|
@logger = opts[:logger] || Logger.new(File.join(self.class.rails_root, 'log', 'rails_spotlight.log'))
|
20
21
|
@storage_path = opts[:storage_path] || File.join(self.class.rails_root, 'tmp', 'data', 'rails_spotlight')
|
21
22
|
@storage_pool_size = opts[:storage_pool_size] || 20
|
22
|
-
@live_console_enabled = opts[:live_console_enabled].nil? ? true :
|
23
|
-
@request_completed_broadcast_enabled =
|
23
|
+
@live_console_enabled = opts[:live_console_enabled].nil? ? true : true?(opts[:live_console_enabled])
|
24
|
+
@request_completed_broadcast_enabled = true?(opts[:request_completed_broadcast_enabled])
|
24
25
|
@middleware_skipped_paths = opts[:middleware_skipped_paths] || []
|
25
26
|
@not_encodable_event_values = DEFAULT_NOT_ENCODABLE_EVENT_VALUES.merge(opts[:not_encodable_event_values] || {})
|
26
|
-
@auto_mount_action_cable = opts[:auto_mount_action_cable].nil? ? true :
|
27
|
+
@auto_mount_action_cable = opts[:auto_mount_action_cable].nil? ? true : true?(opts[:auto_mount_action_cable])
|
27
28
|
@action_cable_mount_path = opts[:action_cable_mount_path] || '/cable'
|
29
|
+
@block_editing_files = opts[:block_editing_files].nil? ? true : true?(opts[:block_editing_files])
|
30
|
+
@block_editing_files_outside_of_the_project = opts[:block_editing_files_outside_of_the_project].nil? ? false : true?(opts[:block_editing_files_outside_of_the_project])
|
28
31
|
end
|
29
32
|
|
30
33
|
def live_console_enabled
|
@@ -75,8 +78,8 @@ module RailsSpotlight
|
|
75
78
|
|
76
79
|
private
|
77
80
|
|
78
|
-
def
|
79
|
-
|
81
|
+
def true?(value)
|
82
|
+
[true, 'true', 1, '1'].include?(value)
|
80
83
|
end
|
81
84
|
|
82
85
|
def detect_project_name
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../rails_command_executor'
|
4
|
+
module RailsSpotlight
|
5
|
+
module Middlewares
|
6
|
+
module Handlers
|
7
|
+
class ConsoleActionHandler < BaseActionHandler
|
8
|
+
def execute
|
9
|
+
validate_project!
|
10
|
+
|
11
|
+
RailsSpotlight.config.logger && RailsSpotlight.config.logger.info("Executing command: #{command}") # rubocop:disable Style/SafeNavigation
|
12
|
+
executor.execute(command)
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
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
|
+
def executor
|
25
|
+
@executor ||= ::RailsSpotlight::RailsCommandExecutor.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect_types
|
29
|
+
@inspect_types ||= json_request_body.fetch('inspect_types')
|
30
|
+
end
|
31
|
+
|
32
|
+
def command
|
33
|
+
@command ||= json_request_body.fetch('command')
|
34
|
+
end
|
35
|
+
|
36
|
+
def for_project
|
37
|
+
@for_project ||= json_request_body['project']
|
38
|
+
end
|
39
|
+
|
40
|
+
def json_response_body
|
41
|
+
executor.result_as_json(inspect_types: inspect_types)
|
42
|
+
.merge(project: ::RailsSpotlight.config.project_name)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -7,7 +7,11 @@ module RailsSpotlight
|
|
7
7
|
def execute
|
8
8
|
raise NotFound, 'File not found' unless path_valid?
|
9
9
|
|
10
|
-
|
10
|
+
if write_mode?
|
11
|
+
try_to_update_file
|
12
|
+
else
|
13
|
+
File.read(file_path)
|
14
|
+
end
|
11
15
|
rescue => e # rubocop:disable Style/RescueStandardError
|
12
16
|
raise UnprocessableEntity, e.message
|
13
17
|
end
|
@@ -34,6 +38,22 @@ module RailsSpotlight
|
|
34
38
|
request_mode == 'write'
|
35
39
|
end
|
36
40
|
|
41
|
+
def try_to_update_file
|
42
|
+
raise UnprocessableEntity, editing_files_block_msg if block_editing_files?
|
43
|
+
raise UnprocessableEntity, editing_files_blocked_err_msg if editing_outside_project_file_is_blocked?(file_path)
|
44
|
+
|
45
|
+
RailsSpotlight.config.logger && RailsSpotlight.config.logger.info("Updating file: #{file_path}") # rubocop:disable Style/SafeNavigation
|
46
|
+
File.write(file_path, new_content)
|
47
|
+
end
|
48
|
+
|
49
|
+
def editing_files_block_msg
|
50
|
+
'Editing files is blocked. Please check the Rails spotlight BLOCK_EDITING_FILES configuration.'
|
51
|
+
end
|
52
|
+
|
53
|
+
def editing_files_blocked_err_msg
|
54
|
+
'Editing files is blocked. Please check the Rails spotlight BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT configuration.'
|
55
|
+
end
|
56
|
+
|
37
57
|
def request_mode
|
38
58
|
@request_mode ||= json_request_body.fetch('mode', 'read')
|
39
59
|
end
|
@@ -43,12 +63,45 @@ module RailsSpotlight
|
|
43
63
|
end
|
44
64
|
|
45
65
|
def file_path
|
46
|
-
@file_path ||= if
|
47
|
-
|
48
|
-
|
49
|
-
File.join(::RailsSpotlight.config.rails_root,
|
66
|
+
@file_path ||= if path_file_in_project?
|
67
|
+
original_file_path
|
68
|
+
elsif file_in_project?
|
69
|
+
File.join(::RailsSpotlight.config.rails_root, original_file_path)
|
70
|
+
else # rubocop:disable Lint/DuplicateBranch
|
71
|
+
original_file_path
|
50
72
|
end
|
51
73
|
end
|
74
|
+
|
75
|
+
def original_file_path
|
76
|
+
@original_file_path ||= json_request_body.fetch('file')
|
77
|
+
end
|
78
|
+
|
79
|
+
def path_file_in_project?
|
80
|
+
@path_file_in_project ||= original_file_path.start_with?(::RailsSpotlight.config.rails_root)
|
81
|
+
end
|
82
|
+
|
83
|
+
def file_in_project?
|
84
|
+
File.exist?(File.join(::RailsSpotlight.config.rails_root, original_file_path))
|
85
|
+
end
|
86
|
+
|
87
|
+
def file_outside_project?
|
88
|
+
!file_in_project? && File.exist?(original_file_path)
|
89
|
+
end
|
90
|
+
|
91
|
+
def editing_outside_project_file_is_blocked?(file_path)
|
92
|
+
return false unless file_outside_project?
|
93
|
+
return false unless block_editing_files_outside_of_the_project?
|
94
|
+
|
95
|
+
!file_path.start_with?(::RailsSpotlight.config.rails_root)
|
96
|
+
end
|
97
|
+
|
98
|
+
def block_editing_files?
|
99
|
+
::RailsSpotlight.config.block_editing_files
|
100
|
+
end
|
101
|
+
|
102
|
+
def block_editing_files_outside_of_the_project?
|
103
|
+
::RailsSpotlight.config.block_editing_files_outside_of_the_project
|
104
|
+
end
|
52
105
|
end
|
53
106
|
end
|
54
107
|
end
|
@@ -16,11 +16,10 @@ module RailsSpotlight
|
|
16
16
|
private
|
17
17
|
|
18
18
|
def validate_project!
|
19
|
-
Rails.logger.warn required_projects
|
20
19
|
return if required_projects.blank?
|
21
20
|
return if required_projects.include?(::RailsSpotlight.config.project_name)
|
22
21
|
|
23
|
-
raise UnprocessableEntity, "Check your
|
22
|
+
raise UnprocessableEntity, "Check your connection settings the current query is not allowed to be executed on the #{::RailsSpotlight.config.project_name} project"
|
24
23
|
end
|
25
24
|
|
26
25
|
def transaction
|
@@ -37,7 +36,8 @@ module RailsSpotlight
|
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
40
|
-
def run
|
39
|
+
def run # rubocop:disable Metrics/AbcSize
|
40
|
+
RailsSpotlight.config.logger && RailsSpotlight.config.logger.info("Executing query: #{query}") # rubocop:disable Style/SafeNavigation
|
41
41
|
return self.result = ActiveRecord::Base.connection.exec_query(query) if connection_options.blank? || !ActiveRecord::Base.respond_to?(:connects_to)
|
42
42
|
|
43
43
|
connections = ActiveRecord::Base.connects_to(**connection_options)
|
@@ -6,6 +6,7 @@ require_relative 'handlers/sql_action_handler'
|
|
6
6
|
require_relative 'handlers/verify_action_handler'
|
7
7
|
require_relative 'handlers/not_found_action_handler'
|
8
8
|
require_relative 'handlers/meta_action_handler'
|
9
|
+
require_relative 'handlers/console_action_handler'
|
9
10
|
|
10
11
|
module RailsSpotlight
|
11
12
|
module Middlewares
|
@@ -33,6 +34,7 @@ module RailsSpotlight
|
|
33
34
|
when 'sql' then Handlers::SqlActionHandler.new(*args).call
|
34
35
|
when 'verify' then Handlers::VerifyActionHandler.new(*args).call
|
35
36
|
when 'meta' then Handlers::MetaActionHandler.new(*args).call
|
37
|
+
when 'console' then Handlers::ConsoleActionHandler.new(*args).call
|
36
38
|
else
|
37
39
|
Handlers::NotFoundActionHandler.new(*args).call
|
38
40
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RailsSpotlight
|
4
|
+
class RailsCommandExecutor
|
5
|
+
def execute(command)
|
6
|
+
output_stream = StringIO.new # Create a new StringIO object to capture output
|
7
|
+
@result = nil
|
8
|
+
@error = nil
|
9
|
+
@syntax_error = false
|
10
|
+
|
11
|
+
begin
|
12
|
+
original_stdout = $stdout
|
13
|
+
$stdout = output_stream
|
14
|
+
@result = eval(command) # rubocop:disable Security/Eval
|
15
|
+
rescue SyntaxError => e
|
16
|
+
@error = e
|
17
|
+
@syntax_error = true
|
18
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
19
|
+
@error = e
|
20
|
+
ensure
|
21
|
+
$stdout = original_stdout
|
22
|
+
end
|
23
|
+
|
24
|
+
@console = output_stream.string
|
25
|
+
self
|
26
|
+
rescue => e # rubocop:disable Style/RescueStandardError
|
27
|
+
@error = e
|
28
|
+
ensure
|
29
|
+
self
|
30
|
+
end
|
31
|
+
|
32
|
+
attr_reader :result, :console, :error, :syntax_error
|
33
|
+
|
34
|
+
def execution_successful?
|
35
|
+
error.nil?
|
36
|
+
end
|
37
|
+
|
38
|
+
def result_as_json(inspect_types: false)
|
39
|
+
if error
|
40
|
+
{
|
41
|
+
status: :error,
|
42
|
+
syntax_error: syntax_error,
|
43
|
+
error: error.respond_to?(:message) ? error.message : error.to_s,
|
44
|
+
backtrace: error.respond_to?(:backtrace) ? error.backtrace : nil
|
45
|
+
}
|
46
|
+
else
|
47
|
+
{
|
48
|
+
status: :ok,
|
49
|
+
inspect: result.inspect,
|
50
|
+
raw: result,
|
51
|
+
type: result.class.name,
|
52
|
+
types: result_inspect_types(inspect_types, result),
|
53
|
+
console: console
|
54
|
+
}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def result_inspect_types(inspect_types, result)
|
61
|
+
return {} unless inspect_types
|
62
|
+
|
63
|
+
{
|
64
|
+
root: result.class.name,
|
65
|
+
items: result_types_items(result)
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def result_types_items(result)
|
70
|
+
case result
|
71
|
+
when Array
|
72
|
+
# Create a hash with indices as keys and class names as values
|
73
|
+
result.each_with_index.to_h { |element, index| [index.to_s, element.class.name] }
|
74
|
+
when Hash
|
75
|
+
# Create a hash with string keys and class names as values
|
76
|
+
result.transform_keys(&:to_s).transform_values { |value| value.class.name }
|
77
|
+
else
|
78
|
+
# For non-collection types, there are no items
|
79
|
+
{}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/tasks/init.rake
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'rake'
|
4
4
|
|
5
|
-
namespace :rails_spotlight do
|
5
|
+
namespace :rails_spotlight do # rubocop:disable Metrics/BlockLength
|
6
6
|
desc 'Generate rails_spotlight configuration file'
|
7
7
|
task generate_config: :environment do
|
8
8
|
require 'fileutils'
|
@@ -21,8 +21,10 @@ namespace :rails_spotlight do
|
|
21
21
|
# Rest of the configuration is required for ActionCable. It will be disabled automatically in when ActionCable is not available.
|
22
22
|
LIVE_CONSOLE_ENABLED: true
|
23
23
|
REQUEST_COMPLETED_BROADCAST_ENABLED: false
|
24
|
-
AUTO_MOUNT_ACTION_CABLE:
|
24
|
+
AUTO_MOUNT_ACTION_CABLE: false
|
25
25
|
ACTION_CABLE_MOUNT_PATH: /cable
|
26
|
+
BLOCK_EDITING_FILES: false
|
27
|
+
BLOCK_EDITING_FILES_OUTSIDE_OF_THE_PROJECT: true
|
26
28
|
YAML
|
27
29
|
|
28
30
|
if File.exist?(config_path)
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rails_spotlight
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Pawel Niemczyk
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-02-
|
11
|
+
date: 2024-02-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rack-contrib
|
@@ -248,6 +248,7 @@ files:
|
|
248
248
|
- lib/rails_spotlight/middlewares.rb
|
249
249
|
- lib/rails_spotlight/middlewares/concerns/skip_request_paths.rb
|
250
250
|
- lib/rails_spotlight/middlewares/handlers/base_action_handler.rb
|
251
|
+
- lib/rails_spotlight/middlewares/handlers/console_action_handler.rb
|
251
252
|
- lib/rails_spotlight/middlewares/handlers/file_action_handler.rb
|
252
253
|
- lib/rails_spotlight/middlewares/handlers/meta_action_handler.rb
|
253
254
|
- lib/rails_spotlight/middlewares/handlers/not_found_action_handler.rb
|
@@ -257,6 +258,7 @@ files:
|
|
257
258
|
- lib/rails_spotlight/middlewares/main_request_handler.rb
|
258
259
|
- lib/rails_spotlight/middlewares/request_completed.rb
|
259
260
|
- lib/rails_spotlight/middlewares/request_handler.rb
|
261
|
+
- lib/rails_spotlight/rails_command_executor.rb
|
260
262
|
- lib/rails_spotlight/railtie.rb
|
261
263
|
- lib/rails_spotlight/storage.rb
|
262
264
|
- lib/rails_spotlight/utils.rb
|