shopify-cli 1.0.2 → 1.1.1

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 (138) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +3 -2
  3. data/CHANGELOG.md +20 -0
  4. data/Gemfile +1 -1
  5. data/Gemfile.lock +13 -13
  6. data/bin/load_shopify.rb +3 -1
  7. data/bin/shopify +2 -0
  8. data/docs/Gemfile.lock +23 -13
  9. data/docs/getting-started/index.md +3 -2
  10. data/docs/getting-started/install/index.md +55 -9
  11. data/docs/getting-started/uninstall/index.md +1 -1
  12. data/docs/getting-started/upgrade/index.md +8 -4
  13. data/ext/shopify-cli/extconf.rb +40 -20
  14. data/lib/project_types/extension/cli.rb +1 -1
  15. data/lib/project_types/extension/commands/build.rb +1 -1
  16. data/lib/project_types/extension/models/type.rb +1 -0
  17. data/lib/project_types/extension/tasks/create_extension.rb +1 -1
  18. data/lib/project_types/extension/tasks/get_app.rb +1 -1
  19. data/lib/project_types/extension/tasks/update_draft.rb +1 -1
  20. data/lib/project_types/node/commands/create.rb +4 -4
  21. data/lib/project_types/node/commands/deploy/heroku.rb +6 -1
  22. data/lib/project_types/node/commands/generate/billing.rb +7 -5
  23. data/lib/project_types/node/commands/generate/page.rb +9 -5
  24. data/lib/project_types/node/commands/generate/webhook.rb +5 -1
  25. data/lib/project_types/node/messages/messages.rb +1 -0
  26. data/lib/project_types/rails/cli.rb +0 -1
  27. data/lib/project_types/rails/commands/create.rb +52 -4
  28. data/lib/project_types/rails/commands/generate.rb +1 -0
  29. data/lib/project_types/rails/commands/generate/webhook.rb +3 -2
  30. data/lib/project_types/rails/commands/serve.rb +6 -2
  31. data/lib/project_types/rails/gem.rb +61 -6
  32. data/lib/project_types/rails/messages/messages.rb +27 -11
  33. data/lib/project_types/script/cli.rb +2 -3
  34. data/lib/project_types/script/commands/create.rb +5 -9
  35. data/lib/project_types/script/commands/disable.rb +4 -15
  36. data/lib/project_types/script/commands/enable.rb +37 -13
  37. data/lib/project_types/script/commands/push.rb +8 -13
  38. data/lib/project_types/script/config/extension_points.yml +9 -3
  39. data/lib/project_types/script/errors.rb +8 -0
  40. data/lib/project_types/script/forms/create.rb +1 -1
  41. data/lib/project_types/script/layers/application/create_script.rb +7 -6
  42. data/lib/project_types/script/layers/application/disable_script.rb +9 -7
  43. data/lib/project_types/script/layers/application/enable_script.rb +11 -9
  44. data/lib/project_types/script/layers/application/push_script.rb +6 -4
  45. data/lib/project_types/script/layers/domain/errors.rb +2 -0
  46. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +2 -2
  47. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +1 -1
  48. data/lib/project_types/script/layers/infrastructure/errors.rb +2 -0
  49. data/lib/project_types/script/layers/infrastructure/script_service.rb +8 -2
  50. data/lib/project_types/script/messages/messages.rb +25 -31
  51. data/lib/project_types/script/script_project.rb +6 -2
  52. data/lib/project_types/script/templates/ts/as-pect.config.js +6 -0
  53. data/lib/project_types/script/ui/error_handler.rb +8 -0
  54. data/lib/project_types/script/ui/printing_spinner.rb +75 -0
  55. data/lib/rubygems_plugin.rb +18 -10
  56. data/lib/shopify-cli/admin_api/populate_resource_command.rb +1 -1
  57. data/lib/shopify-cli/admin_api/schema.rb +20 -18
  58. data/lib/shopify-cli/command.rb +14 -11
  59. data/lib/shopify-cli/commands.rb +1 -0
  60. data/lib/shopify-cli/commands/config.rb +44 -0
  61. data/lib/shopify-cli/commands/connect.rb +8 -69
  62. data/lib/shopify-cli/commands/create.rb +2 -2
  63. data/lib/shopify-cli/commands/help.rb +1 -1
  64. data/lib/shopify-cli/commands/system.rb +22 -13
  65. data/lib/shopify-cli/context.rb +28 -0
  66. data/lib/shopify-cli/core.rb +0 -1
  67. data/lib/shopify-cli/core/entry_point.rb +1 -1
  68. data/lib/shopify-cli/core/executor.rb +3 -5
  69. data/lib/shopify-cli/core/monorail.rb +1 -1
  70. data/lib/shopify-cli/db.rb +1 -1
  71. data/lib/shopify-cli/feature.rb +97 -0
  72. data/lib/shopify-cli/heroku.rb +21 -5
  73. data/lib/shopify-cli/js_deps.rb +2 -2
  74. data/lib/shopify-cli/js_system.rb +2 -2
  75. data/lib/shopify-cli/messages/messages.rb +40 -12
  76. data/lib/shopify-cli/partners_api/organizations.rb +7 -7
  77. data/lib/shopify-cli/process_supervision.rb +60 -21
  78. data/lib/shopify-cli/project.rb +14 -6
  79. data/lib/shopify-cli/project_type.rb +5 -7
  80. data/lib/shopify-cli/sub_command.rb +1 -0
  81. data/lib/shopify-cli/task.rb +2 -2
  82. data/lib/shopify-cli/tasks.rb +11 -4
  83. data/lib/shopify-cli/tasks/ensure_env.rb +72 -16
  84. data/lib/shopify-cli/tasks/update_dashboard_urls.rb +4 -3
  85. data/lib/shopify-cli/tunnel.rb +53 -14
  86. data/lib/shopify-cli/version.rb +1 -1
  87. data/lib/shopify_cli.rb +36 -9
  88. data/shopify-cli.gemspec +4 -1
  89. data/vendor/deps/cli-kit/REVISION +1 -1
  90. data/vendor/deps/cli-kit/lib/cli/kit.rb +1 -1
  91. data/vendor/deps/cli-kit/lib/cli/kit/autocall.rb +2 -2
  92. data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +12 -6
  93. data/vendor/deps/cli-kit/lib/cli/kit/executor.rb +9 -11
  94. data/vendor/deps/cli-kit/lib/cli/kit/logger.rb +8 -2
  95. data/vendor/deps/cli-kit/lib/cli/kit/support/test_helper.rb +7 -7
  96. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +48 -17
  97. data/vendor/deps/cli-ui/REVISION +1 -1
  98. data/vendor/deps/cli-ui/lib/cli/ui.rb +5 -4
  99. data/vendor/deps/cli-ui/lib/cli/ui/ansi.rb +9 -3
  100. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +1 -0
  101. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +3 -2
  102. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +1 -0
  103. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +13 -5
  104. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +29 -2
  105. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +21 -10
  106. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +63 -0
  107. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +11 -2
  108. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +1 -0
  109. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +3 -3
  110. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +6 -8
  111. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +2 -0
  112. data/vendor/gen/lib/gen.rb +39 -0
  113. data/vendor/gen/lib/gen/commands.rb +18 -0
  114. data/vendor/gen/lib/gen/commands/help.rb +20 -0
  115. data/vendor/gen/lib/gen/commands/new.rb +21 -0
  116. data/vendor/gen/lib/gen/entry_point.rb +10 -0
  117. data/vendor/gen/lib/gen/generator.rb +165 -0
  118. data/vendor/gen/template/.gitignore +2 -0
  119. data/vendor/gen/template/Gemfile +10 -0
  120. data/vendor/gen/template/README.md +1 -0
  121. data/vendor/gen/template/bin/testunit +23 -0
  122. data/vendor/gen/template/bin/update-deps +97 -0
  123. data/vendor/gen/template/dev-gems.yml +3 -0
  124. data/vendor/gen/template/dev-vendor.yml +4 -0
  125. data/vendor/gen/template/exe/__app__-gems +17 -0
  126. data/vendor/gen/template/exe/__app__-vendor +18 -0
  127. data/vendor/gen/template/lib/__app__.rb +33 -0
  128. data/vendor/gen/template/lib/__app__/commands.rb +18 -0
  129. data/vendor/gen/template/lib/__app__/commands/example.rb +19 -0
  130. data/vendor/gen/template/lib/__app__/commands/help.rb +21 -0
  131. data/vendor/gen/template/lib/__app__/entry_point.rb +10 -0
  132. data/vendor/gen/template/test/example_test.rb +17 -0
  133. data/vendor/gen/template/test/test_helper.rb +22 -0
  134. metadata +28 -6
  135. data/Vagrantfile +0 -17
  136. data/lib/project_types/script/forms/enable.rb +0 -24
  137. data/lib/project_types/script/forms/push.rb +0 -19
  138. data/lib/project_types/script/forms/script_form.rb +0 -66
@@ -28,16 +28,17 @@ module ShopifyCli
28
28
  end
29
29
 
30
30
  def construct_redirect_urls(urls, new_url, callback_url)
31
- urls.map do |url|
31
+ new_urls = urls.map do |url|
32
32
  if (match = url.match(NGROK_REGEX))
33
33
  "#{new_url}#{match[2]}"
34
34
  else
35
35
  url
36
36
  end
37
37
  end
38
- if urls.grep(/#{new_url}#{callback_url}/).empty?
39
- urls.push("#{new_url}#{callback_url}")
38
+ if new_urls.grep(/#{new_url}#{callback_url}/).empty?
39
+ new_urls.push("#{new_url}#{callback_url}")
40
40
  end
41
+ new_urls.uniq
41
42
  end
42
43
  end
43
44
  end
@@ -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')
@@ -62,14 +63,19 @@ module ShopifyCli
62
63
  #
63
64
  def start(ctx, port: PORT)
64
65
  install(ctx)
65
- process = ShopifyCli::ProcessSupervision.start(:ngrok, ngrok_command(port))
66
- log = fetch_url(ctx, process.log_path)
67
- if log.account
68
- ctx.puts(ctx.message('core.tunnel.start_with_account', log.url, log.account))
66
+ url, account, seconds_remaining = start_ngrok(ctx, port)
67
+ if account
68
+ ctx.puts(ctx.message('core.tunnel.start_with_account', url, account))
69
69
  else
70
- ctx.puts(ctx.message('core.tunnel.start', log.url))
70
+ if seconds_remaining <= 0
71
+ ctx.puts(ctx.message('core.tunnel.timed_out'))
72
+ url, _account, seconds_remaining = restart_ngrok(ctx, port)
73
+ end
74
+ ctx.puts(ctx.message('core.tunnel.start', url))
75
+ ctx.puts(ctx.message('core.tunnel.will_timeout', seconds_to_hm(seconds_remaining)))
76
+ ctx.puts(ctx.message('core.tunnel.signup_suggestion', ShopifyCli::TOOL_NAME))
71
77
  end
72
- log.url
78
+ url
73
79
  end
74
80
 
75
81
  ##
@@ -83,7 +89,7 @@ module ShopifyCli
83
89
  #
84
90
  def auth(ctx, token)
85
91
  install(ctx)
86
- ctx.system(File.join(ShopifyCli::CACHE_DIR, 'ngrok'), 'authtoken', token)
92
+ ctx.system(File.join(ShopifyCli.cache_dir, 'ngrok'), 'authtoken', token)
87
93
  end
88
94
 
89
95
  ##
@@ -117,14 +123,21 @@ module ShopifyCli
117
123
  private
118
124
 
119
125
  def install(ctx)
120
- 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')
121
129
  spinner = CLI::UI::SpinGroup.new
122
130
  spinner.add('Installing ngrok...') do
123
- zip_dest = File.join(ShopifyCli::CACHE_DIR, 'ngrok.zip')
131
+ zip_dest = File.join(ShopifyCli.cache_dir, 'ngrok.zip')
124
132
  unless File.exist?(zip_dest)
125
- 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)
134
+ end
135
+ args = if ctx.linux?
136
+ %W(unzip -u #{zip_dest})
137
+ else
138
+ %W(tar -xf #{zip_dest})
126
139
  end
127
- ctx.system('unzip', '-u', zip_dest, chdir: ShopifyCli::CACHE_DIR)
140
+ ctx.system(*args, chdir: ShopifyCli.cache_dir)
128
141
  ctx.rm(zip_dest)
129
142
  end
130
143
  spinner.wait
@@ -138,13 +151,37 @@ module ShopifyCli
138
151
  end
139
152
 
140
153
  def ngrok_command(port)
141
- "exec #{File.join(ShopifyCli::CACHE_DIR, 'ngrok')} http -log=stdout -log-level=debug #{port}"
154
+ "\"#{File.join(ShopifyCli.cache_dir, 'ngrok')}\" http -inspect=false -log=stdout -log-level=debug #{port}"
155
+ end
156
+
157
+ def seconds_to_hm(seconds)
158
+ format("%d hours %d minutes", seconds / 3600, seconds / 60 % 60)
159
+ end
160
+
161
+ def start_ngrok(ctx, port)
162
+ process = ShopifyCli::ProcessSupervision.start(:ngrok, ngrok_command(port))
163
+ log = fetch_url(ctx, process.log_path)
164
+ seconds_remaining = (process.time.to_i + log.timeout) - Time.now.to_i
165
+ [log.url, log.account, seconds_remaining]
166
+ end
167
+
168
+ def restart_ngrok(ctx, port)
169
+ unless ShopifyCli::ProcessSupervision.stop(:ngrok)
170
+ ctx.abort(ctx.message('core.tunnel.error.stop'))
171
+ end
172
+ start_ngrok(ctx, port)
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))
142
179
  end
143
180
 
144
181
  class LogParser # :nodoc:
145
182
  TIMEOUT = 10
146
183
 
147
- attr_reader :url, :account
184
+ attr_reader :url, :account, :timeout
148
185
 
149
186
  def initialize(log_path)
150
187
  @log_path = log_path
@@ -173,7 +210,9 @@ module ShopifyCli
173
210
  end
174
211
 
175
212
  def parse_account
176
- @account, _ = @log.match(/AccountName:([\w\s]+) SessionDuration/)&.captures
213
+ account, timeout, _ = @log.match(/AccountName:([\w\s\d@._\-]*) SessionDuration:([\d]+) PlanName/)&.captures
214
+ @account = account&.empty? ? nil : account
215
+ @timeout = timeout&.empty? ? 0 : timeout.to_i
177
216
  end
178
217
 
179
218
  def error
@@ -1,3 +1,3 @@
1
1
  module ShopifyCli
2
- VERSION = '1.0.2'
2
+ VERSION = '1.1.1'
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
@@ -105,6 +101,7 @@ module ShopifyCli
105
101
  autoload :Context, 'shopify-cli/context'
106
102
  autoload :Core, 'shopify-cli/core'
107
103
  autoload :DB, 'shopify-cli/db'
104
+ autoload :Feature, 'shopify-cli/feature'
108
105
  autoload :Form, 'shopify-cli/form'
109
106
  autoload :Git, 'shopify-cli/git'
110
107
  autoload :Helpers, 'shopify-cli/helpers'
@@ -126,7 +123,37 @@ module ShopifyCli
126
123
 
127
124
  require 'shopify-cli/messages/messages'
128
125
  Context.load_messages(ShopifyCli::Messages::MESSAGES)
129
- end
130
126
 
131
- # Make sure the cache dir always exists
132
- 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