shopify-cli 1.0.3 → 1.1.2

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 (118) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CONTRIBUTING.md +1 -1
  3. data/.travis.yml +3 -2
  4. data/CHANGELOG.md +20 -0
  5. data/Gemfile +1 -1
  6. data/Gemfile.lock +13 -13
  7. data/bin/load_shopify.rb +3 -1
  8. data/bin/shopify +2 -0
  9. data/docs/Gemfile.lock +23 -13
  10. data/docs/getting-started/index.md +3 -2
  11. data/docs/getting-started/install/index.md +55 -9
  12. data/docs/getting-started/uninstall/index.md +1 -1
  13. data/docs/getting-started/upgrade/index.md +8 -4
  14. data/ext/shopify-cli/extconf.rb +40 -20
  15. data/lib/project_types/extension/models/type.rb +1 -0
  16. data/lib/project_types/extension/tasks/create_extension.rb +1 -1
  17. data/lib/project_types/extension/tasks/get_app.rb +1 -1
  18. data/lib/project_types/extension/tasks/update_draft.rb +1 -1
  19. data/lib/project_types/node/commands/create.rb +4 -4
  20. data/lib/project_types/node/commands/deploy/heroku.rb +6 -1
  21. data/lib/project_types/node/commands/generate/billing.rb +7 -5
  22. data/lib/project_types/node/commands/generate/page.rb +9 -5
  23. data/lib/project_types/node/commands/generate/webhook.rb +5 -1
  24. data/lib/project_types/node/commands/serve.rb +5 -5
  25. data/lib/project_types/node/messages/messages.rb +5 -1
  26. data/lib/project_types/rails/commands/create.rb +56 -5
  27. data/lib/project_types/rails/commands/generate.rb +1 -0
  28. data/lib/project_types/rails/commands/generate/webhook.rb +3 -2
  29. data/lib/project_types/rails/commands/serve.rb +11 -7
  30. data/lib/project_types/rails/gem.rb +61 -6
  31. data/lib/project_types/rails/messages/messages.rb +32 -12
  32. data/lib/project_types/script/commands/create.rb +1 -5
  33. data/lib/project_types/script/commands/disable.rb +1 -2
  34. data/lib/project_types/script/commands/enable.rb +5 -5
  35. data/lib/project_types/script/commands/push.rb +1 -6
  36. data/lib/project_types/script/config/extension_points.yml +4 -4
  37. data/lib/project_types/script/errors.rb +8 -0
  38. data/lib/project_types/script/forms/create.rb +1 -1
  39. data/lib/project_types/script/layers/domain/errors.rb +2 -0
  40. data/lib/project_types/script/layers/infrastructure/errors.rb +1 -0
  41. data/lib/project_types/script/layers/infrastructure/script_service.rb +6 -2
  42. data/lib/project_types/script/messages/messages.rb +19 -48
  43. data/lib/project_types/script/ui/error_handler.rb +4 -0
  44. data/lib/rubygems_plugin.rb +18 -10
  45. data/lib/shopify-cli/admin_api/populate_resource_command.rb +1 -1
  46. data/lib/shopify-cli/command.rb +12 -6
  47. data/lib/shopify-cli/commands/connect.rb +7 -75
  48. data/lib/shopify-cli/commands/create.rb +1 -1
  49. data/lib/shopify-cli/commands/system.rb +21 -12
  50. data/lib/shopify-cli/context.rb +68 -0
  51. data/lib/shopify-cli/core/entry_point.rb +4 -1
  52. data/lib/shopify-cli/core/executor.rb +3 -5
  53. data/lib/shopify-cli/core/monorail.rb +1 -1
  54. data/lib/shopify-cli/db.rb +1 -1
  55. data/lib/shopify-cli/git.rb +1 -1
  56. data/lib/shopify-cli/heroku.rb +21 -5
  57. data/lib/shopify-cli/js_deps.rb +2 -2
  58. data/lib/shopify-cli/js_system.rb +2 -2
  59. data/lib/shopify-cli/messages/messages.rb +27 -20
  60. data/lib/shopify-cli/process_supervision.rb +60 -21
  61. data/lib/shopify-cli/project.rb +14 -6
  62. data/lib/shopify-cli/project_type.rb +3 -2
  63. data/lib/shopify-cli/sub_command.rb +1 -0
  64. data/lib/shopify-cli/task.rb +2 -2
  65. data/lib/shopify-cli/tasks.rb +11 -4
  66. data/lib/shopify-cli/tasks/ensure_env.rb +74 -17
  67. data/lib/shopify-cli/tunnel.rb +22 -7
  68. data/lib/shopify-cli/version.rb +1 -1
  69. data/lib/shopify_cli.rb +35 -9
  70. data/shopify-cli.gemspec +4 -1
  71. data/vendor/deps/cli-kit/REVISION +1 -1
  72. data/vendor/deps/cli-kit/lib/cli/kit.rb +1 -1
  73. data/vendor/deps/cli-kit/lib/cli/kit/autocall.rb +2 -2
  74. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +12 -6
  75. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +9 -11
  76. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +8 -2
  77. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +7 -7
  78. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +48 -17
  79. data/vendor/deps/cli-ui/REVISION +1 -1
  80. data/vendor/deps/cli-ui/lib/cli/ui.rb +5 -4
  81. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +9 -3
  82. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +1 -0
  83. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +3 -2
  84. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +1 -0
  85. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +13 -5
  86. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +29 -2
  87. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +21 -10
  88. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +63 -0
  89. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +11 -2
  90. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +1 -0
  91. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +3 -3
  92. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +6 -8
  93. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +2 -0
  94. data/vendor/gen/lib/gen.rb +39 -0
  95. data/vendor/gen/lib/gen/commands.rb +18 -0
  96. data/vendor/gen/lib/gen/commands/help.rb +20 -0
  97. data/vendor/gen/lib/gen/commands/new.rb +21 -0
  98. data/vendor/gen/lib/gen/entry_point.rb +10 -0
  99. data/vendor/gen/lib/gen/generator.rb +165 -0
  100. data/vendor/gen/template/.gitignore +2 -0
  101. data/vendor/gen/template/Gemfile +10 -0
  102. data/vendor/gen/template/README.md +1 -0
  103. data/vendor/gen/template/bin/testunit +23 -0
  104. data/vendor/gen/template/bin/update-deps +97 -0
  105. data/vendor/gen/template/dev-gems.yml +3 -0
  106. data/vendor/gen/template/dev-vendor.yml +4 -0
  107. data/vendor/gen/template/exe/__app__-gems +17 -0
  108. data/vendor/gen/template/exe/__app__-vendor +18 -0
  109. data/vendor/gen/template/lib/__app__.rb +33 -0
  110. data/vendor/gen/template/lib/__app__/commands.rb +18 -0
  111. data/vendor/gen/template/lib/__app__/commands/example.rb +19 -0
  112. data/vendor/gen/template/lib/__app__/commands/help.rb +21 -0
  113. data/vendor/gen/template/lib/__app__/entry_point.rb +10 -0
  114. data/vendor/gen/template/test/example_test.rb +17 -0
  115. data/vendor/gen/template/test/test_helper.rb +22 -0
  116. metadata +25 -4
  117. data/Vagrantfile +0 -17
  118. data/lib/project_types/script/forms/script_form.rb +0 -69
@@ -21,6 +21,7 @@ module ShopifyCli
21
21
  DOWNLOAD_URLS = {
22
22
  mac: 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-amd64.zip',
23
23
  linux: 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip',
24
+ windows: 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-windows-amd64.zip',
24
25
  }
25
26
 
26
27
  NGROK_TUNNELS_URI = URI.parse('http://localhost:4040/api/tunnels')
@@ -88,7 +89,7 @@ module ShopifyCli
88
89
  #
89
90
  def auth(ctx, token)
90
91
  install(ctx)
91
- ctx.system(File.join(ShopifyCli::CACHE_DIR, 'ngrok'), 'authtoken', token)
92
+ ctx.system(File.join(ShopifyCli.cache_dir, 'ngrok'), 'authtoken', token)
92
93
  end
93
94
 
94
95
  ##
@@ -122,14 +123,21 @@ module ShopifyCli
122
123
  private
123
124
 
124
125
  def install(ctx)
125
- return if File.exist?(File.join(ShopifyCli::CACHE_DIR, 'ngrok'))
126
+ return if File.exist?(File.join(ShopifyCli.cache_dir, ctx.windows? ? 'ngrok.exe' : 'ngrok'))
127
+ check_prereq_command(ctx, 'curl')
128
+ check_prereq_command(ctx, ctx.linux? ? 'unzip' : 'tar')
126
129
  spinner = CLI::UI::SpinGroup.new
127
130
  spinner.add('Installing ngrok...') do
128
- zip_dest = File.join(ShopifyCli::CACHE_DIR, 'ngrok.zip')
131
+ zip_dest = File.join(ShopifyCli.cache_dir, 'ngrok.zip')
129
132
  unless File.exist?(zip_dest)
130
- ctx.system('curl', '-o', zip_dest, DOWNLOAD_URLS[ctx.os], chdir: ShopifyCli::CACHE_DIR)
133
+ ctx.system('curl', '-o', zip_dest, DOWNLOAD_URLS[ctx.os], chdir: ShopifyCli.cache_dir)
131
134
  end
132
- ctx.system('unzip', '-u', zip_dest, chdir: ShopifyCli::CACHE_DIR)
135
+ args = if ctx.linux?
136
+ %W(unzip -u #{zip_dest})
137
+ else
138
+ %W(tar -xf #{zip_dest})
139
+ end
140
+ ctx.system(*args, chdir: ShopifyCli.cache_dir)
133
141
  ctx.rm(zip_dest)
134
142
  end
135
143
  spinner.wait
@@ -143,7 +151,7 @@ module ShopifyCli
143
151
  end
144
152
 
145
153
  def ngrok_command(port)
146
- "exec #{File.join(ShopifyCli::CACHE_DIR, 'ngrok')} http -inspect=false -log=stdout -log-level=debug #{port}"
154
+ "\"#{File.join(ShopifyCli.cache_dir, 'ngrok')}\" http -inspect=false -log=stdout -log-level=debug #{port}"
147
155
  end
148
156
 
149
157
  def seconds_to_hm(seconds)
@@ -163,6 +171,13 @@ module ShopifyCli
163
171
  end
164
172
  start_ngrok(ctx, port)
165
173
  end
174
+
175
+ def check_prereq_command(ctx, command)
176
+ cmd_path = ctx.which(command)
177
+ ctx.abort(ctx.message('core.tunnel.error.prereq_command_required', command)) if cmd_path.nil?
178
+ ctx.done(ctx.message('core.tunnel.prereq_command_location', command, cmd_path))
179
+ end
180
+
166
181
  class LogParser # :nodoc:
167
182
  TIMEOUT = 10
168
183
 
@@ -195,7 +210,7 @@ module ShopifyCli
195
210
  end
196
211
 
197
212
  def parse_account
198
- account, timeout, _ = @log.match(/AccountName:([\w\s]*) SessionDuration:([\d]+) PlanName/)&.captures
213
+ account, timeout, _ = @log.match(/AccountName:([\w\s\d@._\-]*) SessionDuration:([\d]+) PlanName/)&.captures
199
214
  @account = account&.empty? ? nil : account
200
215
  @timeout = timeout&.empty? ? 0 : timeout.to_i
201
216
  end
@@ -1,3 +1,3 @@
1
1
  module ShopifyCli
2
- VERSION = '1.0.3'
2
+ VERSION = '1.1.2'
3
3
  end
@@ -44,10 +44,6 @@ module ShopifyCli
44
44
  ROOT = File.expand_path('../..', __FILE__)
45
45
  PROJECT_TYPES_DIR = File.join(ROOT, 'lib', 'project_types')
46
46
  TEMP_DIR = File.join(ROOT, '.tmp')
47
- CACHE_DIR = File.join(File.expand_path(ENV.fetch('XDG_CACHE_HOME', '~/.cache')), TOOL_NAME)
48
- TOOL_CONFIG_PATH = File.join(File.expand_path(ENV.fetch('XDG_CONFIG_HOME', '~/.config')), TOOL_NAME)
49
- LOG_FILE = File.join(TOOL_CONFIG_PATH, 'logs', 'log.log')
50
- DEBUG_LOG_FILE = File.join(TOOL_CONFIG_PATH, 'logs', 'debug.log')
51
47
 
52
48
  # programmer emoji if default install location, else wrench emoji
53
49
  EMOJI = ROOT == '/opt/shopify' ? "\u{1f469}\u{200d}\u{1f4bb}" : "\u{1f527}"
@@ -82,7 +78,7 @@ module ShopifyCli
82
78
  # ShopifyCli::Config
83
79
  autocall(:Config) { CLI::Kit::Config.new(tool_name: TOOL_NAME) }
84
80
  # ShopifyCli::Logger
85
- autocall(:Logger) { CLI::Kit::Logger.new(debug_log_file: DEBUG_LOG_FILE) }
81
+ autocall(:Logger) { CLI::Kit::Logger.new(debug_log_file: ShopifyCli.debug_log_file) }
86
82
  # ShopifyCli::Resolver
87
83
  autocall(:Resolver) do
88
84
  ShopifyCli::Core::HelpResolver.new(
@@ -93,7 +89,7 @@ module ShopifyCli
93
89
  # ShopifyCli::ErrorHandler
94
90
  autocall(:ErrorHandler) do
95
91
  CLI::Kit::ErrorHandler.new(
96
- log_file: ShopifyCli::LOG_FILE,
92
+ log_file: ShopifyCli.log_file,
97
93
  exception_reporter: nil,
98
94
  )
99
95
  end
@@ -127,7 +123,37 @@ module ShopifyCli
127
123
 
128
124
  require 'shopify-cli/messages/messages'
129
125
  Context.load_messages(ShopifyCli::Messages::MESSAGES)
130
- end
131
126
 
132
- # Make sure the cache dir always exists
133
- FileUtils.mkdir_p(ShopifyCli::CACHE_DIR)
127
+ def self.cache_dir
128
+ cache_dir = if ENV.key?('RUNNING_SHOPIFY_CLI_TESTS')
129
+ TEMP_DIR
130
+ elsif ENV['LOCALAPPDATA'].nil?
131
+ File.join(File.expand_path(ENV.fetch('XDG_CACHE_HOME', '~/.cache')), TOOL_NAME)
132
+ else
133
+ File.join(File.expand_path(ENV['LOCALAPPDATA']), TOOL_NAME)
134
+ end
135
+
136
+ # Make sure the cache dir always exists
137
+ @cache_dir_exists ||= FileUtils.mkdir_p(cache_dir)
138
+
139
+ cache_dir
140
+ end
141
+
142
+ def self.tool_config_path
143
+ if ENV.key?('RUNNING_SHOPIFY_CLI_TESTS')
144
+ TEMP_DIR
145
+ elsif ENV['APPDATA'].nil?
146
+ File.join(File.expand_path(ENV.fetch('XDG_CONFIG_HOME', '~/.config')), TOOL_NAME)
147
+ else
148
+ File.join(File.expand_path(ENV['APPDATA']), TOOL_NAME)
149
+ end
150
+ end
151
+
152
+ def self.log_file
153
+ File.join(tool_config_path, 'logs', 'log.log')
154
+ end
155
+
156
+ def self.debug_log_file
157
+ File.join(tool_config_path, 'logs', 'debug.log')
158
+ end
159
+ end
@@ -26,7 +26,10 @@ Gem::Specification.new do |spec|
26
26
  # Specify which files should be added to the gem when it is released.
27
27
  # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
28
  spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
29
- %x(git ls-files -z).split("\x0").reject { |f| f.match(%r{^(test|spec|features|packaging)/}) }
29
+ %x(git ls-files -z).split("\x0").reject do |f|
30
+ f.match(%r{^(test|spec|features|packaging)/}) ||
31
+ f.match(%r{^bin/(update-deps|shopify.bat)$})
32
+ end
30
33
  end
31
34
  spec.bindir = "bin"
32
35
  spec.require_paths = ["lib", "vendor"]
@@ -1 +1 @@
1
- fc25a17dff9d3130bb705659c002282ef7827932
1
+ 1013aa5c5664e7034ca3f02fd2e0513361b07e95 (dirty)
@@ -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)
@@ -11,8 +11,8 @@ module CLI
11
11
  def const_missing(const)
12
12
  block = begin
13
13
  @autocalls.fetch(const)
14
- rescue KeyError
15
- return super
14
+ rescue KeyError
15
+ return super
16
16
  end
17
17
  const_set(const, block.call)
18
18
  end
@@ -22,11 +22,11 @@ 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
- rescue => e
29
- "(#{e.class}: #{e.message})"
28
+ rescue => e
29
+ "(#{e.class}: #{e.message})"
30
30
  end
31
31
  exception_reporter.report(notify_with, logs)
32
32
  end
@@ -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 1
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,7 +83,7 @@ module CLI
83
83
 
84
84
  CLI::Kit::EXIT_FAILURE_BUT_NOT_BUG
85
85
  rescue Interrupt
86
- $stderr.puts(format_error_message("Interrupt"))
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
@@ -91,10 +91,16 @@ module CLI
91
91
  else
92
92
  "Your disk is full - free space is required to operate"
93
93
  end
94
- $stderr.puts(format_error_message(message))
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
@@ -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
- command.call(args, command_name)
18
- rescue => e
19
- begin
20
- $stderr.puts "This command ran with ID: #{id}"
21
- $stderr.puts "Please include this information in any issues/report along with relevant logs"
22
- rescue SystemCallError
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
@@ -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 ENV['DEBUG']
64
+ $stdout.puts CLI::UI.fmt(msg) if is_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 is_debug?
77
+ val = ENV[@env_debug_name]
78
+ val && val != '0' && val != ''
79
+ end
74
80
  end
75
81
  end
76
82
  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 false, errors if should_raise && !errors.nil?
13
+ assert(false, errors) if should_raise && !errors.nil?
14
14
  errors
15
15
  end
16
16
 
@@ -196,22 +196,22 @@ module CLI
196
196
 
197
197
  unless errors[:unexpected].empty?
198
198
  final_error << CLI::UI.fmt(<<~EOF)
199
- {{bold:Unexpected command invocations:}}
200
- {{command:#{errors[:unexpected].join("\n")}}}
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
- {{bold:Expected commands were not run:}}
207
- {{command:#{errors[:not_run].join("\n")}}}
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
- {{bold:Commands were not run as expected:}}
214
- #{errors[:other].map { |cmd, msg| "{{command:#{cmd}}}\n#{msg}" }.join("\n\n")}
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
 
@@ -19,7 +19,7 @@ module CLI
19
19
  #
20
20
  def sudo_reason(msg)
21
21
  # See if sudo has a cached password
22
- `env SUDO_ASKPASS=/usr/bin/false sudo -A true`
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)
98
+ delegate_open3(*a, sudo: sudo, env: env, method: :popen2e, **kwargs, &block)
99
+ end
100
+
101
+ def popen3(*a, sudo: false, env: ENV, **kwargs)
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
  #
@@ -130,13 +142,11 @@ module CLI
130
142
 
131
143
  readers, = IO.select(ios)
132
144
  readers.each do |io|
133
- begin
134
- data, trailing = split_partial_characters(io.readpartial(4096))
135
- handlers[io].call(previous_trailing[io] + data)
136
- previous_trailing[io] = trailing
137
- rescue IOError
138
- io.close
139
- end
145
+ data, trailing = split_partial_characters(io.readpartial(4096))
146
+ handlers[io].call(previous_trailing[io] + data)
147
+ previous_trailing[io] = trailing
148
+ rescue IOError
149
+ io.close
140
150
  end
141
151
  end
142
152
 
@@ -163,6 +173,14 @@ module CLI
163
173
  [data.byteslice(0...partial_character_index), data.byteslice(partial_character_index..-1)]
164
174
  end
165
175
 
176
+ def os
177
+ return :mac if /darwin/.match(RUBY_PLATFORM)
178
+ return :linux if /linux/.match(RUBY_PLATFORM)
179
+ return :windows if /mingw32/.match(RUBY_PLATFORM)
180
+
181
+ raise "Could not determine OS from platform #{RUBY_PLATFORM}"
182
+ end
183
+
166
184
  private
167
185
 
168
186
  def apply_sudo(*a, sudo)
@@ -171,9 +189,9 @@ module CLI
171
189
  a
172
190
  end
173
191
 
174
- def delegate_open3(*a, sudo: raise, env: raise, method: raise, **kwargs)
192
+ def delegate_open3(*a, sudo: raise, env: raise, method: raise, **kwargs, &block)
175
193
  a = apply_sudo(*a, sudo)
176
- Open3.send(method, env, *resolve_path(a, env), **kwargs)
194
+ Open3.send(method, env, *resolve_path(a, env), **kwargs, &block)
177
195
  rescue Errno::EINTR
178
196
  raise(Errno::EINTR, "command interrupted: #{a.join(' ')}")
179
197
  end
@@ -189,17 +207,30 @@ module CLI
189
207
  # See https://github.com/Shopify/dev/pull/625 for more details.
190
208
  def resolve_path(a, env)
191
209
  # If only one argument was provided, make sure it's interpreted by a shell.
192
- return ["true ; " + a[0]] if a.size == 1
210
+ if a.size == 1
211
+ if os == :windows
212
+ return ["break && " + a[0]]
213
+ else
214
+ return ["true ; " + a[0]]
215
+ end
216
+ end
193
217
  return a if a.first.include?('/')
194
218
 
195
- paths = env.fetch('PATH', '').split(':')
196
- item = paths.detect do |f|
197
- command_path = "#{f}/#{a.first}"
198
- File.executable?(command_path) && File.file?(command_path)
219
+ item = which(a.first, env)
220
+ a[0] = item if item
221
+ a
222
+ end
223
+
224
+ def which(cmd, env)
225
+ exts = os == :windows ? env.fetch('PATHEXT').split(';') : ['']
226
+ env.fetch('PATH', '').split(File::PATH_SEPARATOR).each do |path|
227
+ exts.each do |ext|
228
+ exe = File.join(path, "#{cmd}#{ext}")
229
+ return exe if File.executable?(exe) && !File.directory?(exe)
230
+ end
199
231
  end
200
232
 
201
- a[0] = "#{item}/#{a.first}" if item
202
- a
233
+ nil
203
234
  end
204
235
  end
205
236
  end
@@ -1 +1 @@
1
- c59f601fe271432dfe304f30f08a48b2a343606e
1
+ 5ea578d3110af35c73837a8008147ce53a4b56ed (dirty)