cli-kit 3.3.0 → 4.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/dependabot.yml +10 -0
- data/.github/workflows/ruby.yml +32 -0
- data/.rubocop.yml +9 -15
- data/Gemfile +4 -3
- data/Gemfile.lock +33 -27
- data/Rakefile +27 -1
- data/bin/console +3 -3
- data/bin/test_gen +4 -1
- data/bin/testunit +2 -2
- data/cli-kit.gemspec +6 -5
- data/dev.yml +1 -1
- data/examples/minimal/example.rb +2 -2
- data/examples/single-file/example.rb +1 -1
- data/gen/lib/gen/commands/help.rb +4 -4
- data/gen/lib/gen/generator.rb +11 -11
- data/lib/cli/kit/base_command.rb +5 -5
- data/lib/cli/kit/config.rb +8 -6
- data/lib/cli/kit/error_handler.rb +12 -6
- data/lib/cli/kit/executor.rb +20 -17
- data/lib/cli/kit/ini.rb +2 -2
- data/lib/cli/kit/logger.rb +8 -2
- data/lib/cli/kit/resolver.rb +2 -2
- data/lib/cli/kit/support/test_helper.rb +9 -9
- data/lib/cli/kit/system.rb +59 -24
- data/lib/cli/kit/util.rb +10 -10
- data/lib/cli/kit/version.rb +1 -1
- data/lib/cli/kit.rb +1 -1
- metadata +16 -15
- data/.travis.yml +0 -14
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 523252459712e507c4579e244e6330eb3e8a5d8c71334d4e41553903ab651cd6
|
4
|
+
data.tar.gz: 459cb0a684e2890718d0cffdd74cd8540bd91a1995dfe1db593df9a189da20d7
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8579924711573ce6338b52dc44c47ad40c12f925bbd6f3ad6cee5be9f44b2b4f315e6d272adc309788b0418ee6806567660f0386d7e91d9d09a524beeb0a4a1f
|
7
|
+
data.tar.gz: b5507292ba97fd6f13b83b6426e81e4500e0ef1480387164c936164b7593bf7daac98e39cbb835362f198c39e562f2d12adf92384365a9c99a7f0ed42c158a45
|
@@ -0,0 +1,32 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
style:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: '2.7'
|
14
|
+
bundler-cache: true
|
15
|
+
- name: Check style
|
16
|
+
run: bundle exec rake style
|
17
|
+
test:
|
18
|
+
strategy:
|
19
|
+
matrix:
|
20
|
+
os: [macos-latest, ubuntu-latest, windows-latest]
|
21
|
+
ruby-version: ['2.6', '2.7']
|
22
|
+
|
23
|
+
runs-on: ${{ matrix.os }}
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v2
|
26
|
+
- name: Set up Ruby
|
27
|
+
uses: ruby/setup-ruby@v1
|
28
|
+
with:
|
29
|
+
ruby-version: ${{ matrix.ruby-version }}
|
30
|
+
bundler-cache: true
|
31
|
+
- name: Run tests
|
32
|
+
run: bundle exec rake test
|
data/.rubocop.yml
CHANGED
@@ -1,9 +1,11 @@
|
|
1
|
-
|
2
|
-
-
|
1
|
+
inherit_gem:
|
2
|
+
rubocop-shopify: rubocop-cli.yml
|
3
3
|
|
4
4
|
AllCops:
|
5
|
-
Exclude:
|
6
|
-
|
5
|
+
Exclude:
|
6
|
+
- gen/template/**/*
|
7
|
+
- vendor/**/*
|
8
|
+
TargetRubyVersion: 2.5
|
7
9
|
|
8
10
|
Style/ClassAndModuleChildren:
|
9
11
|
Exclude:
|
@@ -12,15 +14,8 @@ Style/ClassAndModuleChildren:
|
|
12
14
|
Style/FrozenStringLiteralComment:
|
13
15
|
Enabled: false
|
14
16
|
|
15
|
-
Shopify/RubocopComments:
|
16
|
-
Enabled: false
|
17
|
-
|
18
|
-
# This doesn't understand that <<~ doesn't exist in 2.0
|
19
|
-
Layout/IndentHeredoc:
|
20
|
-
Enabled: false
|
21
|
-
|
22
17
|
# This doesn't take into account retrying from an exception
|
23
|
-
Lint/
|
18
|
+
Lint/SuppressedException:
|
24
19
|
Enabled: false
|
25
20
|
|
26
21
|
# allow String.new to create mutable strings
|
@@ -39,6 +34,5 @@ Style/RegexpLiteral:
|
|
39
34
|
Style/MultilineBlockChain:
|
40
35
|
Enabled: false
|
41
36
|
|
42
|
-
|
43
|
-
|
44
|
-
Enabled: false
|
37
|
+
Style/StringLiterals:
|
38
|
+
EnforcedStyle: single_quotes
|
data/Gemfile
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# NOTE: These are development-only dependencies
|
2
|
-
source
|
2
|
+
source 'https://rubygems.org'
|
3
3
|
|
4
4
|
gemspec
|
5
5
|
|
6
6
|
group :development, :test do
|
7
|
-
gem 'rubocop'
|
7
|
+
gem 'rubocop'
|
8
|
+
gem 'rubocop-shopify'
|
8
9
|
gem 'byebug'
|
9
10
|
gem 'method_source'
|
10
11
|
end
|
11
12
|
|
12
13
|
group :test do
|
13
|
-
gem 'mocha', '~> 1.
|
14
|
+
gem 'mocha', '~> 1.13.0', require: false
|
14
15
|
gem 'minitest', '>= 5.0.0', require: false
|
15
16
|
gem 'minitest-reporters', require: false
|
16
17
|
end
|
data/Gemfile.lock
CHANGED
@@ -1,56 +1,62 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
cli-kit (
|
4
|
+
cli-kit (4.0.0)
|
5
5
|
cli-ui (>= 1.1.4)
|
6
6
|
|
7
7
|
GEM
|
8
8
|
remote: https://rubygems.org/
|
9
9
|
specs:
|
10
10
|
ansi (1.5.0)
|
11
|
-
ast (2.4.
|
12
|
-
builder (3.2.
|
13
|
-
byebug (
|
14
|
-
cli-ui (1.
|
15
|
-
|
16
|
-
|
17
|
-
minitest (
|
18
|
-
minitest-reporters (1.1.14)
|
11
|
+
ast (2.4.2)
|
12
|
+
builder (3.2.4)
|
13
|
+
byebug (11.1.3)
|
14
|
+
cli-ui (1.5.1)
|
15
|
+
method_source (1.0.0)
|
16
|
+
minitest (5.14.4)
|
17
|
+
minitest-reporters (1.4.3)
|
19
18
|
ansi
|
20
19
|
builder
|
21
20
|
minitest (>= 5.0)
|
22
21
|
ruby-progressbar
|
23
|
-
mocha (1.
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
ast (~> 2.4.0)
|
28
|
-
powerpack (0.1.1)
|
22
|
+
mocha (1.13.0)
|
23
|
+
parallel (1.21.0)
|
24
|
+
parser (3.0.2.0)
|
25
|
+
ast (~> 2.4.1)
|
29
26
|
rainbow (3.0.0)
|
30
|
-
rake (
|
31
|
-
|
27
|
+
rake (13.0.6)
|
28
|
+
regexp_parser (2.1.1)
|
29
|
+
rexml (3.2.5)
|
30
|
+
rubocop (1.22.3)
|
32
31
|
parallel (~> 1.10)
|
33
|
-
parser (>=
|
34
|
-
powerpack (~> 0.1)
|
32
|
+
parser (>= 3.0.0.0)
|
35
33
|
rainbow (>= 2.2.2, < 4.0)
|
34
|
+
regexp_parser (>= 1.8, < 3.0)
|
35
|
+
rexml
|
36
|
+
rubocop-ast (>= 1.12.0, < 2.0)
|
36
37
|
ruby-progressbar (~> 1.7)
|
37
|
-
unicode-display_width (
|
38
|
-
|
39
|
-
|
38
|
+
unicode-display_width (>= 1.4.0, < 3.0)
|
39
|
+
rubocop-ast (1.12.0)
|
40
|
+
parser (>= 3.0.1.1)
|
41
|
+
rubocop-shopify (2.3.0)
|
42
|
+
rubocop (~> 1.22)
|
43
|
+
ruby-progressbar (1.11.0)
|
44
|
+
unicode-display_width (2.1.0)
|
40
45
|
|
41
46
|
PLATFORMS
|
42
47
|
ruby
|
43
48
|
|
44
49
|
DEPENDENCIES
|
45
|
-
bundler (~> 1
|
50
|
+
bundler (~> 2.1)
|
46
51
|
byebug
|
47
52
|
cli-kit!
|
48
53
|
method_source
|
49
54
|
minitest (>= 5.0.0)
|
50
55
|
minitest-reporters
|
51
|
-
mocha (~> 1.
|
52
|
-
rake (~>
|
53
|
-
rubocop
|
56
|
+
mocha (~> 1.13.0)
|
57
|
+
rake (~> 13.0)
|
58
|
+
rubocop
|
59
|
+
rubocop-shopify
|
54
60
|
|
55
61
|
BUNDLED WITH
|
56
|
-
|
62
|
+
2.2.24
|
data/Rakefile
CHANGED
@@ -1 +1,27 @@
|
|
1
|
-
|
1
|
+
$LOAD_PATH.unshift(File.expand_path('../lib', __FILE__))
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rubocop/rake_task'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
|
6
|
+
TEST_ROOT = File.expand_path('../test', __FILE__)
|
7
|
+
|
8
|
+
Rake::TestTask.new do |t|
|
9
|
+
t.libs += ['test']
|
10
|
+
t.test_files = FileList[File.join(TEST_ROOT, '**', '*_test.rb')]
|
11
|
+
t.verbose = false
|
12
|
+
t.warning = false
|
13
|
+
end
|
14
|
+
|
15
|
+
RuboCop::RakeTask.new(:style) do |t|
|
16
|
+
t.options = ['--display-cop-names']
|
17
|
+
end
|
18
|
+
|
19
|
+
task :test_gen_bundler do
|
20
|
+
sh 'DEPS=bundler bin/test_gen'
|
21
|
+
end
|
22
|
+
|
23
|
+
task :test_gen_vendor do
|
24
|
+
sh 'DEPS=vendor bin/test_gen'
|
25
|
+
end
|
26
|
+
|
27
|
+
task(default: [:test, :style, :test_gen_bundler, :test_gen_vendor])
|
data/bin/console
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'cli/kit'
|
5
5
|
|
6
6
|
# You can add fixtures and/or initialization code here to make experimenting
|
7
7
|
# with your gem easier. You can also use a different console, if you like.
|
@@ -10,5 +10,5 @@ require "cli/kit"
|
|
10
10
|
# require "pry"
|
11
11
|
# Pry.start
|
12
12
|
|
13
|
-
require
|
13
|
+
require 'irb'
|
14
14
|
IRB.start(__FILE__)
|
data/bin/test_gen
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
#!/usr/bin/env bash
|
2
|
+
set -euo pipefail
|
2
3
|
|
3
4
|
# Make sure we're in the cli kit directory
|
4
5
|
CURR_DIR=$(dirname "$0")
|
@@ -19,7 +20,9 @@ mv myapp ../
|
|
19
20
|
cd ../myapp
|
20
21
|
|
21
22
|
# Test
|
22
|
-
|
23
|
+
if [[ $DEPS == 'bundler' ]]; then
|
24
|
+
bundle install
|
25
|
+
fi
|
23
26
|
bin/testunit
|
24
27
|
|
25
28
|
if [[ $DEPS == 'vendor' ]]; then
|
data/bin/testunit
CHANGED
@@ -9,12 +9,12 @@ CLI_TEST_ROOT = root + '/test'
|
|
9
9
|
$LOAD_PATH.unshift(CLI_TEST_ROOT)
|
10
10
|
|
11
11
|
def test_files
|
12
|
-
Dir.glob(CLI_TEST_ROOT +
|
12
|
+
Dir.glob(CLI_TEST_ROOT + '/**/*_test.rb')
|
13
13
|
end
|
14
14
|
|
15
15
|
if ARGV.empty?
|
16
16
|
test_files.each { |f| require(f) }
|
17
|
-
exit
|
17
|
+
exit(0)
|
18
18
|
end
|
19
19
|
|
20
20
|
# A list of files is presumed to be specified
|
data/cli-kit.gemspec
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
# coding: utf-8
|
2
|
+
|
2
3
|
lib = File.expand_path('../lib', __FILE__)
|
3
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
5
|
require 'cli/kit/version'
|
@@ -14,16 +15,16 @@ Gem::Specification.new do |spec|
|
|
14
15
|
spec.homepage = 'https://github.com/shopify/cli-kit'
|
15
16
|
spec.license = 'MIT'
|
16
17
|
|
17
|
-
spec.files =
|
18
|
+
spec.files = %x(git ls-files -z).split("\x0").reject do |f|
|
18
19
|
f.match(%r{^(test|spec|features)/})
|
19
20
|
end
|
20
21
|
spec.bindir = 'exe'
|
21
22
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
23
|
spec.require_paths = ['lib']
|
23
24
|
|
24
|
-
spec.add_runtime_dependency
|
25
|
+
spec.add_runtime_dependency('cli-ui', '>= 1.1.4')
|
25
26
|
|
26
|
-
spec.add_development_dependency
|
27
|
-
spec.add_development_dependency
|
28
|
-
spec.add_development_dependency
|
27
|
+
spec.add_development_dependency('bundler', '~> 2.1')
|
28
|
+
spec.add_development_dependency('minitest', '~> 5.0')
|
29
|
+
spec.add_development_dependency('rake', '~> 13.0')
|
29
30
|
end
|
data/dev.yml
CHANGED
data/examples/minimal/example.rb
CHANGED
@@ -5,12 +5,12 @@ require 'cli/kit'
|
|
5
5
|
|
6
6
|
CLI::UI::StdoutRouter.enable
|
7
7
|
|
8
|
-
include
|
8
|
+
include(CLI::Kit)
|
9
9
|
|
10
10
|
registry = CommandRegistry.new(default: 'hello', contextual_resolver: nil)
|
11
11
|
registry.add(Class.new(BaseCommand) do
|
12
12
|
def call(_args, _name)
|
13
|
-
puts
|
13
|
+
puts 'hello, world!'
|
14
14
|
end
|
15
15
|
end, 'hello')
|
16
16
|
|
@@ -4,15 +4,15 @@ module Gen
|
|
4
4
|
module Commands
|
5
5
|
class Help < Gen::Command
|
6
6
|
def call(_args, _name)
|
7
|
-
puts CLI::UI.fmt(
|
8
|
-
puts
|
7
|
+
puts CLI::UI.fmt('{{bold:Available commands}}')
|
8
|
+
puts ''
|
9
9
|
|
10
10
|
Gen::Commands::Registry.resolved_commands.each do |name, klass|
|
11
11
|
puts CLI::UI.fmt("{{command:#{Gen::TOOL_NAME} #{name}}}")
|
12
|
-
if klass.respond_to?(:help) && help = klass.help
|
12
|
+
if klass.respond_to?(:help) && (help = klass.help)
|
13
13
|
puts CLI::UI.fmt(help)
|
14
14
|
end
|
15
|
-
puts
|
15
|
+
puts ''
|
16
16
|
end
|
17
17
|
end
|
18
18
|
end
|
data/gen/lib/gen/generator.rb
CHANGED
@@ -18,20 +18,20 @@ module Gen
|
|
18
18
|
# false -> delete file
|
19
19
|
# string -> rename file before applying template substitutions
|
20
20
|
VENDOR_TRANSLATIONS = {
|
21
|
-
'Gemfile'
|
22
|
-
'exe/__app__-gems'
|
21
|
+
'Gemfile' => false,
|
22
|
+
'exe/__app__-gems' => false,
|
23
23
|
'exe/__app__-vendor' => 'exe/__app__',
|
24
|
-
'dev-gems.yml'
|
25
|
-
'dev-vendor.yml'
|
24
|
+
'dev-gems.yml' => false,
|
25
|
+
'dev-vendor.yml' => 'dev.yml',
|
26
26
|
}.freeze
|
27
27
|
private_constant :VENDOR_TRANSLATIONS
|
28
28
|
|
29
29
|
BUNDLER_TRANSLATIONS = {
|
30
|
-
'bin/update-deps'
|
31
|
-
'exe/__app__-gems'
|
30
|
+
'bin/update-deps' => false,
|
31
|
+
'exe/__app__-gems' => 'exe/__app__',
|
32
32
|
'exe/__app__-vendor' => false,
|
33
|
-
'dev-gems.yml'
|
34
|
-
'dev-vendor.yml'
|
33
|
+
'dev-gems.yml' => 'dev.yml',
|
34
|
+
'dev-vendor.yml' => false,
|
35
35
|
}.freeze
|
36
36
|
private_constant :BUNDLER_TRANSLATIONS
|
37
37
|
|
@@ -58,8 +58,8 @@ module Gen
|
|
58
58
|
private
|
59
59
|
|
60
60
|
def ask_vendor?
|
61
|
-
return
|
62
|
-
return
|
61
|
+
return true if ENV['DEPS'] == 'vendor'
|
62
|
+
return false if ENV['DEPS'] == 'bundler'
|
63
63
|
|
64
64
|
vendor = nil
|
65
65
|
CLI::UI::Frame.open('Configuration') do
|
@@ -116,7 +116,7 @@ module Gen
|
|
116
116
|
out, stat = Open3.capture2e('git', '-C', dir, 'clone', "https://github.com/shopify/#{repo}")
|
117
117
|
unless stat.success?
|
118
118
|
STDERR.puts(out)
|
119
|
-
error(
|
119
|
+
error('git clone failed')
|
120
120
|
end
|
121
121
|
end
|
122
122
|
|
data/lib/cli/kit/base_command.rb
CHANGED
@@ -19,13 +19,13 @@ module CLI
|
|
19
19
|
cmd = new
|
20
20
|
stats_tags = cmd.stats_tags(args, command_name)
|
21
21
|
begin
|
22
|
-
statsd_increment(
|
23
|
-
statsd_time(
|
22
|
+
statsd_increment('cli.command.invoked', tags: stats_tags)
|
23
|
+
statsd_time('cli.command.time', tags: stats_tags) do
|
24
24
|
cmd.call(args, command_name)
|
25
25
|
end
|
26
|
-
statsd_increment(
|
26
|
+
statsd_increment('cli.command.success', tags: stats_tags)
|
27
27
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
28
|
-
statsd_increment(
|
28
|
+
statsd_increment('cli.command.exception', tags: stats_tags + ["exception:#{e.class}"])
|
29
29
|
raise e
|
30
30
|
end
|
31
31
|
end
|
@@ -38,7 +38,7 @@ module CLI
|
|
38
38
|
end
|
39
39
|
|
40
40
|
def call(_args, _command_name)
|
41
|
-
raise NotImplementedError
|
41
|
+
raise NotImplementedError, "#{self.class.name} must implement #{__method__}"
|
42
42
|
end
|
43
43
|
|
44
44
|
def has_subcommands?
|
data/lib/cli/kit/config.rb
CHANGED
@@ -18,22 +18,24 @@ module CLI
|
|
18
18
|
# `name` : the name of the config value you are looking for
|
19
19
|
#
|
20
20
|
# #### Returns
|
21
|
-
# `value` : the value of the config variable (
|
21
|
+
# `value` : the value of the config variable (nil if none)
|
22
22
|
#
|
23
23
|
# #### Example Usage
|
24
24
|
# `config.get('name.of.config')`
|
25
25
|
#
|
26
|
-
def get(section, name, default:
|
26
|
+
def get(section, name, default: nil)
|
27
27
|
all_configs.dig("[#{section}]", name) || default
|
28
28
|
end
|
29
29
|
|
30
30
|
# Coalesce and enforce the value of a config to a boolean
|
31
31
|
def get_bool(section, name, default: false)
|
32
|
-
case get(section, name, default: default)
|
33
|
-
when
|
32
|
+
case get(section, name, default: default)
|
33
|
+
when 'true'
|
34
34
|
true
|
35
|
-
when
|
35
|
+
when 'false'
|
36
36
|
false
|
37
|
+
when default
|
38
|
+
default
|
37
39
|
else
|
38
40
|
raise CLI::Kit::Abort, "Invalid config: #{section}.#{name} is expected to be true or false"
|
39
41
|
end
|
@@ -116,7 +118,7 @@ module CLI
|
|
116
118
|
|
117
119
|
def ini
|
118
120
|
@ini ||= CLI::Kit::Ini
|
119
|
-
.new(file, default_section:
|
121
|
+
.new(file, default_section: '[global]', convert_types: false)
|
120
122
|
.tap(&:parse)
|
121
123
|
end
|
122
124
|
|
@@ -22,7 +22,7 @@ module CLI
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def handle_exception(error)
|
25
|
-
if notify_with = exception_for_submission(error)
|
25
|
+
if (notify_with = exception_for_submission(error))
|
26
26
|
logs = begin
|
27
27
|
File.read(@log_file)
|
28
28
|
rescue => e
|
@@ -46,7 +46,7 @@ module CLI
|
|
46
46
|
when CLI::Kit::Abort, CLI::Kit::AbortSilent # Not a bug
|
47
47
|
nil
|
48
48
|
when SignalException
|
49
|
-
skip =
|
49
|
+
skip = ['SIGTERM', 'SIGHUP', 'SIGINT']
|
50
50
|
skip.include?(error.message) ? nil : error
|
51
51
|
when SystemExit # "exit N" called
|
52
52
|
case error.status
|
@@ -56,7 +56,7 @@ module CLI
|
|
56
56
|
# if it was `exit 30`, translate the exit code to 1, and submit nothing.
|
57
57
|
# 30 is used to signal normal failures that are not indicative of bugs.
|
58
58
|
# However, users should see it presented as 1.
|
59
|
-
exit
|
59
|
+
exit(1)
|
60
60
|
else
|
61
61
|
# A weird termination status happened. `error.exception "message"` will maintain backtrace
|
62
62
|
# but allow us to set a message
|
@@ -83,18 +83,24 @@ module CLI
|
|
83
83
|
|
84
84
|
CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
85
85
|
rescue Interrupt
|
86
|
-
|
86
|
+
stderr_puts_message('Interrupt')
|
87
87
|
CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
88
88
|
rescue Errno::ENOSPC
|
89
89
|
message = if @tool_name
|
90
90
|
"Your disk is full - {{command:#{@tool_name}}} requires free space to operate"
|
91
91
|
else
|
92
|
-
|
92
|
+
'Your disk is full - free space is required to operate'
|
93
93
|
end
|
94
|
-
|
94
|
+
stderr_puts_message(message)
|
95
95
|
CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
|
96
96
|
end
|
97
97
|
|
98
|
+
def stderr_puts_message(message)
|
99
|
+
$stderr.puts(format_error_message(message))
|
100
|
+
rescue Errno::EPIPE
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
|
98
104
|
def exception_reporter
|
99
105
|
if @exception_reporter_or_proc.respond_to?(:report)
|
100
106
|
@exception_reporter_or_proc
|
data/lib/cli/kit/executor.rb
CHANGED
@@ -13,19 +13,17 @@ module CLI
|
|
13
13
|
def call(command, command_name, args)
|
14
14
|
with_traps do
|
15
15
|
with_logging do |id|
|
16
|
+
command.call(args, command_name)
|
17
|
+
rescue => e
|
16
18
|
begin
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
# Outputting to stderr is best-effort. Avoid raising another error when outputting debug info so that
|
24
|
-
# we can detect and log the original error, which may even be the source of this error.
|
25
|
-
nil
|
26
|
-
end
|
27
|
-
raise e
|
19
|
+
$stderr.puts "This command ran with ID: #{id}"
|
20
|
+
$stderr.puts 'Please include this information in any issues/report along with relevant logs'
|
21
|
+
rescue SystemCallError
|
22
|
+
# Outputting to stderr is best-effort. Avoid raising another error when outputting debug info so that
|
23
|
+
# we can detect and log the original error, which may even be the source of this error.
|
24
|
+
nil
|
28
25
|
end
|
26
|
+
raise e
|
29
27
|
end
|
30
28
|
end
|
31
29
|
end
|
@@ -41,11 +39,9 @@ module CLI
|
|
41
39
|
end
|
42
40
|
end
|
43
41
|
|
44
|
-
def with_traps
|
42
|
+
def with_traps(&block)
|
45
43
|
twrap('QUIT', method(:quit_handler)) do
|
46
|
-
twrap('INFO', method(:info_handler))
|
47
|
-
yield
|
48
|
-
end
|
44
|
+
twrap('INFO', method(:info_handler), &block)
|
49
45
|
end
|
50
46
|
end
|
51
47
|
|
@@ -53,10 +49,17 @@ module CLI
|
|
53
49
|
return yield unless Signal.list.key?(signal)
|
54
50
|
|
55
51
|
begin
|
56
|
-
|
52
|
+
begin
|
53
|
+
prev_handler = trap(signal, handler)
|
54
|
+
installed = true
|
55
|
+
rescue ArgumentError
|
56
|
+
# If we couldn't install a signal handler because the signal is
|
57
|
+
# reserved, remember not to uninstall it later.
|
58
|
+
installed = false
|
59
|
+
end
|
57
60
|
yield
|
58
61
|
ensure
|
59
|
-
trap(signal, prev_handler)
|
62
|
+
trap(signal, prev_handler) if installed
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
data/lib/cli/kit/ini.rb
CHANGED
@@ -61,11 +61,11 @@ module CLI
|
|
61
61
|
private
|
62
62
|
|
63
63
|
def to_ini(h, git_format: false)
|
64
|
-
optional_tab = git_format ? "\t" :
|
64
|
+
optional_tab = git_format ? "\t" : ''
|
65
65
|
str = []
|
66
66
|
h.each do |k, v|
|
67
67
|
if section_designator?(k)
|
68
|
-
str <<
|
68
|
+
str << '' unless str.empty? || git_format
|
69
69
|
str << k
|
70
70
|
str << to_ini(v, git_format: git_format)
|
71
71
|
else
|
data/lib/cli/kit/logger.rb
CHANGED
@@ -10,9 +10,10 @@ module CLI
|
|
10
10
|
# Constructor for CLI::Kit::Logger
|
11
11
|
#
|
12
12
|
# @param debug_log_file [String] path to the file where debug logs should be stored
|
13
|
-
def initialize(debug_log_file:)
|
13
|
+
def initialize(debug_log_file:, env_debug_name: 'DEBUG')
|
14
14
|
FileUtils.mkpath(File.dirname(debug_log_file))
|
15
15
|
@debug_logger = ::Logger.new(debug_log_file, MAX_NUM_LOGS, MAX_LOG_SIZE)
|
16
|
+
@env_debug_name = env_debug_name
|
16
17
|
end
|
17
18
|
|
18
19
|
# Functionally equivalent to Logger#info
|
@@ -60,7 +61,7 @@ module CLI
|
|
60
61
|
#
|
61
62
|
# @param msg [String] the message to log
|
62
63
|
def debug(msg)
|
63
|
-
$stdout.puts CLI::UI.fmt(msg) if
|
64
|
+
$stdout.puts CLI::UI.fmt(msg) if debug?
|
64
65
|
@debug_logger.debug(format_debug(msg))
|
65
66
|
end
|
66
67
|
|
@@ -71,6 +72,11 @@ module CLI
|
|
71
72
|
return msg unless CLI::UI::StdoutRouter.current_id
|
72
73
|
"[#{CLI::UI::StdoutRouter.current_id[:id]}] #{msg}"
|
73
74
|
end
|
75
|
+
|
76
|
+
def debug?
|
77
|
+
val = ENV[@env_debug_name]
|
78
|
+
val && val != '0' && val != ''
|
79
|
+
end
|
74
80
|
end
|
75
81
|
end
|
76
82
|
end
|
data/lib/cli/kit/resolver.rb
CHANGED
@@ -25,7 +25,7 @@ module CLI
|
|
25
25
|
private
|
26
26
|
|
27
27
|
def command_not_found(name)
|
28
|
-
CLI::UI::Frame.open(
|
28
|
+
CLI::UI::Frame.open('Command not found', color: :red, timing: false) do
|
29
29
|
$stderr.puts(CLI::UI.fmt("{{command:#{@tool_name} #{name}}} was not found"))
|
30
30
|
end
|
31
31
|
|
@@ -43,7 +43,7 @@ module CLI
|
|
43
43
|
|
44
44
|
# If we have any matches left, tell the user
|
45
45
|
if possible_matches.any?
|
46
|
-
CLI::UI::Frame.open(
|
46
|
+
CLI::UI::Frame.open('{{bold:Did you mean?}}', timing: false, color: :blue) do
|
47
47
|
possible_matches.each do |possible_match|
|
48
48
|
$stderr.puts CLI::UI.fmt("{{command:#{@tool_name} #{possible_match}}}")
|
49
49
|
end
|
@@ -10,7 +10,7 @@ module CLI
|
|
10
10
|
def assert_all_commands_run(should_raise: true)
|
11
11
|
errors = CLI::Kit::System.error_message
|
12
12
|
CLI::Kit::System.reset!
|
13
|
-
assert
|
13
|
+
assert(false, errors) if should_raise && !errors.nil?
|
14
14
|
errors
|
15
15
|
end
|
16
16
|
|
@@ -134,8 +134,8 @@ module CLI
|
|
134
134
|
#
|
135
135
|
# Note: Must set allow or success
|
136
136
|
#
|
137
|
-
def fake(*a, stdout:
|
138
|
-
raise ArgumentError,
|
137
|
+
def fake(*a, stdout: '', stderr: '', allow: nil, success: nil, sudo: false, env: {})
|
138
|
+
raise ArgumentError, 'success or allow must be set' if success.nil? && allow.nil?
|
139
139
|
|
140
140
|
@delegate_open3 ||= {}
|
141
141
|
@delegate_open3[a.join(' ')] = {
|
@@ -196,22 +196,22 @@ module CLI
|
|
196
196
|
|
197
197
|
unless errors[:unexpected].empty?
|
198
198
|
final_error << CLI::UI.fmt(<<~EOF)
|
199
|
-
|
200
|
-
|
199
|
+
{{bold:Unexpected command invocations:}}
|
200
|
+
{{command:#{errors[:unexpected].join("\n")}}}
|
201
201
|
EOF
|
202
202
|
end
|
203
203
|
|
204
204
|
unless errors[:not_run].empty?
|
205
205
|
final_error << CLI::UI.fmt(<<~EOF)
|
206
|
-
|
207
|
-
|
206
|
+
{{bold:Expected commands were not run:}}
|
207
|
+
{{command:#{errors[:not_run].join("\n")}}}
|
208
208
|
EOF
|
209
209
|
end
|
210
210
|
|
211
211
|
unless errors[:other].empty?
|
212
212
|
final_error << CLI::UI.fmt(<<~EOF)
|
213
|
-
|
214
|
-
|
213
|
+
{{bold:Commands were not run as expected:}}
|
214
|
+
#{errors[:other].map { |cmd, msg| "{{command:#{cmd}}}\n#{msg}" }.join("\n\n")}
|
215
215
|
EOF
|
216
216
|
end
|
217
217
|
|
data/lib/cli/kit/system.rb
CHANGED
@@ -6,7 +6,7 @@ require 'English'
|
|
6
6
|
module CLI
|
7
7
|
module Kit
|
8
8
|
module System
|
9
|
-
SUDO_PROMPT = CLI::UI.fmt(
|
9
|
+
SUDO_PROMPT = CLI::UI.fmt('{{info:(sudo)}} Password: ')
|
10
10
|
class << self
|
11
11
|
# Ask for sudo access with a message explaning the need for it
|
12
12
|
# Will make subsequent commands capable of running with sudo for a period of time
|
@@ -19,7 +19,7 @@ module CLI
|
|
19
19
|
#
|
20
20
|
def sudo_reason(msg)
|
21
21
|
# See if sudo has a cached password
|
22
|
-
|
22
|
+
%x(env SUDO_ASKPASS=/usr/bin/false sudo -A true)
|
23
23
|
return if $CHILD_STATUS.success?
|
24
24
|
CLI::UI.with_frame_color(:blue) do
|
25
25
|
puts(CLI::UI.fmt("{{i}} #{msg}"))
|
@@ -90,6 +90,18 @@ module CLI
|
|
90
90
|
delegate_open3(*a, sudo: sudo, env: env, method: :capture3, **kwargs)
|
91
91
|
end
|
92
92
|
|
93
|
+
def popen2(*a, sudo: false, env: ENV, **kwargs, &block)
|
94
|
+
delegate_open3(*a, sudo: sudo, env: env, method: :popen2, **kwargs, &block)
|
95
|
+
end
|
96
|
+
|
97
|
+
def popen2e(*a, sudo: false, env: ENV, **kwargs, &block)
|
98
|
+
delegate_open3(*a, sudo: sudo, env: env, method: :popen2e, **kwargs, &block)
|
99
|
+
end
|
100
|
+
|
101
|
+
def popen3(*a, sudo: false, env: ENV, **kwargs, &block)
|
102
|
+
delegate_open3(*a, sudo: sudo, env: env, method: :popen3, **kwargs, &block)
|
103
|
+
end
|
104
|
+
|
93
105
|
# Execute a command in the user's environment
|
94
106
|
# Outputs result of the command without capturing it
|
95
107
|
#
|
@@ -100,7 +112,7 @@ module CLI
|
|
100
112
|
# - `**kwargs`: additional keyword arguments to pass to Process.spawn
|
101
113
|
#
|
102
114
|
# #### Returns
|
103
|
-
# - `status`:
|
115
|
+
# - `status`: The `Process:Status` result for the command execution
|
104
116
|
#
|
105
117
|
# #### Usage
|
106
118
|
# `stat = CLI::Kit::System.system('ls', 'a_folder')`
|
@@ -116,11 +128,15 @@ module CLI
|
|
116
128
|
err_w.close
|
117
129
|
|
118
130
|
handlers = if block_given?
|
119
|
-
{
|
120
|
-
|
131
|
+
{
|
132
|
+
out_r => ->(data) { yield(data.force_encoding(Encoding::UTF_8), '') },
|
133
|
+
err_r => ->(data) { yield('', data.force_encoding(Encoding::UTF_8)) },
|
134
|
+
}
|
121
135
|
else
|
122
|
-
{
|
123
|
-
|
136
|
+
{
|
137
|
+
out_r => ->(data) { STDOUT.write(data) },
|
138
|
+
err_r => ->(data) { STDOUT.write(data) },
|
139
|
+
}
|
124
140
|
end
|
125
141
|
|
126
142
|
previous_trailing = Hash.new('')
|
@@ -130,13 +146,11 @@ module CLI
|
|
130
146
|
|
131
147
|
readers, = IO.select(ios)
|
132
148
|
readers.each do |io|
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
io.close
|
139
|
-
end
|
149
|
+
data, trailing = split_partial_characters(io.readpartial(4096))
|
150
|
+
handlers[io].call(previous_trailing[io] + data)
|
151
|
+
previous_trailing[io] = trailing
|
152
|
+
rescue IOError
|
153
|
+
io.close
|
140
154
|
end
|
141
155
|
end
|
142
156
|
|
@@ -163,6 +177,14 @@ module CLI
|
|
163
177
|
[data.byteslice(0...partial_character_index), data.byteslice(partial_character_index..-1)]
|
164
178
|
end
|
165
179
|
|
180
|
+
def os
|
181
|
+
return :mac if /darwin/.match(RUBY_PLATFORM)
|
182
|
+
return :linux if /linux/.match(RUBY_PLATFORM)
|
183
|
+
return :windows if /mingw32/.match(RUBY_PLATFORM)
|
184
|
+
|
185
|
+
raise "Could not determine OS from platform #{RUBY_PLATFORM}"
|
186
|
+
end
|
187
|
+
|
166
188
|
private
|
167
189
|
|
168
190
|
def apply_sudo(*a, sudo)
|
@@ -171,11 +193,11 @@ module CLI
|
|
171
193
|
a
|
172
194
|
end
|
173
195
|
|
174
|
-
def delegate_open3(*a, sudo: raise, env: raise, method: raise, **kwargs)
|
196
|
+
def delegate_open3(*a, sudo: raise, env: raise, method: raise, **kwargs, &block)
|
175
197
|
a = apply_sudo(*a, sudo)
|
176
|
-
Open3.send(method, env, *resolve_path(a, env), **kwargs)
|
198
|
+
Open3.send(method, env, *resolve_path(a, env), **kwargs, &block)
|
177
199
|
rescue Errno::EINTR
|
178
|
-
raise(Errno::EINTR, "command interrupted: #{a.join(
|
200
|
+
raise(Errno::EINTR, "command interrupted: #{a.join(" ")}")
|
179
201
|
end
|
180
202
|
|
181
203
|
# Ruby resolves the program to execute using its own PATH, but we want it to
|
@@ -189,17 +211,30 @@ module CLI
|
|
189
211
|
# See https://github.com/Shopify/dev/pull/625 for more details.
|
190
212
|
def resolve_path(a, env)
|
191
213
|
# If only one argument was provided, make sure it's interpreted by a shell.
|
192
|
-
|
214
|
+
if a.size == 1
|
215
|
+
if os == :windows
|
216
|
+
return ['break && ' + a[0]]
|
217
|
+
else
|
218
|
+
return ['true ; ' + a[0]]
|
219
|
+
end
|
220
|
+
end
|
193
221
|
return a if a.first.include?('/')
|
194
222
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
223
|
+
item = which(a.first, env)
|
224
|
+
a[0] = item if item
|
225
|
+
a
|
226
|
+
end
|
227
|
+
|
228
|
+
def which(cmd, env)
|
229
|
+
exts = os == :windows ? env.fetch('PATHEXT').split(';') : ['']
|
230
|
+
env.fetch('PATH', '').split(File::PATH_SEPARATOR).each do |path|
|
231
|
+
exts.each do |ext|
|
232
|
+
exe = File.join(path, "#{cmd}#{ext}")
|
233
|
+
return exe if File.executable?(exe) && !File.directory?(exe)
|
234
|
+
end
|
199
235
|
end
|
200
236
|
|
201
|
-
|
202
|
-
a
|
237
|
+
nil
|
203
238
|
end
|
204
239
|
end
|
205
240
|
end
|
data/lib/cli/kit/util.rb
CHANGED
@@ -2,7 +2,7 @@ module CLI
|
|
2
2
|
module Kit
|
3
3
|
module Util
|
4
4
|
class << self
|
5
|
-
def snake_case(camel_case, seperator =
|
5
|
+
def snake_case(camel_case, seperator = '_')
|
6
6
|
camel_case.to_s # MyCoolThing::MyAPIModule
|
7
7
|
.gsub(/::/, '/') # MyCoolThing/MyAPIModule
|
8
8
|
.gsub(/([A-Z]+)([A-Z][a-z])/, "\\1#{seperator}\\2") # MyCoolThing::MyAPI_Module
|
@@ -43,15 +43,15 @@ module CLI
|
|
43
43
|
# non-empty line in the whole string
|
44
44
|
#
|
45
45
|
def strip_heredoc(str)
|
46
|
-
str.gsub(/^#{str.scan(/^[ \t]*(?=\S)/).min}/,
|
46
|
+
str.gsub(/^#{str.scan(/^[ \t]*(?=\S)/).min}/, ''.freeze)
|
47
47
|
end
|
48
48
|
|
49
49
|
# Joins an array with commas and "and", using the Oxford comma.
|
50
50
|
def english_join(array)
|
51
|
-
return
|
52
|
-
return array.join(
|
51
|
+
return '' if array.nil?
|
52
|
+
return array.join(' and ') if array.length < 3
|
53
53
|
|
54
|
-
"#{array[0..-2].join(
|
54
|
+
"#{array[0..-2].join(", ")}, and #{array[-1]}"
|
55
55
|
end
|
56
56
|
|
57
57
|
# Execute a block within the context of a variable enviroment
|
@@ -77,15 +77,15 @@ module CLI
|
|
77
77
|
# Converts a number to a human readable format on the SI scale
|
78
78
|
#
|
79
79
|
def to_si_scale(number, unit = '', factor: 1000, precision: 2, space: false)
|
80
|
-
raise ArgumentError,
|
80
|
+
raise ArgumentError, 'factor should only be 1000 or 1024' unless [1000, 1024].include?(factor)
|
81
81
|
|
82
|
-
small_scale =
|
83
|
-
big_scale =
|
82
|
+
small_scale = ['m', 'µ', 'n', 'p', 'f', 'a', 'z', 'y']
|
83
|
+
big_scale = ['k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']
|
84
84
|
negative = number < 0
|
85
85
|
number = number.abs.to_f
|
86
86
|
|
87
87
|
if number == 0 || number.between?(1, factor)
|
88
|
-
prefix =
|
88
|
+
prefix = ''
|
89
89
|
scale = 0
|
90
90
|
else
|
91
91
|
scale = Math.log(number, factor).floor
|
@@ -107,7 +107,7 @@ module CLI
|
|
107
107
|
fnum = fnum.to_i if (fnum.to_i.to_f * divider) == number
|
108
108
|
|
109
109
|
fnum = -fnum if negative
|
110
|
-
prefix =
|
110
|
+
prefix = ' ' + prefix if space
|
111
111
|
|
112
112
|
"#{fnum}#{prefix}#{unit}"
|
113
113
|
end
|
data/lib/cli/kit/version.rb
CHANGED
data/lib/cli/kit.rb
CHANGED
@@ -51,7 +51,7 @@ module CLI
|
|
51
51
|
# 1. rescue Abort or Bug
|
52
52
|
# 2. Print a contextualized error message
|
53
53
|
# 3. Re-raise AbortSilent or BugSilent respectively.
|
54
|
-
GenericAbort = Class.new(Exception)
|
54
|
+
GenericAbort = Class.new(Exception) # rubocop:disable Lint/InheritException
|
55
55
|
Abort = Class.new(GenericAbort)
|
56
56
|
Bug = Class.new(GenericAbort)
|
57
57
|
BugSilent = Class.new(GenericAbort)
|
metadata
CHANGED
@@ -1,16 +1,16 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cli-kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 4.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Burke Libbey
|
8
8
|
- Aaron Olson
|
9
9
|
- Lisa Ugray
|
10
|
-
autorequire:
|
10
|
+
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date:
|
13
|
+
date: 2021-11-10 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: cli-ui
|
@@ -32,42 +32,42 @@ dependencies:
|
|
32
32
|
requirements:
|
33
33
|
- - "~>"
|
34
34
|
- !ruby/object:Gem::Version
|
35
|
-
version: '1
|
35
|
+
version: '2.1'
|
36
36
|
type: :development
|
37
37
|
prerelease: false
|
38
38
|
version_requirements: !ruby/object:Gem::Requirement
|
39
39
|
requirements:
|
40
40
|
- - "~>"
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: '1
|
42
|
+
version: '2.1'
|
43
43
|
- !ruby/object:Gem::Dependency
|
44
|
-
name:
|
44
|
+
name: minitest
|
45
45
|
requirement: !ruby/object:Gem::Requirement
|
46
46
|
requirements:
|
47
47
|
- - "~>"
|
48
48
|
- !ruby/object:Gem::Version
|
49
|
-
version: '
|
49
|
+
version: '5.0'
|
50
50
|
type: :development
|
51
51
|
prerelease: false
|
52
52
|
version_requirements: !ruby/object:Gem::Requirement
|
53
53
|
requirements:
|
54
54
|
- - "~>"
|
55
55
|
- !ruby/object:Gem::Version
|
56
|
-
version: '
|
56
|
+
version: '5.0'
|
57
57
|
- !ruby/object:Gem::Dependency
|
58
|
-
name:
|
58
|
+
name: rake
|
59
59
|
requirement: !ruby/object:Gem::Requirement
|
60
60
|
requirements:
|
61
61
|
- - "~>"
|
62
62
|
- !ruby/object:Gem::Version
|
63
|
-
version: '
|
63
|
+
version: '13.0'
|
64
64
|
type: :development
|
65
65
|
prerelease: false
|
66
66
|
version_requirements: !ruby/object:Gem::Requirement
|
67
67
|
requirements:
|
68
68
|
- - "~>"
|
69
69
|
- !ruby/object:Gem::Version
|
70
|
-
version: '
|
70
|
+
version: '13.0'
|
71
71
|
description: Terminal UI framework extensions
|
72
72
|
email:
|
73
73
|
- burke.libbey@shopify.com
|
@@ -79,10 +79,11 @@ extensions: []
|
|
79
79
|
extra_rdoc_files: []
|
80
80
|
files:
|
81
81
|
- ".github/CODEOWNERS"
|
82
|
+
- ".github/dependabot.yml"
|
82
83
|
- ".github/probots.yml"
|
84
|
+
- ".github/workflows/ruby.yml"
|
83
85
|
- ".gitignore"
|
84
86
|
- ".rubocop.yml"
|
85
|
-
- ".travis.yml"
|
86
87
|
- Gemfile
|
87
88
|
- Gemfile.lock
|
88
89
|
- LICENSE.txt
|
@@ -142,7 +143,7 @@ homepage: https://github.com/shopify/cli-kit
|
|
142
143
|
licenses:
|
143
144
|
- MIT
|
144
145
|
metadata: {}
|
145
|
-
post_install_message:
|
146
|
+
post_install_message:
|
146
147
|
rdoc_options: []
|
147
148
|
require_paths:
|
148
149
|
- lib
|
@@ -157,8 +158,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
158
|
- !ruby/object:Gem::Version
|
158
159
|
version: '0'
|
159
160
|
requirements: []
|
160
|
-
rubygems_version: 3.
|
161
|
-
signing_key:
|
161
|
+
rubygems_version: 3.2.20
|
162
|
+
signing_key:
|
162
163
|
specification_version: 4
|
163
164
|
summary: Terminal UI framework extensions
|
164
165
|
test_files: []
|
data/.travis.yml
DELETED