cpl 0.1.0 → 0.3.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.
- checksums.yaml +4 -4
- data/.github/workflows/{ci.yml → rspec.yml} +2 -25
- data/.github/workflows/rubocop.yml +29 -0
- data/CHANGELOG.md +6 -2
- data/CONTRIBUTING.md +16 -0
- data/Gemfile.lock +4 -16
- data/README.md +46 -25
- data/Rakefile +5 -0
- data/cpl.gemspec +0 -6
- data/docs/commands.md +40 -8
- data/examples/circleci.yml +4 -4
- data/lib/command/base.rb +43 -14
- data/lib/command/cleanup_old_images.rb +76 -0
- data/lib/command/cleanup_stale_apps.rb +88 -0
- data/lib/command/delete.rb +13 -5
- data/lib/command/{promote_image.rb → deploy_image.rb} +5 -5
- data/lib/command/env.rb +1 -1
- data/lib/command/no_command.rb +19 -0
- data/lib/command/open.rb +1 -1
- data/lib/command/run.rb +1 -1
- data/lib/command/run_detached.rb +1 -1
- data/lib/command/setup.rb +5 -2
- data/lib/command/test.rb +0 -2
- data/lib/command/version.rb +16 -0
- data/lib/core/config.rb +49 -16
- data/lib/core/controlplane.rb +27 -4
- data/lib/core/controlplane_api_direct.rb +2 -1
- data/lib/core/shell.rb +31 -0
- data/lib/cpl/version.rb +1 -1
- data/lib/cpl.rb +5 -3
- data/rakelib/create_release.rake +80 -0
- data/script/generate_commands_docs +3 -1
- metadata +13 -90
- /data/{postgres.md → docs/postgres.md} +0 -0
- /data/{redis.md → docs/redis.md} +0 -0
data/lib/command/delete.rb
CHANGED
@@ -4,7 +4,8 @@ module Command
|
|
4
4
|
class Delete < Base
|
5
5
|
NAME = "delete"
|
6
6
|
OPTIONS = [
|
7
|
-
app_option(required: true)
|
7
|
+
app_option(required: true),
|
8
|
+
skip_confirm_option
|
8
9
|
].freeze
|
9
10
|
DESCRIPTION = "Deletes the whole app (GVC with all workloads and all images)"
|
10
11
|
LONG_DESCRIPTION = <<~HEREDOC
|
@@ -13,10 +14,7 @@ module Command
|
|
13
14
|
HEREDOC
|
14
15
|
|
15
16
|
def call
|
16
|
-
|
17
|
-
progress.print "> "
|
18
|
-
|
19
|
-
return progress.puts "Not confirmed" unless $stdin.gets.chomp == "delete"
|
17
|
+
return unless confirm_delete
|
20
18
|
|
21
19
|
delete_gvc
|
22
20
|
delete_images
|
@@ -24,6 +22,16 @@ module Command
|
|
24
22
|
|
25
23
|
private
|
26
24
|
|
25
|
+
def confirm_delete
|
26
|
+
return true if config.options[:yes]
|
27
|
+
|
28
|
+
confirmed = Shell.confirm("Are you sure you want to delete '#{config.app}'?")
|
29
|
+
return false unless confirmed
|
30
|
+
|
31
|
+
progress.puts
|
32
|
+
true
|
33
|
+
end
|
34
|
+
|
27
35
|
def delete_gvc
|
28
36
|
progress.puts "- Deleting gvc:"
|
29
37
|
|
@@ -1,21 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Command
|
4
|
-
class
|
5
|
-
NAME = "
|
4
|
+
class DeployImage < Base
|
5
|
+
NAME = "deploy-image"
|
6
6
|
OPTIONS = [
|
7
7
|
app_option(required: true)
|
8
8
|
].freeze
|
9
|
-
DESCRIPTION = "
|
9
|
+
DESCRIPTION = "Deploys the latest image to app workloads"
|
10
10
|
LONG_DESCRIPTION = <<~HEREDOC
|
11
|
-
-
|
11
|
+
- Deploys the latest image to app workloads
|
12
12
|
HEREDOC
|
13
13
|
|
14
14
|
def call
|
15
15
|
image = latest_image
|
16
16
|
|
17
17
|
config[:app_workloads].each do |workload|
|
18
|
-
cp.
|
18
|
+
cp.workload_get_and_ensure(workload).dig("spec", "containers").each do |container|
|
19
19
|
next unless container["image"].match?(%r{^/org/#{config[:cpln_org]}/image/#{config.app}:})
|
20
20
|
|
21
21
|
cp.workload_set_image_ref(workload, container: container["name"], image: image)
|
data/lib/command/env.rb
CHANGED
@@ -12,7 +12,7 @@ module Command
|
|
12
12
|
HEREDOC
|
13
13
|
|
14
14
|
def call
|
15
|
-
cp.
|
15
|
+
cp.gvc_get_and_ensure.dig("spec", "env").map do |prop|
|
16
16
|
# NOTE: atm no special chars handling, consider adding if needed
|
17
17
|
puts "#{prop['name']}=#{prop['value']}"
|
18
18
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class NoCommand < Base
|
5
|
+
NAME = "no-command"
|
6
|
+
OPTIONS = [version_option].freeze
|
7
|
+
DESCRIPTION = "Called when no command was specified"
|
8
|
+
LONG_DESCRIPTION = <<~HEREDOC
|
9
|
+
- Called when no command was specified
|
10
|
+
HEREDOC
|
11
|
+
HIDE = true
|
12
|
+
|
13
|
+
def call
|
14
|
+
return unless config.options[:version]
|
15
|
+
|
16
|
+
Version.new(config).call
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/command/open.rb
CHANGED
@@ -23,7 +23,7 @@ module Command
|
|
23
23
|
|
24
24
|
def call
|
25
25
|
workload = config.options[:workload] || config[:one_off_workload]
|
26
|
-
data = cp.
|
26
|
+
data = cp.workload_get_and_ensure(workload)
|
27
27
|
url = data["status"]["endpoint"]
|
28
28
|
opener = `which xdg-open open`.split("\n").grep_v("not found").first
|
29
29
|
|
data/lib/command/run.rb
CHANGED
@@ -59,7 +59,7 @@ module Command
|
|
59
59
|
progress.puts "- Cloning workload '#{workload}' on '#{config.options[:app]}' to '#{one_off}'"
|
60
60
|
|
61
61
|
# Create a base copy of workload props
|
62
|
-
spec = cp.
|
62
|
+
spec = cp.workload_get_and_ensure(workload).fetch("spec")
|
63
63
|
container = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
|
64
64
|
|
65
65
|
# remove other containers if any
|
data/lib/command/run_detached.rb
CHANGED
@@ -57,7 +57,7 @@ module Command
|
|
57
57
|
progress.puts "- Cloning workload '#{workload}' on '#{config.options[:app]}' to '#{one_off}'"
|
58
58
|
|
59
59
|
# Get base specs of workload
|
60
|
-
spec = cp.
|
60
|
+
spec = cp.workload_get_and_ensure(workload).fetch("spec")
|
61
61
|
container = spec["containers"].detect { _1["name"] == workload } || spec["containers"].first
|
62
62
|
|
63
63
|
# remove other containers if any
|
data/lib/command/setup.rb
CHANGED
@@ -37,8 +37,7 @@ module Command
|
|
37
37
|
def call
|
38
38
|
config.args.each do |template|
|
39
39
|
filename = "#{config.app_cpln_dir}/templates/#{template}.yml"
|
40
|
-
|
41
|
-
|
40
|
+
ensure_template(template, filename)
|
42
41
|
apply_template(filename)
|
43
42
|
progress.puts(template)
|
44
43
|
end
|
@@ -46,6 +45,10 @@ module Command
|
|
46
45
|
|
47
46
|
private
|
48
47
|
|
48
|
+
def ensure_template(template, filename)
|
49
|
+
Shell.abort("Can't find template '#{template}' at '#{filename}', please create it.") unless File.exist?(filename)
|
50
|
+
end
|
51
|
+
|
49
52
|
def apply_template(filename)
|
50
53
|
data = File.read(filename)
|
51
54
|
.gsub("APP_GVC", config.app)
|
data/lib/command/test.rb
CHANGED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Command
|
4
|
+
class Version < Base
|
5
|
+
NAME = "version"
|
6
|
+
DESCRIPTION = "Displays the current version of the CLI"
|
7
|
+
LONG_DESCRIPTION = <<~HEREDOC
|
8
|
+
- Displays the current version of the CLI
|
9
|
+
- Can also be done with `cpl --version` or `cpl -v`
|
10
|
+
HEREDOC
|
11
|
+
|
12
|
+
def call
|
13
|
+
puts Cpl::VERSION
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
data/lib/core/config.rb
CHANGED
@@ -15,26 +15,19 @@ class Config
|
|
15
15
|
|
16
16
|
load_app_config
|
17
17
|
pick_current_config if app
|
18
|
+
warn_deprecated_options if current
|
18
19
|
end
|
19
20
|
|
20
|
-
def [](key)
|
21
|
-
|
22
|
-
|
23
|
-
logger = $stderr
|
21
|
+
def [](key)
|
22
|
+
ensure_current_config
|
24
23
|
|
24
|
+
old_key = old_option_keys[key]
|
25
25
|
if current.key?(key)
|
26
26
|
current.fetch(key)
|
27
|
-
elsif
|
28
|
-
|
29
|
-
current.fetch(:org)
|
30
|
-
elsif key == :default_location && current.key?(:location)
|
31
|
-
logger.puts("DEPRECATED: option 'location' is deprecated, use 'default_location' instead\n")
|
32
|
-
current.fetch(:location)
|
33
|
-
elsif key == :match_if_app_name_starts_with && current.key?(:prefix)
|
34
|
-
logger.puts("DEPRECATED: option 'prefix' is deprecated, use 'match_if_app_name_starts_with' instead\n")
|
35
|
-
current.fetch(:prefix)
|
27
|
+
elsif old_key && current.key?(old_key)
|
28
|
+
current.fetch(old_key)
|
36
29
|
else
|
37
|
-
abort("
|
30
|
+
Shell.abort("Can't find option '#{key}' for app '#{app}' in 'controlplane.yml'.")
|
38
31
|
end
|
39
32
|
end
|
40
33
|
|
@@ -48,13 +41,37 @@ class Config
|
|
48
41
|
|
49
42
|
private
|
50
43
|
|
44
|
+
def ensure_current_config
|
45
|
+
Shell.abort("Can't find current config, please specify an app.") unless current
|
46
|
+
end
|
47
|
+
|
48
|
+
def ensure_current_config_app(app)
|
49
|
+
Shell.abort("Can't find app '#{app}' in 'controlplane.yml'.") unless current
|
50
|
+
end
|
51
|
+
|
52
|
+
def ensure_config
|
53
|
+
Shell.abort("'controlplane.yml' is empty.") unless config
|
54
|
+
end
|
55
|
+
|
56
|
+
def ensure_config_apps
|
57
|
+
Shell.abort("Can't find key 'apps' in 'controlplane.yml'.") unless config[:apps]
|
58
|
+
end
|
59
|
+
|
60
|
+
def ensure_config_app(app, options)
|
61
|
+
Shell.abort("App '#{app}' is empty in 'controlplane.yml'.") unless options
|
62
|
+
end
|
63
|
+
|
51
64
|
def pick_current_config
|
65
|
+
ensure_config
|
66
|
+
ensure_config_apps
|
52
67
|
config[:apps].each do |c_app, c_data|
|
68
|
+
ensure_config_app(c_app, c_data)
|
53
69
|
if c_app.to_s == app || (c_data[:match_if_app_name_starts_with] && app.start_with?(c_app.to_s))
|
54
70
|
@current = c_data
|
55
71
|
break
|
56
72
|
end
|
57
73
|
end
|
74
|
+
ensure_current_config_app(app)
|
58
75
|
end
|
59
76
|
|
60
77
|
def load_app_config
|
@@ -73,8 +90,24 @@ class Config
|
|
73
90
|
path = path.parent
|
74
91
|
|
75
92
|
if path.root?
|
76
|
-
|
77
|
-
|
93
|
+
Shell.abort("Can't find project config file at 'project_folder/#{CONFIG_FILE_LOCATIION}', please create it.")
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def old_option_keys
|
99
|
+
{
|
100
|
+
cpln_org: :org,
|
101
|
+
default_location: :location,
|
102
|
+
match_if_app_name_starts_with: :prefix
|
103
|
+
}
|
104
|
+
end
|
105
|
+
|
106
|
+
def warn_deprecated_options
|
107
|
+
old_option_keys.each do |new_key, old_key|
|
108
|
+
if current.key?(old_key)
|
109
|
+
Shell.warn_deprecated("Option '#{old_key}' is deprecated, " \
|
110
|
+
"please use '#{new_key}' instead (in 'controlplane.yml').")
|
78
111
|
end
|
79
112
|
end
|
80
113
|
end
|
data/lib/core/controlplane.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
class Controlplane
|
3
|
+
class Controlplane # rubocop:disable Metrics/ClassLength
|
4
4
|
attr_reader :config, :api, :gvc, :org
|
5
5
|
|
6
6
|
def initialize(config)
|
@@ -18,8 +18,8 @@ class Controlplane
|
|
18
18
|
perform(cmd)
|
19
19
|
end
|
20
20
|
|
21
|
-
def image_query
|
22
|
-
cmd = "cpln image query --org #{org} -o yaml --max -1 --prop repository=#{
|
21
|
+
def image_query(app_name = config.app)
|
22
|
+
cmd = "cpln image query --org #{org} -o yaml --max -1 --prop repository=#{app_name}"
|
23
23
|
perform_yaml(cmd)
|
24
24
|
end
|
25
25
|
|
@@ -29,10 +29,26 @@ class Controlplane
|
|
29
29
|
|
30
30
|
# gvc
|
31
31
|
|
32
|
+
def gvc_query(app_name = config.app)
|
33
|
+
# When `match_if_app_name_starts_with` is `true`, we query for any gvc containing the name,
|
34
|
+
# otherwise we query for a gvc with the exact name.
|
35
|
+
op = config.current[:match_if_app_name_starts_with] ? "~" : "="
|
36
|
+
|
37
|
+
cmd = "cpln gvc query --org #{org} -o yaml --prop name#{op}#{app_name}"
|
38
|
+
perform_yaml(cmd)
|
39
|
+
end
|
40
|
+
|
32
41
|
def gvc_get(a_gvc = gvc)
|
33
42
|
api.gvc_get(gvc: a_gvc, org: org)
|
34
43
|
end
|
35
44
|
|
45
|
+
def gvc_get_and_ensure(a_gvc = gvc)
|
46
|
+
gvc_data = gvc_get(a_gvc)
|
47
|
+
return gvc_data if gvc_data
|
48
|
+
|
49
|
+
Shell.abort("Can't find GVC '#{gvc}', please create it with 'cpl setup gvc -a #{config.app}'.")
|
50
|
+
end
|
51
|
+
|
36
52
|
def gvc_delete(a_gvc = gvc)
|
37
53
|
api.gvc_delete(gvc: a_gvc, org: org)
|
38
54
|
end
|
@@ -43,6 +59,13 @@ class Controlplane
|
|
43
59
|
api.workload_get(workload: workload, gvc: gvc, org: org)
|
44
60
|
end
|
45
61
|
|
62
|
+
def workload_get_and_ensure(workload)
|
63
|
+
workload_data = workload_get(workload)
|
64
|
+
return workload_data if workload_data
|
65
|
+
|
66
|
+
Shell.abort("Can't find workload '#{workload}', please create it with 'cpl setup #{workload} -a #{config.app}'.")
|
67
|
+
end
|
68
|
+
|
46
69
|
def workload_get_replicas(workload, location:)
|
47
70
|
cmd = "cpln workload get-replicas #{workload} #{gvc_org} --location #{location} -o yaml"
|
48
71
|
perform_yaml(cmd)
|
@@ -55,7 +78,7 @@ class Controlplane
|
|
55
78
|
end
|
56
79
|
|
57
80
|
def workload_set_suspend(workload, value)
|
58
|
-
data =
|
81
|
+
data = workload_get_and_ensure(workload)
|
59
82
|
data["spec"]["defaultOptions"]["suspend"] = value
|
60
83
|
apply(data)
|
61
84
|
end
|
@@ -37,6 +37,7 @@ class ControlplaneApiDirect
|
|
37
37
|
@@api_token = ENV.fetch("CPLN_TOKEN", `cpln profile token`.chomp) # rubocop:disable Style/ClassVars
|
38
38
|
return @@api_token if @@api_token.match?(API_TOKEN_REGEX)
|
39
39
|
|
40
|
-
abort("
|
40
|
+
Shell.abort("Unknown API token format. " \
|
41
|
+
"Please re-run 'cpln profile login' or set the correct CPLN_TOKEN env variable.")
|
41
42
|
end
|
42
43
|
end
|
data/lib/core/shell.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Shell
|
4
|
+
def self.shell
|
5
|
+
@shell ||= Thor::Shell::Color.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.stderr
|
9
|
+
@stderr ||= $stderr
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.color(message, color_key)
|
13
|
+
shell.set_color(message, color_key)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.confirm(message)
|
17
|
+
shell.yes?("#{message} (y/n)")
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.warn(message)
|
21
|
+
stderr.puts(color("WARNING: #{message}\n", :yellow))
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.warn_deprecated(message)
|
25
|
+
stderr.puts(color("DEPRECATED: #{message}\n", :yellow))
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.abort(message)
|
29
|
+
Kernel.abort(color("ERROR: #{message}\n", :red))
|
30
|
+
end
|
31
|
+
end
|
data/lib/cpl/version.rb
CHANGED
data/lib/cpl.rb
CHANGED
@@ -37,6 +37,7 @@ module Cpl
|
|
37
37
|
|
38
38
|
class Cli < Thor
|
39
39
|
package_name "cpl"
|
40
|
+
default_task :no_command
|
40
41
|
|
41
42
|
def self.start(*args)
|
42
43
|
fix_help_option
|
@@ -71,7 +72,8 @@ module Cpl
|
|
71
72
|
def self.deprecated_commands
|
72
73
|
{
|
73
74
|
build: ::Command::BuildImage,
|
74
|
-
promote: ::Command::
|
75
|
+
promote: ::Command::DeployImage,
|
76
|
+
promote_image: ::Command::DeployImage,
|
75
77
|
runner: ::Command::RunDetached
|
76
78
|
}
|
77
79
|
end
|
@@ -109,8 +111,8 @@ module Cpl
|
|
109
111
|
|
110
112
|
define_method(name_for_method) do |*provided_args| # rubocop:disable Metrics/MethodLength
|
111
113
|
if deprecated
|
112
|
-
|
113
|
-
|
114
|
+
::Shell.warn_deprecated("Command '#{command_key}' is deprecated, " \
|
115
|
+
"please use '#{name}' instead.")
|
114
116
|
end
|
115
117
|
|
116
118
|
args = if provided_args.length.positive?
|
@@ -0,0 +1,80 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "English"
|
4
|
+
|
5
|
+
desc("Releases the gem package using the given version.
|
6
|
+
|
7
|
+
IMPORTANT: the gem version must be in valid rubygem format (no dashes).
|
8
|
+
This task depends on the gem-release ruby gem.
|
9
|
+
|
10
|
+
1st argument: The new version in rubygem format (no dashes). Pass no argument to
|
11
|
+
automatically perform a patch version bump.
|
12
|
+
2nd argument: Perform a dry run by passing 'true' as a second argument.
|
13
|
+
|
14
|
+
Example: `rake create_release[2.1.0,false]`")
|
15
|
+
|
16
|
+
task :create_release, %i[gem_version dry_run] do |_t, args|
|
17
|
+
args_hash = args.to_hash
|
18
|
+
|
19
|
+
is_dry_run = Release.object_to_boolean(args_hash[:dry_run])
|
20
|
+
gem_version = args_hash.fetch(:gem_version, "").strip
|
21
|
+
gem_root = Release.gem_root
|
22
|
+
|
23
|
+
Release.update_the_local_project
|
24
|
+
Release.ensure_there_is_nothing_to_commit
|
25
|
+
Release.sh_in_dir(gem_root,
|
26
|
+
"gem bump --no-commit #{gem_version == '' ? '' : %(--version #{gem_version})}")
|
27
|
+
Release.sh_in_dir(gem_root, "bundle install")
|
28
|
+
Release.sh_in_dir(gem_root, "git commit -am 'Bump version to #{gem_version}'")
|
29
|
+
|
30
|
+
# See https://github.com/svenfuchs/gem-release
|
31
|
+
Release.release_the_new_gem_version unless is_dry_run
|
32
|
+
end
|
33
|
+
|
34
|
+
module Release
|
35
|
+
extend FileUtils
|
36
|
+
class << self
|
37
|
+
def gem_root
|
38
|
+
File.expand_path("..", __dir__)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Executes a string or an array of strings in a shell in the given directory in an unbundled environment
|
42
|
+
def sh_in_dir(dir, *shell_commands)
|
43
|
+
shell_commands.flatten.each { |shell_command| sh %(cd #{dir} && #{shell_command.strip}) }
|
44
|
+
end
|
45
|
+
|
46
|
+
def ensure_there_is_nothing_to_commit
|
47
|
+
status = `git status --porcelain`
|
48
|
+
|
49
|
+
return if $CHILD_STATUS.success? && status == ""
|
50
|
+
|
51
|
+
error = if $CHILD_STATUS.success?
|
52
|
+
"You have uncommitted code. Please commit or stash your changes before continuing"
|
53
|
+
else
|
54
|
+
"You do not have Git installed. Please install Git, and commit your changes before continuing"
|
55
|
+
end
|
56
|
+
raise(error)
|
57
|
+
end
|
58
|
+
|
59
|
+
def object_to_boolean(value)
|
60
|
+
[true, "true", "yes", 1, "1", "t"].include?(value.instance_of?(String) ? value.downcase : value)
|
61
|
+
end
|
62
|
+
|
63
|
+
def update_the_local_project
|
64
|
+
puts "Pulling latest commits from remote repository"
|
65
|
+
|
66
|
+
sh_in_dir(gem_root, "git pull --rebase")
|
67
|
+
raise "Failed in pulling latest changes from default remore repository." unless $CHILD_STATUS.success?
|
68
|
+
rescue Errno::ENOENT
|
69
|
+
raise "Ensure you have Git and Bundler installed before continuing."
|
70
|
+
end
|
71
|
+
|
72
|
+
def release_the_new_gem_version
|
73
|
+
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
74
|
+
puts "Use the OTP for RubyGems!"
|
75
|
+
puts "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
|
76
|
+
|
77
|
+
sh_in_dir(gem_root, "gem release")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -6,7 +6,9 @@ require_relative "../lib/cpl"
|
|
6
6
|
commands_str_arr = []
|
7
7
|
|
8
8
|
commands = Command::Base.all_commands
|
9
|
-
commands.each do |
|
9
|
+
commands.keys.sort.each do |command_key|
|
10
|
+
command_class = commands[command_key]
|
11
|
+
|
10
12
|
next if command_class::HIDE
|
11
13
|
|
12
14
|
name = command_class::NAME
|