cli-kit 3.3.0 → 4.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.
- 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