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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 41eb3756c5f0405d7811d039c0953ac3a62b993486a4fc2e2085686c8c9cd54b
4
- data.tar.gz: fd0d5d64ff53a660ade6867f05c152e36cd228ccb02b1d9006c3e4be8ed8e6cd
3
+ metadata.gz: 811024ca98d1eff1d90ec498134b41d547c19f0a731074f0f76b85f42d34ae62
4
+ data.tar.gz: fde226acfcae18f43435d64cccd3fae6b0651b0a606d3d196cd08bdaffa60403
5
5
  SHA512:
6
- metadata.gz: d3159584ad8395c2ea3a17fb969cd9a50c94a120c0db950ff11db8d52ce3357f443c7944a23dc8dc3521844f98e6cdc30da288f53a08e6308f8722c53a85907b
7
- data.tar.gz: 2057d0386f21959a19b229ca2f3b4e46db2a0270d1c8f1262f551363eeda54c7127bc3b78c1c209b6e949a62dc2ce1c82989b17650eaa7c6dc02bb99874f3389
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: true
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, { inspect_types: inspect_types })
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 execute_command(command, opts = {})
41
- output_stream = StringIO.new # Create a new StringIO object to capture output
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 result_inspect_types(inspect_types, result)
68
- return {} unless inspect_types
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
- root: result.class.name,
72
- items: result_types_items(result)
73
- }
74
- end
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
- # For non-collection types, there are no items
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 : is_true?(opts[:live_console_enabled])
23
- @request_completed_broadcast_enabled = is_true?(opts[: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 : is_true?(opts[:auto_mount_action_cable])
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 is_true?(value)
79
- value == true || value == 'true' || value == 1 || value == '1'
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
- File.write(file_path, new_content) if write_mode?
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 json_request_body.fetch('file').start_with?(::RailsSpotlight.config.rails_root)
47
- json_request_body.fetch('file')
48
- else
49
- File.join(::RailsSpotlight.config.rails_root, json_request_body.fetch('file'))
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
@@ -11,7 +11,8 @@ module RailsSpotlight
11
11
  def json_response_body
12
12
  {
13
13
  events: events,
14
- project: ::RailsSpotlight.config.project_name
14
+ project: ::RailsSpotlight.config.project_name,
15
+ root_path: ::RailsSpotlight.config.rails_root
15
16
  }
16
17
  end
17
18
 
@@ -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 connetction settings the current query is not allowed to be executed on the #{::RailsSpotlight.config.project_name} project"
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RailsSpotlight
4
- VERSION = '0.2.1'
4
+ VERSION = '0.2.2'
5
5
  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: true
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.1
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 00:00:00.000000000 Z
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