Class: Securial::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/securial/cli.rb

Overview

Command-line interface for the Securial gem.

This class provides the command-line functionality for Securial, enabling users to create new Rails applications with Securial pre-installed and configured. It handles command parsing, flag processing, and orchestrates the setup of new applications.

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.start(argv) ⇒ Integer

Entry point for the CLI application.

Creates a new CLI instance and delegates to its start method.

Parameters:

  • argv (Array<String>)

    command line arguments

Returns:

  • (Integer)

    exit status code (0 for success, non-zero for errors)



38
39
40
# File 'lib/securial/cli.rb', line 38

def self.start(argv)
  new.start(argv)
end

Instance Method Details

#add_securial_gem(app_name) ⇒ void (private)

This method returns an undefined value.

Adds the Securial gem to the application’s Gemfile.

Parameters:

  • app_name (String)

    name of the Rails application



246
247
248
249
250
# File 'lib/securial/cli.rb', line 246

def add_securial_gem(app_name)
  puts "📦  Adding Securial gem to Gemfile"
  gemfile_path = File.join(app_name, "Gemfile")
  File.open(gemfile_path, "a") { |f| f.puts "\ngem 'securial'" }
end

#create_option_parserOptionParser (private)

Creates and configures the option parser.

Sets up the command-line options and their handlers.

Returns:

  • (OptionParser)

    configured option parser instance



130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# File 'lib/securial/cli.rb', line 130

def create_option_parser
  OptionParser.new do |opts|
    opts.banner = "Usage: securial [options] <command> [command options]\n\n"

    opts.separator ""
    opts.separator "Commands:"
    opts.separator "    new APP_NAME [rails_options...]    # Create a new Rails app with Securial pre-installed"
    opts.separator ""
    opts.separator "Options:"

    opts.on("-v", "--version", "Show Securial version") do
      show_version
      exit(0)
    end

    opts.on("-h", "--help", "Show this help message") do
      puts opts
      exit(0)
    end
  end
end

#create_rails_app(app_name, rails_options) ⇒ Integer (private)

Creates a new Rails application.

Parameters:

  • app_name (String)

    name of the Rails application to create

  • rails_options (Array<String>)

    options to pass to ‘rails new’

Returns:

  • (Integer)

    command exit status



190
191
192
193
# File 'lib/securial/cli.rb', line 190

def create_rails_app(app_name, rails_options)
  rails_command = ["rails", "new", app_name, *rails_options]
  run(rails_command)
end

#handle_commands(argv) ⇒ Integer (private)

Processes commands from the command line arguments.

Identifies the command (e.g., ‘new’) and delegates to the appropriate handler method for that command.

Parameters:

  • argv (Array<String>)

    command line arguments

Returns:

  • (Integer)

    exit status code (0 for success, non-zero for errors)



91
92
93
94
95
96
97
98
99
100
101
# File 'lib/securial/cli.rb', line 91

def handle_commands(argv)
  cmd = argv.shift

  case cmd
  when "new"
    handle_new_command(argv)
  else
    puts create_option_parser
    1
  end
end

#handle_flags(argv) ⇒ nil, Integer (private)

Processes option flags from the command line arguments.

Parses options like –version and –help, executing their actions if present and removing them from the argument list.

Parameters:

  • argv (Array<String>)

    command line arguments

Returns:

  • (nil, Integer)

    nil to continue processing, or exit code



70
71
72
73
74
75
76
77
78
79
80
81
# File 'lib/securial/cli.rb', line 70

def handle_flags(argv)
  parser = create_option_parser

  begin
    parser.order!(argv)
    nil # Continue to command handling
  rescue OptionParser::InvalidOption => e
    warn "ERROR: Illegal option(s): #{e.args.join(' ')}"
    puts parser
    1
  end
end

#handle_new_command(argv) ⇒ Integer (private)

Handles the ‘new’ command for creating Rails applications.

Validates that an application name is provided, then delegates to securial_new to create the application with Securial.

Parameters:

  • argv (Array<String>)

    remaining command line arguments

Returns:

  • (Integer)

    exit status code (0 for success, non-zero for errors)



111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/securial/cli.rb', line 111

def handle_new_command(argv)
  app_name = argv.shift

  if app_name.nil?
    puts "ERROR: Please provide an app name."
    puts create_option_parser
    return 1
  end

  securial_new(app_name, argv)
  0
end

#install_gems(app_name) ⇒ Integer (private)

Installs gems for the application using Bundler.

Parameters:

  • app_name (String)

    name of the Rails application

Returns:

  • (Integer)

    command exit status



257
258
259
# File 'lib/securial/cli.rb', line 257

def install_gems(app_name)
  run("bundle install", chdir: app_name)
end

#install_securial(app_name) ⇒ Integer (private)

Installs and configures Securial in the application.

Parameters:

  • app_name (String)

    name of the Rails application

Returns:

  • (Integer)

    command exit status



266
267
268
269
270
# File 'lib/securial/cli.rb', line 266

def install_securial(app_name)
  puts "🔧  Installing Securial"
  run("bin/rails generate securial:install", chdir: app_name)
  run("bin/rails db:migrate", chdir: app_name)
end

#mount_securial_engine(app_name) ⇒ void (private)

This method returns an undefined value.

Mounts the Securial engine in the application’s routes.

Parameters:

  • app_name (String)

    name of the Rails application



277
278
279
280
281
282
283
284
285
# File 'lib/securial/cli.rb', line 277

def mount_securial_engine(app_name)
  puts "🔗  Mounting Securial engine in routes"
  routes_path = File.join(app_name, "config/routes.rb")
  routes = File.read(routes_path)
  updated = routes.sub("Rails.application.routes.draw do") do |match|
    "#{match}\n  mount Securial::Engine => '/securial'"
  end
  File.write(routes_path, updated)
end

This method returns an undefined value.

Prints final setup instructions after successful installation.

Parameters:

  • app_name (String)

    name of the Rails application



292
293
294
295
296
297
298
299
300
301
302
# File 'lib/securial/cli.rb', line 292

def print_final_instructions(app_name)
  puts <<~INSTRUCTIONS
    🎉  Securial has been successfully installed in your Rails app!
    ✅  Your app is ready at: ./#{app_name}

    ➡️  Next steps:
        cd #{app_name}
    ⚙️  Optional: Configure Securial in config/initializers/securial.rb
        rails server
  INSTRUCTIONS
end

#run(command, chdir: nil) ⇒ Integer (private)

Runs a system command with optional directory change.

Executes the provided command, optionally in a different directory, and handles success/failure conditions.

Parameters:

  • command (String, Array<String>)

    command to run

  • chdir (String, nil) (defaults to: nil)

    directory to change to before running command

Returns:

  • (Integer)

    command exit status code (0 for success)

Raises:

  • (SystemExit)

    if the command fails



314
315
316
317
318
319
320
321
322
323
324
325
326
327
# File 'lib/securial/cli.rb', line 314

def run(command, chdir: nil)
  puts "#{command.inspect}"
  result =
    if chdir
      Dir.chdir(chdir) { system(*command) }
    else
      system(*command)
    end

  unless result
    abort("❌ Command failed: #{command}")
  end
  0
end

#securial_new(app_name, rails_options) ⇒ void (private)

This method returns an undefined value.

Creates a new Rails application with Securial pre-installed.

Orchestrates the process of creating a Rails application, adding the Securial gem, installing dependencies, and configuring the application.

Parameters:

  • app_name (String)

    name of the Rails application to create

  • rails_options (Array<String>)

    options to pass to ‘rails new’



172
173
174
175
176
177
178
179
180
181
182
# File 'lib/securial/cli.rb', line 172

def securial_new(app_name, rails_options)
  puts "🏗️  Creating new Rails app: #{app_name}"

  create_rails_app(app_name, rails_options)
  update_database_yml_host(app_name)
  add_securial_gem(app_name)
  install_gems(app_name)
  install_securial(app_name)
  mount_securial_engine(app_name)
  print_final_instructions(app_name)
end

#show_versionvoid (private)

This method returns an undefined value.

Displays the current Securial version.



156
157
158
159
160
161
# File 'lib/securial/cli.rb', line 156

def show_version
  require "securial/version"
  puts "Securial v#{Securial::VERSION}"
rescue LoadError
  puts "Securial version information not available."
end

#start(argv) ⇒ Integer

Processes command line arguments and executes the appropriate action.

This method handles both option flags (like –version) and commands (like ‘new’), delegating to specialized handlers and returning the appropriate exit code.

Parameters:

  • argv (Array<String>)

    command line arguments

Returns:

  • (Integer)

    exit status code (0 for success, non-zero for errors)



51
52
53
54
55
56
57
58
# File 'lib/securial/cli.rb', line 51

def start(argv)
  # Process options and exit if a flag was handled
  result = handle_flags(argv)
  exit(result) if result

  # Otherwise handle commands
  exit(handle_commands(argv))
end

#update_database_yml_host(app_name) ⇒ void (private)

This method returns an undefined value.

Adds DB_HOST env. variable to as default host in ‘database.yml`.

Parameters:

  • app_name (String)

    name of the Rails application



200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/securial/cli.rb', line 200

def update_database_yml_host(app_name) # rubocop:disable Metrics/MethodLength
  db_config_path = File.join(app_name, "config", "database.yml")

  # Step 1: Parse to check adapter
  raw = File.read(db_config_path)
  rendered = ERB.new(raw).result
  config = YAML.safe_load(rendered, aliases: true) || {}

  adapter = config.dig("default", "adapter")
  return unless adapter.is_a?(String) && %w[postgresql mysql2].include?(adapter)

  # Step 2: Modify the raw YAML file line-by-line
  lines = File.readlines(db_config_path)
  updated_lines = []
  inside_default = false
  inserted = false

  lines.each do |line|
    updated_lines << line

    if line =~ /^default:/
      inside_default = true
      next
    end

    if inside_default && line.strip.start_with?("adapter:")
      # Insert immediately after the adapter line
      updated_lines += [
        "  host: <%= ENV.fetch(\"DB_HOST\", \"localhost\") %>\n",
        "  username: <%= ENV.fetch(\"DB_USERNAME\") { \"postgres\" } %>\n",
        "  password: <%= ENV.fetch(\"DB_PASSWORD\") { \"postgres\" } %>\n",
      ]
      inserted = true
    end

    # Exit `default:` block when another top-level key appears
    inside_default = false if inside_default && line =~ /^\S/ && line !~ /^\s/
  end
  File.write(db_config_path, updated_lines.join)
end