rails-mcp-server 1.0.0 → 1.1.0

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.
@@ -1,104 +1,67 @@
1
- #!/usr/bin/env ruby
2
-
3
- require "mcp"
4
- require "yaml"
5
1
  require "logger"
6
- require "json"
7
2
  require "fileutils"
3
+ require "forwardable"
4
+ require "open3"
8
5
  require_relative "rails-mcp-server/version"
6
+ require_relative "rails-mcp-server/config"
7
+ require_relative "rails-mcp-server/utilities/run_process"
8
+ require_relative "rails-mcp-server/tools/base_tool"
9
+ require_relative "rails-mcp-server/tools/project_info"
10
+ require_relative "rails-mcp-server/tools/list_files"
11
+ require_relative "rails-mcp-server/tools/get_file"
12
+ require_relative "rails-mcp-server/tools/get_routes"
13
+ require_relative "rails-mcp-server/tools/analyze_models"
14
+ require_relative "rails-mcp-server/tools/get_schema"
15
+ require_relative "rails-mcp-server/tools/analyze_controller_views"
16
+ require_relative "rails-mcp-server/tools/analyze_environment_config"
17
+ require_relative "rails-mcp-server/tools/switch_project"
9
18
 
10
19
  module RailsMcpServer
11
- class Error < StandardError; end
12
- end
13
-
14
- # rubocop:disable Style/GlobalVars
15
- # Initialize configuration
16
- def get_config_dir
17
- # Use XDG_CONFIG_HOME if set, otherwise use ~/.config
18
- xdg_config_home = ENV["XDG_CONFIG_HOME"]
19
- if xdg_config_home && !xdg_config_home.empty?
20
- File.join(xdg_config_home, "rails-mcp")
21
- else
22
- File.join(Dir.home, ".config", "rails-mcp")
23
- end
24
- end
25
-
26
- # Create config directory if it doesn't exist
27
- config_dir = get_config_dir
28
- FileUtils.mkdir_p(File.join(config_dir, "log"))
29
-
30
- # Default paths
31
- projects_file = File.join(config_dir, "projects.yml")
32
- log_file = File.join(config_dir, "log", "rails_mcp_server.log")
33
- log_level = :info
34
-
35
- # Parse command-line arguments
36
- i = 0
37
- while i < ARGV.length
38
- case ARGV[i]
39
- when "--log-level"
40
- log_level = ARGV[i + 1].to_sym
41
- i += 2
42
- else
43
- i += 1
44
- end
45
- end
20
+ @levels = {debug: Logger::DEBUG, info: Logger::INFO, error: Logger::ERROR}
21
+ @config = Config.setup
46
22
 
47
- # Initialize logger
48
- $logger = Logger.new(log_file)
49
- $logger.level = Logger.const_get(log_level.to_s.upcase)
23
+ class << self
24
+ extend Forwardable
50
25
 
51
- # Set a nicer formatter
52
- $logger.formatter = proc do |severity, datetime, progname, msg|
53
- "[#{datetime.strftime("%Y-%m-%d %H:%M:%S")}] #{severity}: #{msg}\n"
54
- end
55
-
56
- def log(level, message)
57
- levels = {debug: Logger::DEBUG, info: Logger::INFO, warn: Logger::WARN, error: Logger::ERROR, fatal: Logger::FATAL}
58
- log_level = levels[level] || Logger::INFO
59
- $logger.add(log_level, message)
60
- end
26
+ attr_reader :config
61
27
 
62
- log(:info, "Starting Rails MCP Server...")
63
- log(:info, "Using config directory: #{config_dir}")
28
+ def_delegators :@config, :log_level, :log_level=
29
+ def_delegators :@config, :logger, :logger=
30
+ def_delegators :@config, :projects
31
+ def_delegators :@config, :current_project, :current_project=
32
+ def_delegators :@config, :active_project_path, :active_project_path=
64
33
 
65
- # Create empty projects file if it doesn't exist
66
- unless File.exist?(projects_file)
67
- log(:info, "Creating empty projects file: #{projects_file}")
68
- FileUtils.mkdir_p(File.dirname(projects_file))
69
- File.write(projects_file, "# Rails MCP Projects\n# Format: project_name: /path/to/project\n")
70
- end
71
-
72
- # Load projects
73
- projects_file = File.expand_path(projects_file)
74
- projects = {}
34
+ def log(level, message)
35
+ log_level = @levels[level] || Logger::INFO
75
36
 
76
- if File.exist?(projects_file)
77
- log(:info, "Loading projects from: #{projects_file}")
78
- projects = YAML.load_file(projects_file) || {}
79
- log(:info, "Loaded #{projects.size} projects: #{projects.keys.join(", ")}")
80
- else
81
- log(:warn, "Projects file not found: #{projects_file}")
37
+ @config.logger.add(log_level, message)
38
+ end
39
+ end
40
+ class Error < StandardError; end
82
41
  end
83
42
 
84
- # Initialize state
85
- $active_project = nil
86
- $active_project_path = nil
87
-
88
- # Define MCP server using the mcp-rb DSL
89
- name "rails-mcp-server"
90
- version RailsMcpServer::VERSION
43
+ # rubocop:disable Style/GlobalVars
91
44
 
92
45
  # Utility functions for Rails operations
93
46
  def get_directory_structure(path, max_depth: 3, current_depth: 0, prefix: "")
94
47
  return "" if current_depth > max_depth || !File.directory?(path)
95
48
 
49
+ # Define ignored directories
50
+ ignored_dirs = [
51
+ ".git", "node_modules", "tmp", "log",
52
+ "storage", "coverage", "public/assets",
53
+ "public/packs", ".bundle", "vendor/bundle",
54
+ "vendor/cache"
55
+ ]
56
+
96
57
  output = ""
97
58
  directories = []
98
59
  files = []
99
60
 
100
61
  Dir.foreach(path) do |entry|
101
62
  next if entry == "." || entry == ".."
63
+ next if ignored_dirs.include?(entry) # Skip ignored directories
64
+
102
65
  full_path = File.join(path, entry)
103
66
 
104
67
  if File.directory?(full_path)
@@ -155,355 +118,218 @@ def underscore(string)
155
118
  .downcase
156
119
  end
157
120
 
158
- # Define tools using the mcp-rb DSL
159
- tool "switch_project" do
160
- description "Change the active Rails project to interact with a different codebase. Must be called before using other tools. Available projects are defined in the projects.yml configuration file."
121
+ # Helper method to extract settings from environment files
122
+ def extract_env_settings(content)
123
+ settings = {}
161
124
 
162
- argument :project_name, String, required: true,
163
- description: "Name of the project as defined in the projects.yml file (case-sensitive)"
125
+ # Match configuration settings
126
+ content.scan(/config\.([a-zA-Z0-9_.]+)\s*=\s*([^#\n]+)/) do |match|
127
+ key = match[0].strip
128
+ value = match[1].strip
164
129
 
165
- call do |args|
166
- project_name = args[:project_name]
130
+ # Clean up the value
131
+ value = value.chomp(";").strip
167
132
 
168
- if projects.key?(project_name)
169
- $active_project = project_name
170
- $active_project_path = File.expand_path(projects[project_name])
171
- log(:info, "Switched to project: #{project_name} at path: #{$active_project_path}")
172
- "Switched to project: #{project_name} at path: #{$active_project_path}"
173
- else
174
- log(:warn, "Project not found: #{project_name}")
175
- raise "Project '#{project_name}' not found. Available projects: #{projects.keys.join(", ")}"
176
- end
133
+ settings[key] = value
177
134
  end
178
- end
179
-
180
- tool "get_project_info" do
181
- description "Retrieve comprehensive information about the current Rails project, including Rails version, directory structure, API-only status, and overall project organization. Useful for initial project exploration and understanding the codebase structure."
182
-
183
- call do |args|
184
- unless $active_project
185
- raise "No active project. Please switch to a project first."
186
- end
187
135
 
188
- # Get additional project information
189
- gemfile_path = File.join($active_project_path, "Gemfile")
190
- gemfile_content = File.exist?(gemfile_path) ? File.read(gemfile_path) : "Gemfile not found"
191
-
192
- # Get Rails version
193
- rails_version = gemfile_content.match(/gem ['"]rails['"],\s*['"](.+?)['"]/)&.captures&.first || "Unknown"
194
-
195
- # Check if it's an API-only app
196
- config_application_path = File.join($active_project_path, "config", "application.rb")
197
- is_api_only = File.exist?(config_application_path) &&
198
- File.read(config_application_path).include?("config.api_only = true")
199
-
200
- log(:info, "Project info: Rails v#{rails_version}, API-only: #{is_api_only}")
201
-
202
- <<~INFO
203
- Current project: #{$active_project}
204
- Path: #{$active_project_path}
205
- Rails version: #{rails_version}
206
- API only: #{is_api_only ? "Yes" : "No"}
207
-
208
- Project structure:
209
- #{get_directory_structure($active_project_path, max_depth: 2)}
210
- INFO
211
- end
136
+ settings
212
137
  end
213
138
 
214
- tool "list_files" do
215
- description "List files in the Rails project matching specific criteria. Use this to explore project directories or locate specific file types. If no parameters are provided, lists files in the project root."
216
-
217
- argument :directory, String, required: false,
218
- description: "Directory path relative to the project root (e.g., 'app/models', 'config'). Leave empty to list files at the root."
139
+ # Helper method to find ENV variable usage in the codebase
140
+ def find_env_vars_in_codebase(project_path)
141
+ env_vars = {}
142
+
143
+ # Define directories to search
144
+ search_dirs = [
145
+ File.join(project_path, "app"),
146
+ File.join(project_path, "config"),
147
+ File.join(project_path, "lib")
148
+ ]
149
+
150
+ # Define file patterns to search
151
+ file_patterns = ["*.rb", "*.yml", "*.erb", "*.js"]
152
+
153
+ search_dirs.each do |dir|
154
+ if File.directory?(dir)
155
+ file_patterns.each do |pattern|
156
+ Dir.glob(File.join(dir, "**", pattern)).each do |file|
157
+ content = File.read(file)
158
+
159
+ # Extract ENV variables
160
+ content.scan(/ENV\s*\[\s*['"]([^'"]+)['"]\s*\]/).each do |match|
161
+ env_var = match[0]
162
+ env_vars[env_var] ||= []
163
+ env_vars[env_var] << file.sub("#{project_path}/", "")
164
+ end
219
165
 
220
- argument :pattern, String, required: false,
221
- description: "File pattern using glob syntax (e.g., '*.rb' for Ruby files, '*.erb' for ERB templates, '*_controller.rb' for controllers)"
166
+ # Also match ENV['VAR'] pattern
167
+ content.scan(/ENV\s*\.\s*\[\s*['"]([^'"]+)['"]\s*\]/).each do |match|
168
+ env_var = match[0]
169
+ env_vars[env_var] ||= []
170
+ env_vars[env_var] << file.sub("#{project_path}/", "")
171
+ end
222
172
 
223
- call do |args|
224
- unless $active_project
225
- raise "No active project. Please switch to a project first."
173
+ # Also match ENV.fetch('VAR') pattern
174
+ content.scan(/ENV\s*\.\s*fetch\s*\(\s*['"]([^'"]+)['"]\s*/).each do |match|
175
+ env_var = match[0]
176
+ env_vars[env_var] ||= []
177
+ env_vars[env_var] << file.sub("#{project_path}/", "")
178
+ end
179
+ rescue => e
180
+ log(:error, "Error reading file #{file}: #{e.message}")
181
+ end
182
+ end
226
183
  end
184
+ end
227
185
 
228
- directory = args[:directory] || ""
229
- pattern = args[:pattern] || "*"
230
-
231
- full_path = File.join($active_project_path, directory)
232
-
233
- unless File.directory?(full_path)
234
- raise "Directory '#{directory}' not found in the project."
235
- end
186
+ env_vars
187
+ end
236
188
 
237
- # Use Dir.glob to get matching files
238
- files = Dir.glob(File.join(full_path, pattern))
239
- .map { |f| f.sub("#{$active_project_path}/", "") }
240
- .sort # rubocop:disable Performance/ChainArrayAllocation
189
+ # Helper method to parse .env files
190
+ def parse_dotenv_file(file_path)
191
+ vars = {}
241
192
 
242
- log(:debug, "Found #{files.size} files matching pattern")
193
+ begin
194
+ File.readlines(file_path).each do |line| # rubocop:disable Performance/IoReadlines
195
+ # Skip comments and empty lines
196
+ next if line.strip.empty? || line.strip.start_with?("#")
243
197
 
244
- "Files in #{directory.empty? ? "project root" : directory} matching '#{pattern}':\n\n#{files.join("\n")}"
198
+ # Parse KEY=value pattern
199
+ if line =~ /\A([A-Za-z0-9_]+)=(.*)\z/
200
+ key = $1
201
+ # Store just the existence of the variable, not its value
202
+ vars[key] = true
203
+ end
204
+ end
205
+ rescue => e
206
+ log(:error, "Error parsing .env file #{file_path}: #{e.message}")
245
207
  end
208
+
209
+ vars
246
210
  end
247
211
 
248
- tool "get_file" do
249
- description "Retrieve the complete content of a specific file with syntax highlighting. Use this to examine implementation details, configurations, or any text file in the project."
212
+ # Helper method to parse database.yml
213
+ def parse_database_config(file_path)
214
+ config = {}
250
215
 
251
- argument :path, String, required: true,
252
- description: "File path relative to the project root (e.g., 'app/models/user.rb', 'config/routes.rb'). Use list_files first if you're not sure about the exact path."
216
+ begin
217
+ # Simple YAML parsing - not handling ERB
218
+ yaml_content = File.read(file_path)
219
+ yaml_data = YAML.safe_load(yaml_content) || {}
253
220
 
254
- call do |args|
255
- unless $active_project
256
- raise "No active project. Please switch to a project first."
221
+ # Extract environment configurations
222
+ %w[development test production staging].each do |env|
223
+ config[env] = yaml_data[env] if yaml_data[env]
257
224
  end
225
+ rescue => e
226
+ log(:error, "Error parsing database.yml: #{e.message}")
227
+ end
258
228
 
259
- path = args[:path]
260
- full_path = File.join($active_project_path, path)
229
+ config
230
+ end
261
231
 
262
- unless File.exist?(full_path)
263
- raise "File '#{path}' not found in the project."
264
- end
232
+ # Helper method to compare environment settings
233
+ def compare_environment_settings(env_settings)
234
+ result = {
235
+ unique_settings: {},
236
+ different_values: {}
237
+ }
265
238
 
266
- content = File.read(full_path)
267
- log(:debug, "Read file: #{path} (#{content.size} bytes)")
239
+ # Get all settings across all environments
240
+ all_settings = env_settings.values.map(&:keys).flatten.uniq # rubocop:disable Performance/ChainArrayAllocation
268
241
 
269
- "File: #{path}\n\n```#{get_file_extension(path)}\n#{content}\n```"
242
+ # Find settings unique to certain environments
243
+ env_settings.each do |env, settings|
244
+ unique = settings.keys - (all_settings - settings.keys)
245
+ result[:unique_settings][env] = unique if unique.any?
270
246
  end
271
- end
272
247
 
273
- tool "get_routes" do
274
- description "Retrieve all HTTP routes defined in the Rails application with their associated controllers and actions. Equivalent to running 'rails routes' command. This helps understand the API endpoints or page URLs available in the application."
248
+ # Find settings with different values across environments
249
+ all_settings.each do |setting|
250
+ values = {}
275
251
 
276
- call do |args|
277
- unless $active_project
278
- raise "No active project. Please switch to a project first."
252
+ env_settings.each do |env, settings|
253
+ values[env] = settings[setting] if settings[setting]
279
254
  end
280
255
 
281
- # Execute the Rails routes command
282
- routes_output = execute_rails_command($active_project_path, "routes")
283
- log(:debug, "Routes command completed, output size: #{routes_output.size} bytes")
284
-
285
- "Rails Routes:\n\n```\n#{routes_output}\n```"
256
+ # Only include if there are different values
257
+ if values.values.uniq.size > 1
258
+ result[:different_values][setting] = values
259
+ end
286
260
  end
287
- end
288
-
289
- tool "get_models" do
290
- description "Retrieve detailed information about Active Record models in the project. When called without parameters, lists all model files. When a specific model is specified, returns its schema, associations (has_many, belongs_to, has_one), and complete source code."
291
261
 
292
- argument :model_name, String, required: false,
293
- description: "Class name of a specific model to get detailed information for (e.g., 'User', 'Product'). Use CamelCase, not snake_case. If omitted, returns a list of all models."
262
+ result
263
+ end
294
264
 
295
- call do |args|
296
- unless $active_project
297
- raise "No active project. Please switch to a project first."
298
- end
265
+ # Helper method to find missing ENV variables
266
+ def find_missing_env_vars(env_vars_in_code, dotenv_vars)
267
+ missing_vars = {}
299
268
 
300
- model_name = args[:model_name]
269
+ # Check each ENV variable used in code
270
+ env_vars_in_code.each do |var, files|
271
+ # Environments where the variable is missing
272
+ missing_in = []
301
273
 
302
- if model_name
303
- log(:info, "Getting info for specific model: #{model_name}")
274
+ # Check in each .env file
275
+ if dotenv_vars.empty?
276
+ missing_in << "all environments (no .env files found)"
277
+ else
278
+ dotenv_vars.each do |env_file, vars|
279
+ env_name = env_file.gsub(/^\.env\.?|\.local$/, "")
280
+ env_name = "development" if env_name.empty?
304
281
 
305
- # Check if the model file exists
306
- model_file = File.join($active_project_path, "app", "models", "#{underscore(model_name)}.rb")
307
- unless File.exist?(model_file)
308
- log(:warn, "Model file not found: #{model_name}")
309
- raise "Model '#{model_name}' not found."
282
+ if !vars.key?(var)
283
+ missing_in << env_name
284
+ end
310
285
  end
286
+ end
311
287
 
312
- log(:debug, "Reading model file: #{model_file}")
313
-
314
- # Get the model file content
315
- model_content = File.read(model_file)
288
+ missing_vars[var] = missing_in if missing_in.any?
289
+ end
316
290
 
317
- # Try to get schema information
318
- log(:debug, "Executing Rails runner to get schema information")
319
- schema_info = execute_rails_command(
320
- $active_project_path,
321
- "runner \"puts #{model_name}.column_names\""
322
- )
291
+ missing_vars
292
+ end
323
293
 
324
- # Try to get associations
325
- associations = []
326
- if model_content.include?("has_many")
327
- has_many = model_content.scan(/has_many\s+:(\w+)/).flatten
328
- associations << "Has many: #{has_many.join(", ")}" unless has_many.empty?
329
- end
294
+ # Helper method to check for security issues
295
+ def check_security_configuration(env_settings, database_config)
296
+ findings = []
330
297
 
331
- if model_content.include?("belongs_to")
332
- belongs_to = model_content.scan(/belongs_to\s+:(\w+)/).flatten
333
- associations << "Belongs to: #{belongs_to.join(", ")}" unless belongs_to.empty?
298
+ # Check for common security settings
299
+ env_settings.each do |env, settings|
300
+ # Check for secure cookies in production
301
+ if env == "production"
302
+ if settings["cookies.secure"] == "false"
303
+ findings << "Production has cookies.secure = false"
334
304
  end
335
305
 
336
- if model_content.include?("has_one")
337
- has_one = model_content.scan(/has_one\s+:(\w+)/).flatten
338
- associations << "Has one: #{has_one.join(", ")}" unless has_one.empty?
306
+ if settings["session_store.secure"] == "false"
307
+ findings << "Production has session_store.secure = false"
339
308
  end
340
309
 
341
- log(:debug, "Found #{associations.size} associations for model: #{model_name}")
342
-
343
- # Format the output
344
- <<~INFO
345
- Model: #{model_name}
346
-
347
- Schema:
348
- #{schema_info}
349
-
350
- Associations:
351
- #{associations.empty? ? "None found" : associations.join("\n")}
352
-
353
- Model Definition:
354
- ```ruby
355
- #{model_content}
356
- ```
357
- INFO
358
- else
359
- log(:info, "Listing all models")
360
-
361
- # List all models
362
- models_dir = File.join($active_project_path, "app", "models")
363
- unless File.directory?(models_dir)
364
- raise "Models directory not found."
310
+ # Force SSL
311
+ if settings["force_ssl"] == "false"
312
+ findings << "Production has force_ssl = false"
365
313
  end
314
+ end
366
315
 
367
- # Get all .rb files in the models directory and its subdirectories
368
- model_files = Dir.glob(File.join(models_dir, "**", "*.rb"))
369
- .map { |f| f.sub("#{models_dir}/", "").sub(/\.rb$/, "") }
370
- .sort # rubocop:disable Performance/ChainArrayAllocation
371
-
372
- log(:debug, "Found #{model_files.size} model files")
373
-
374
- "Models in the project:\n\n#{model_files.join("\n")}"
316
+ # Check for CSRF protection
317
+ if settings["action_controller.default_protect_from_forgery"] == "false"
318
+ findings << "#{env} has CSRF protection disabled"
375
319
  end
376
320
  end
377
- end
378
321
 
379
- tool "get_schema" do
380
- description "Retrieve database schema information for the Rails application. Without parameters, returns all tables and the complete schema.rb. With a table name, returns detailed column information including data types, constraints, and foreign keys for that specific table."
381
-
382
- argument :table_name, String, required: false,
383
- description: "Database table name to get detailed schema information for (e.g., 'users', 'products'). Use snake_case, plural form. If omitted, returns complete database schema."
384
-
385
- call do |args|
386
- unless $active_project
387
- raise "No active project. Please switch to a project first."
322
+ # Check for hardcoded credentials in database.yml
323
+ database_config.each do |env, config|
324
+ if config["username"] && !config["username"].include?("ENV")
325
+ findings << "Database username hardcoded in database.yml for #{env}"
388
326
  end
389
327
 
390
- table_name = args[:table_name]
391
-
392
- if table_name
393
- log(:info, "Getting schema for table: #{table_name}")
394
-
395
- # Execute the Rails schema command for a specific table
396
- schema_output = execute_rails_command(
397
- $active_project_path,
398
- "runner \"require 'active_record'; puts ActiveRecord::Base.connection.columns('#{table_name}').map{|c| [c.name, c.type, c.null, c.default].inspect}.join('\n')\""
399
- )
400
-
401
- if schema_output.strip.empty?
402
- raise "Table '#{table_name}' not found or has no columns."
403
- end
404
-
405
- # Parse the column information
406
- columns = schema_output.strip.split("\n").map do |column_info|
407
- eval(column_info) # This is safe because we're generating the string ourselves # rubocop:disable Security/Eval
408
- end
409
-
410
- # Format the output
411
- formatted_columns = columns.map do |name, type, nullable, default|
412
- "#{name} (#{type})#{nullable ? ", nullable" : ""}#{default ? ", default: #{default}" : ""}"
413
- end
414
-
415
- output = <<~SCHEMA
416
- Table: #{table_name}
417
-
418
- Columns:
419
- #{formatted_columns.join("\n")}
420
- SCHEMA
421
-
422
- # Try to get foreign keys
423
- begin
424
- fk_output = execute_rails_command(
425
- $active_project_path,
426
- "runner \"require 'active_record'; puts ActiveRecord::Base.connection.foreign_keys('#{table_name}').map{|fk| [fk.from_table, fk.to_table, fk.column, fk.primary_key].inspect}.join('\n')\""
427
- )
428
-
429
- unless fk_output.strip.empty?
430
- foreign_keys = fk_output.strip.split("\n").map do |fk_info|
431
- eval(fk_info) # This is safe because we're generating the string ourselves # rubocop:disable Security/Eval
432
- end
433
-
434
- formatted_fks = foreign_keys.map do |from_table, to_table, column, primary_key|
435
- "#{column} -> #{to_table}.#{primary_key}"
436
- end
437
-
438
- output += <<~FK
439
-
440
- Foreign Keys:
441
- #{formatted_fks.join("\n")}
442
- FK
443
- end
444
- rescue => e
445
- log(:warn, "Error fetching foreign keys: #{e.message}")
446
- end
447
-
448
- output
449
- else
450
- log(:info, "Getting full schema")
451
-
452
- # Execute the Rails schema:dump command
453
- # First, check if we need to create the schema file
454
- schema_file = File.join($active_project_path, "db", "schema.rb")
455
- unless File.exist?(schema_file)
456
- log(:info, "Schema file not found, attempting to generate it")
457
- execute_rails_command($active_project_path, "db:schema:dump")
458
- end
459
-
460
- if File.exist?(schema_file)
461
- # Read the schema file
462
- schema_content = File.read(schema_file)
463
-
464
- # Try to get table list
465
- tables_output = execute_rails_command(
466
- $active_project_path,
467
- "runner \"require 'active_record'; puts ActiveRecord::Base.connection.tables.sort.join('\n')\""
468
- )
469
-
470
- tables = tables_output.strip.split("\n")
471
-
472
- <<~SCHEMA
473
- Database Schema
474
-
475
- Tables:
476
- #{tables.join("\n")}
477
-
478
- Schema Definition:
479
- ```ruby
480
- #{schema_content}
481
- ```
482
- SCHEMA
483
-
484
- else
485
- # If we can't get the schema file, try to get the table list
486
- tables_output = execute_rails_command(
487
- $active_project_path,
488
- "runner \"require 'active_record'; puts ActiveRecord::Base.connection.tables.sort.join('\n')\""
489
- )
490
-
491
- if tables_output.strip.empty?
492
- raise "Could not retrieve schema information. Try running 'rails db:schema:dump' in your project first."
493
- end
494
-
495
- tables = tables_output.strip.split("\n")
496
-
497
- <<~SCHEMA
498
- Database Schema
499
-
500
- Tables:
501
- #{tables.join("\n")}
502
-
503
- Note: Full schema definition is not available. Run 'rails db:schema:dump' to generate the schema.rb file.
504
- SCHEMA
505
- end
328
+ if config["password"] && !config["password"].include?("ENV")
329
+ findings << "Database password hardcoded in database.yml for #{env}"
506
330
  end
507
331
  end
332
+
333
+ findings
508
334
  end
509
335
  # rubocop:enable Style/GlobalVars