hanami-cli 2.0.0.rc1 → 2.0.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.
Files changed (70) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +2 -0
  3. data/.yardopts +3 -0
  4. data/CHANGELOG.md +12 -0
  5. data/Gemfile +3 -4
  6. data/exe/hanami +6 -1
  7. data/hanami-cli.gemspec +3 -2
  8. data/lib/hanami/cli/bundler.rb +95 -5
  9. data/lib/hanami/cli/command.rb +41 -0
  10. data/lib/hanami/cli/commands/app/command.rb +63 -2
  11. data/lib/hanami/cli/commands/app/console.rb +7 -3
  12. data/lib/hanami/cli/commands/app/db/create.rb +2 -0
  13. data/lib/hanami/cli/commands/app/db/create_migration.rb +3 -0
  14. data/lib/hanami/cli/commands/app/db/drop.rb +2 -0
  15. data/lib/hanami/cli/commands/app/db/migrate.rb +2 -0
  16. data/lib/hanami/cli/commands/app/db/reset.rb +2 -0
  17. data/lib/hanami/cli/commands/app/db/rollback.rb +2 -0
  18. data/lib/hanami/cli/commands/app/db/sample_data.rb +2 -0
  19. data/lib/hanami/cli/commands/app/db/seed.rb +2 -0
  20. data/lib/hanami/cli/commands/app/db/setup.rb +2 -0
  21. data/lib/hanami/cli/commands/app/db/structure/dump.rb +3 -0
  22. data/lib/hanami/cli/commands/app/db/utils/database.rb +155 -0
  23. data/lib/hanami/cli/commands/app/db/utils/database_config.rb +60 -0
  24. data/lib/hanami/cli/commands/app/db/utils/mysql.rb +33 -0
  25. data/lib/hanami/cli/commands/app/db/utils/postgres.rb +64 -0
  26. data/lib/hanami/cli/commands/app/db/utils/sqlite.rb +45 -0
  27. data/lib/hanami/cli/commands/app/db/version.rb +2 -0
  28. data/lib/hanami/cli/commands/app/generate/action.rb +10 -3
  29. data/lib/hanami/cli/commands/app/generate/slice.rb +9 -6
  30. data/lib/hanami/cli/commands/app/generate.rb +2 -0
  31. data/lib/hanami/cli/commands/app/install.rb +13 -2
  32. data/lib/hanami/cli/commands/app/middleware.rb +7 -1
  33. data/lib/hanami/cli/commands/app/routes.rb +8 -0
  34. data/lib/hanami/cli/commands/app/server.rb +10 -0
  35. data/lib/hanami/cli/commands/app/version.rb +4 -2
  36. data/lib/hanami/cli/commands/app.rb +6 -19
  37. data/lib/hanami/cli/commands/gem/new.rb +12 -13
  38. data/lib/hanami/cli/commands/gem/version.rb +4 -2
  39. data/lib/hanami/cli/commands/gem.rb +8 -5
  40. data/lib/hanami/cli/commands.rb +19 -3
  41. data/lib/hanami/cli/errors.rb +65 -0
  42. data/lib/hanami/cli/files.rb +10 -0
  43. data/lib/hanami/cli/generators/app/action.rb +11 -5
  44. data/lib/hanami/cli/generators/app/action_context.rb +22 -0
  45. data/lib/hanami/cli/generators/app/slice.rb +6 -1
  46. data/lib/hanami/cli/generators/app/slice_context.rb +8 -0
  47. data/lib/hanami/cli/generators/context.rb +14 -0
  48. data/lib/hanami/cli/generators/gem/app.rb +9 -5
  49. data/lib/hanami/cli/generators/version.rb +8 -6
  50. data/lib/hanami/cli/middleware_stack_inspector.rb +5 -0
  51. data/lib/hanami/cli/rake_tasks.rb +0 -1
  52. data/lib/hanami/cli/repl/core.rb +12 -4
  53. data/lib/hanami/cli/repl/irb.rb +3 -2
  54. data/lib/hanami/cli/repl/pry.rb +1 -3
  55. data/lib/hanami/cli/server.rb +11 -0
  56. data/lib/hanami/cli/system_call.rb +65 -7
  57. data/lib/hanami/cli/url.rb +9 -2
  58. data/lib/hanami/cli/version.rb +4 -1
  59. data/lib/hanami/cli.rb +26 -3
  60. data/lib/hanami/console/context.rb +4 -2
  61. data/lib/hanami/console/plugins/slice_readers.rb +1 -0
  62. data/lib/hanami-cli.rb +3 -0
  63. metadata +46 -25
  64. data/lib/hanami/cli/command_line.rb +0 -17
  65. data/lib/hanami/cli/commands/db/utils/database.rb +0 -133
  66. data/lib/hanami/cli/commands/db/utils/database_config.rb +0 -48
  67. data/lib/hanami/cli/commands/db/utils/mysql.rb +0 -27
  68. data/lib/hanami/cli/commands/db/utils/postgres.rb +0 -54
  69. data/lib/hanami/cli/commands/db/utils/sqlite.rb +0 -37
  70. data/lib/hanami/cli/error.rb +0 -8
@@ -0,0 +1,155 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "database_config"
4
+
5
+ module Hanami
6
+ module CLI
7
+ module Commands
8
+ module App
9
+ module DB
10
+ module Utils
11
+ # @api private
12
+ class Database
13
+ # @api private
14
+ attr_reader :app
15
+
16
+ # @api private
17
+ attr_reader :config
18
+
19
+ # @api private
20
+ SCHEME_MAP = {
21
+ "sqlite" => -> {
22
+ require_relative("sqlite")
23
+ Sqlite
24
+ },
25
+ "postgres" => -> {
26
+ require_relative("postgres")
27
+ Postgres
28
+ },
29
+ "postgresql" => -> {
30
+ require_relative("postgres")
31
+ Postgres
32
+ },
33
+ "mysql" => -> {
34
+ require_relative("mysql")
35
+ Mysql
36
+ }
37
+ }.freeze
38
+
39
+ # @api private
40
+ def self.[](app)
41
+ database_url =
42
+ if app.key?(:settings) && app[:settings].respond_to?(:database_url)
43
+ app[:settings].database_url
44
+ else
45
+ ENV.fetch("DATABASE_URL")
46
+ end
47
+
48
+ config = DatabaseConfig.new(database_url)
49
+
50
+ resolver = SCHEME_MAP.fetch(config.db_type) do
51
+ raise "#{config.db_type} is not a supported db scheme"
52
+ end
53
+
54
+ klass = resolver.()
55
+
56
+ klass.new(app: app, config: config)
57
+ end
58
+
59
+ # @api private
60
+ def initialize(app:, config:)
61
+ @app = app
62
+ @config = config
63
+ end
64
+
65
+ # @api private
66
+ def create_command
67
+ raise Hanami::CLI::NotImplementedError
68
+ end
69
+
70
+ # @api private
71
+ def drop_command
72
+ raise Hanami::CLI::NotImplementedError
73
+ end
74
+
75
+ # @api private
76
+ def dump_command
77
+ raise Hanami::CLI::NotImplementedError
78
+ end
79
+
80
+ # @api private
81
+ def load_command
82
+ raise Hanami::CLI::NotImplementedError
83
+ end
84
+
85
+ # @api private
86
+ def root_path
87
+ app.root
88
+ end
89
+
90
+ # @api private
91
+ def rom_config
92
+ @rom_config ||=
93
+ begin
94
+ app.prepare(:persistence)
95
+ app.container["persistence.config"]
96
+ end
97
+ end
98
+
99
+ # @api private
100
+ def name
101
+ config.db_name
102
+ end
103
+
104
+ # @api private
105
+ def gateway
106
+ rom_config.gateways[:default]
107
+ end
108
+
109
+ # @api private
110
+ def connection
111
+ gateway.connection
112
+ end
113
+
114
+ # @api private
115
+ def run_migrations(**options)
116
+ require "rom/sql"
117
+ ROM::SQL.with_gateway(gateway) do
118
+ migrator.run(options)
119
+ end
120
+ end
121
+
122
+ # @api private
123
+ def migrator
124
+ @migrator ||=
125
+ begin
126
+ require "rom/sql"
127
+ ROM::SQL::Migration::Migrator.new(connection, path: File.join(root_path, "db/migrate"))
128
+ end
129
+ end
130
+
131
+ # @api private
132
+ def applied_migrations
133
+ sequel_migrator.applied_migrations
134
+ end
135
+
136
+ private
137
+
138
+ def sequel_migrator
139
+ @sequel_migrator ||= begin
140
+ require "sequel"
141
+ Sequel.extension :migration
142
+ Sequel::TimestampMigrator.new(migrator.connection, migrations_path, {})
143
+ end
144
+ end
145
+
146
+ def migrations_path
147
+ File.join(root_path, "db/migrate")
148
+ end
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
155
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "uri"
4
+
5
+ module Hanami
6
+ module CLI
7
+ module Commands
8
+ module App
9
+ module DB
10
+ # @api private
11
+ module Utils
12
+ # @api private
13
+ class DatabaseConfig
14
+ # @api private
15
+ attr_reader :uri
16
+
17
+ # @api private
18
+ def initialize(database_url)
19
+ @uri = URI(database_url)
20
+ end
21
+
22
+ # @api private
23
+ def hostname
24
+ uri.hostname
25
+ end
26
+ alias_method :host, :hostname
27
+
28
+ # @api private
29
+ def user
30
+ uri.user
31
+ end
32
+ alias_method :username, :user
33
+
34
+ # @api private
35
+ def password
36
+ uri.password
37
+ end
38
+ alias_method :pass, :password
39
+
40
+ # @api private
41
+ def port
42
+ uri.port
43
+ end
44
+
45
+ # @api private
46
+ def db_name
47
+ @db_name ||= uri.path.gsub(/\A\//, "")
48
+ end
49
+
50
+ # @api private
51
+ def db_type
52
+ uri.scheme
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "database"
4
+
5
+ module Hanami
6
+ module CLI
7
+ module Commands
8
+ module App
9
+ module DB
10
+ module Utils
11
+ # @api private
12
+ class Mysql < Database
13
+ # @api private
14
+ def create_command
15
+ raise Hanami::CLI::NotImplementedError
16
+ end
17
+
18
+ # @api private
19
+ def dump_command
20
+ raise Hanami::CLI::NotImplementedError
21
+ end
22
+
23
+ # @api private
24
+ def load_command
25
+ raise Hanami::CLI::NotImplementedError
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shellwords"
4
+ require "open3"
5
+ require_relative "database"
6
+
7
+ module Hanami
8
+ module CLI
9
+ module Commands
10
+ module App
11
+ module DB
12
+ module Utils
13
+ # @api private
14
+ class Postgres < Database
15
+ # @api private
16
+ def create_command
17
+ existing_stdout, status = Open3.capture2(cli_env_vars, "psql -t -c '\\l #{escaped_name}'")
18
+
19
+ return true if status.success? && existing_stdout.include?(escaped_name)
20
+
21
+ system(cli_env_vars, "createdb #{escaped_name}")
22
+ end
23
+
24
+ # @api private
25
+ def drop_command
26
+ system(cli_env_vars, "dropdb #{escaped_name}")
27
+ end
28
+
29
+ # @api private
30
+ def dump_command
31
+ system(cli_env_vars, "pg_dump --schema-only --no-owner #{escaped_name} > #{dump_file}")
32
+ end
33
+
34
+ # @api private
35
+ def load_command
36
+ raise "Not Implemented Yet"
37
+ end
38
+
39
+ # @api private
40
+ def escaped_name
41
+ Shellwords.escape(name)
42
+ end
43
+
44
+ # @api private
45
+ def cli_env_vars
46
+ @cli_env_vars ||= {}.tap do |vars|
47
+ vars["PGHOST"] = config.host.to_s
48
+ vars["PGPORT"] = config.port.to_s if config.port
49
+ vars["PGUSER"] = config.user.to_s if config.user
50
+ vars["PGPASSWORD"] = config.pass.to_s if config.pass
51
+ end
52
+ end
53
+
54
+ # @api private
55
+ def dump_file
56
+ "#{root_path}/db/structure.sql"
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "database"
4
+
5
+ module Hanami
6
+ module CLI
7
+ module Commands
8
+ module App
9
+ module DB
10
+ module Utils
11
+ # @api private
12
+ class Sqlite < Database
13
+ # @api private
14
+ def create_command
15
+ rom_config
16
+ true
17
+ end
18
+
19
+ # @api private
20
+ def drop_command
21
+ file_path.unlink
22
+ true
23
+ end
24
+
25
+ # @api private
26
+ def dump_command
27
+ raise Hanami::CLI::NotImplementedError
28
+ end
29
+
30
+ # @api private
31
+ def load_command
32
+ raise Hanami::CLI::NotImplementedError
33
+ end
34
+
35
+ # @api private
36
+ def file_path
37
+ @file_path ||= Pathname("#{root_path}#{config.uri.path}").realpath
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -7,11 +7,13 @@ module Hanami
7
7
  module Commands
8
8
  module App
9
9
  module DB
10
+ # @api private
10
11
  class Version < App::Command
11
12
  desc "Print schema version"
12
13
 
13
14
  option :target, desc: "Target migration number", aliases: ["-t"]
14
15
 
16
+ # @api private
15
17
  def call(target: nil, **) # rubocop:disable Lint/UnusedMethodArgument
16
18
  migration = database.applied_migrations.last
17
19
  version = migration ? File.basename(migration, ".*") : "not available"
@@ -1,16 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/cli/commands/app/command"
4
- require "hanami/cli/generators/app/action"
5
3
  require "dry/inflector"
6
4
  require "dry/files"
7
5
  require "shellwords"
6
+ require_relative "../../../errors"
8
7
 
9
8
  module Hanami
10
9
  module CLI
11
10
  module Commands
12
11
  module App
13
12
  module Generate
13
+ # @since 2.0.0
14
+ # @api private
14
15
  class Action < App::Command
15
16
  # TODO: ideally the default format should lookup
16
17
  # slice configuration (Action's `default_response_format`)
@@ -44,6 +45,8 @@ module Hanami
44
45
  ]
45
46
  # rubocop:enable Layout/LineLength
46
47
 
48
+ # @since 2.0.0
49
+ # @api private
47
50
  def initialize(fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new,
48
51
  generator: Generators::App::Action.new(fs: fs, inflector: inflector), **)
49
52
  @generator = generator
@@ -51,17 +54,21 @@ module Hanami
51
54
  end
52
55
 
53
56
  # rubocop:disable Metrics/ParameterLists
57
+
58
+ # @since 2.0.0
59
+ # @api private
54
60
  def call(name:, url: nil, http: nil, format: DEFAULT_FORMAT, skip_view: DEFAULT_SKIP_VIEW, slice: nil, **)
55
61
  slice = inflector.underscore(Shellwords.shellescape(slice)) if slice
56
62
  name = inflector.underscore(Shellwords.shellescape(name))
57
63
  *controller, action = name.split(ACTION_SEPARATOR)
58
64
 
59
65
  if controller.empty?
60
- raise ArgumentError.new("cannot parse controller and action name: `#{name}'\n\texample: users.show")
66
+ raise InvalidActionNameError.new(name)
61
67
  end
62
68
 
63
69
  generator.call(app.namespace, controller, action, url, http, format, skip_view, slice)
64
70
  end
71
+
65
72
  # rubocop:enable Metrics/ParameterLists
66
73
 
67
74
  private
@@ -1,18 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/cli/command"
4
- require "hanami/cli/generators/app/slice"
5
3
  require "dry/inflector"
6
4
  require "dry/files"
7
5
  require "shellwords"
8
- require "hanami/cli/files"
9
- require "hanami/cli/url"
6
+ require_relative "../../../errors"
10
7
 
11
8
  module Hanami
12
9
  module CLI
13
10
  module Commands
14
11
  module App
15
12
  module Generate
13
+ # @since 2.0.0
14
+ # @api private
16
15
  class Slice < Command
17
16
  argument :name, required: true, desc: "The slice name"
18
17
  option :url, required: false, type: :string, desc: "The slice URL prefix"
@@ -22,12 +21,16 @@ module Hanami
22
21
  "users --url=/u # Users slice (/u URL prefix)",
23
22
  ]
24
23
 
24
+ # @since 2.0.0
25
+ # @api private
25
26
  def initialize(fs: Hanami::CLI::Files.new, inflector: Dry::Inflector.new,
26
27
  generator: Generators::App::Slice.new(fs: fs, inflector: inflector), **)
27
28
  @generator = generator
28
29
  super(fs: fs)
29
30
  end
30
31
 
32
+ # @since 2.0.0
33
+ # @api private
31
34
  def call(name:, url: nil, **)
32
35
  require "hanami/setup"
33
36
 
@@ -51,8 +54,8 @@ module Hanami
51
54
 
52
55
  result ||= DEFAULT_URL_PREFIX + name
53
56
  CLI::URL.call(result)
54
- rescue ArgumentError
55
- raise ArgumentError.new("invalid URL prefix: `#{url}'")
57
+ rescue InvalidURLError
58
+ raise InvalidURLPrefixError.new(url)
56
59
  end
57
60
 
58
61
  def valid_url?(url)
@@ -4,6 +4,8 @@ module Hanami
4
4
  module CLI
5
5
  module Commands
6
6
  module App
7
+ # @since 2.0.0
8
+ # @api private
7
9
  module Generate
8
10
  require_relative "./generate/slice"
9
11
  require_relative "./generate/action"
@@ -1,11 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/cli/command"
4
-
5
3
  module Hanami
6
4
  module CLI
7
5
  module Commands
8
6
  module App
7
+ # The `install` command exists to provide third parties a hook for their own installation
8
+ # behaviour to be run as part of `hanami new`.
9
+ #
10
+ # Third parties should register their install commands like so:
11
+ #
12
+ # ```
13
+ # if Hanami::CLI.within_hanami_app?
14
+ # Hanami::CLI.after "install", MyHanamiGem::CLI::Commands::Install
15
+ # end
16
+ # ````
17
+ #
18
+ # @since 2.0.0
19
+ # @api private
9
20
  class Install < Command
10
21
  desc "Install Hanami third-party plugins"
11
22
 
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "hanami"
4
- require "hanami/cli/middleware_stack_inspector"
5
4
 
6
5
  module Hanami
7
6
  module CLI
@@ -23,10 +22,16 @@ module Hanami
23
22
  # $ bundle exec hanami middleware --with-arguments
24
23
  # / Rack::Session::Cookie args: [{:secret=>"foo"}]
25
24
  # ```
25
+ #
26
+ # @since 2.0.0
27
+ # @api private
26
28
  class Middleware < Hanami::CLI::Command
27
29
  desc "Print app Rack middleware stack"
28
30
 
31
+ # @since 2.0.0
32
+ # @api private
29
33
  DEFAULT_WITH_ARGUMENTS = false
34
+ private_constant :DEFAULT_WITH_ARGUMENTS
30
35
 
31
36
  option :with_arguments, default: DEFAULT_WITH_ARGUMENTS, required: false,
32
37
  desc: "Include inspected arguments", type: :boolean
@@ -36,6 +41,7 @@ module Hanami
36
41
  "middleware --with-arguments # Print app Rack middleware stack, including initialize arguments",
37
42
  ]
38
43
 
44
+ # @since 2.0.0
39
45
  # @api private
40
46
  def call(with_arguments: DEFAULT_WITH_ARGUMENTS)
41
47
  require "hanami/prepare"
@@ -20,10 +20,17 @@ module Hanami
20
20
  # ```
21
21
  # $ bundle exec hanami routes --format=custom_routes_formatter
22
22
  # ```
23
+ #
24
+ # @since 2.0.0
25
+ # @api private
23
26
  class Routes < Hanami::CLI::Command
27
+ # @since 2.0.0
28
+ # @api private
24
29
  DEFAULT_FORMAT = "human_friendly"
25
30
  private_constant :DEFAULT_FORMAT
26
31
 
32
+ # @since 2.0.0
33
+ # @api private
27
34
  VALID_FORMATS = [
28
35
  DEFAULT_FORMAT,
29
36
  "csv"
@@ -42,6 +49,7 @@ module Hanami
42
49
  "routes --format=csv # Print app routes, using CSV format",
43
50
  ]
44
51
 
52
+ # @since 2.0.0
45
53
  # @api private
46
54
  def call(format: DEFAULT_FORMAT, **)
47
55
  require "hanami/router/inspector"
@@ -24,10 +24,17 @@ module Hanami
24
24
  # - All others are always given by the Hanami command.
25
25
  #
26
26
  # Run `bundle exec hanami server -h` to see all the supported options.
27
+ #
28
+ # @since 2.0.0
29
+ # @api private
27
30
  class Server < Command
31
+ # @since 2.0.0
32
+ # @api private
28
33
  DEFAULT_PORT = 2300
29
34
  private_constant :DEFAULT_PORT
30
35
 
36
+ # @since 2.0.0
37
+ # @api private
31
38
  DEFAULT_CONFIG_PATH = "config.ru"
32
39
  private_constant :DEFAULT_CONFIG_PATH
33
40
 
@@ -41,11 +48,14 @@ module Hanami
41
48
  option :debug, default: false, required: false, desc: "Turn on/off debug output", type: :boolean
42
49
  option :warn, default: false, required: false, desc: "Turn on/off warnings", type: :boolean
43
50
 
51
+ # @since 2.0.0
52
+ # @api private
44
53
  def initialize(server: Hanami::CLI::Server.new)
45
54
  super()
46
55
  @server = server
47
56
  end
48
57
 
58
+ # @since 2.0.0
49
59
  # @api private
50
60
  def call(port: DEFAULT_PORT, **kwargs)
51
61
  server.call(port: port, **kwargs)
@@ -1,14 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "hanami/cli/command"
4
-
5
3
  module Hanami
6
4
  module CLI
7
5
  module Commands
8
6
  module App
7
+ # @since 2.0.0
8
+ # @api private
9
9
  class Version < Command
10
10
  desc "Print Hanami app version"
11
11
 
12
+ # @since 2.0.0
13
+ # @api private
12
14
  def call(*)
13
15
  require "hanami/version"
14
16
  out.puts "v#{Hanami::VERSION}"
@@ -3,26 +3,13 @@
3
3
  module Hanami
4
4
  module CLI
5
5
  module Commands
6
+ # Commands made available when the `hanami` CLI is executed within an Hanami app.
7
+ #
8
+ # @api private
9
+ # @since 2.0.0
6
10
  module App
7
- require_relative "app/version"
8
- require_relative "app/install"
9
- require_relative "app/console"
10
- require_relative "app/server"
11
- require_relative "app/routes"
12
- require_relative "app/generate"
13
- require_relative "app/middleware"
14
- # require_relative "app/db/create"
15
- # require_relative "app/db/create_migration"
16
- # require_relative "app/db/drop"
17
- # require_relative "app/db/migrate"
18
- # require_relative "app/db/setup"
19
- # require_relative "app/db/reset"
20
- # require_relative "app/db/rollback"
21
- # require_relative "app/db/sample_data"
22
- # require_relative "app/db/seed"
23
- # require_relative "app/db/structure/dump"
24
- # require_relative "app/db/version"
25
-
11
+ # @since 2.0.0
12
+ # @api private
26
13
  def self.extended(base)
27
14
  base.module_eval do
28
15
  register "version", Commands::App::Version, aliases: ["v", "-v", "--version"]