shopify-cli 0.9.0 → 1.0.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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CODEOWNERS +3 -0
  3. data/CHANGELOG.md +26 -0
  4. data/{LICENSE.md → LICENSE} +0 -0
  5. data/RELEASING.md +51 -0
  6. data/Rakefile +1 -6
  7. data/docs/_config.yml +3 -0
  8. data/docs/_data/nav.yml +9 -0
  9. data/docs/getting-started/index.md +5 -40
  10. data/docs/getting-started/install/index.md +39 -0
  11. data/docs/getting-started/migrate/index.md +63 -0
  12. data/docs/getting-started/uninstall/index.md +37 -0
  13. data/docs/getting-started/upgrade/index.md +37 -0
  14. data/docs/index.md +5 -6
  15. data/lib/project_types/extension/cli.rb +2 -1
  16. data/lib/project_types/extension/commands/tunnel.rb +1 -1
  17. data/lib/project_types/extension/forms/register.rb +2 -3
  18. data/lib/project_types/extension/graphql/get_app_by_api_key.graphql +9 -0
  19. data/lib/project_types/extension/tasks/converters/app_converter.rb +27 -0
  20. data/lib/project_types/extension/tasks/get_app.rb +22 -0
  21. data/lib/project_types/extension/tasks/get_apps.rb +1 -6
  22. data/lib/project_types/node/forms/create.rb +3 -54
  23. data/lib/project_types/node/messages/messages.rb +1 -12
  24. data/lib/project_types/rails/forms/create.rb +3 -52
  25. data/lib/project_types/rails/messages/messages.rb +0 -11
  26. data/lib/project_types/script/cli.rb +5 -5
  27. data/lib/project_types/script/commands/create.rb +5 -4
  28. data/lib/project_types/script/commands/push.rb +1 -0
  29. data/lib/project_types/script/errors.rb +1 -0
  30. data/lib/project_types/script/forms/create.rb +8 -4
  31. data/lib/project_types/script/layers/application/build_script.rb +7 -10
  32. data/lib/project_types/script/layers/application/create_script.rb +16 -14
  33. data/lib/project_types/script/layers/application/project_dependencies.rb +3 -9
  34. data/lib/project_types/script/layers/application/push_script.rb +13 -11
  35. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +106 -0
  36. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +64 -0
  37. data/lib/project_types/script/layers/infrastructure/errors.rb +2 -2
  38. data/lib/project_types/script/layers/infrastructure/project_creator.rb +23 -0
  39. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +5 -2
  40. data/lib/project_types/script/layers/infrastructure/script_repository.rb +7 -37
  41. data/lib/project_types/script/layers/infrastructure/task_runner.rb +18 -0
  42. data/lib/project_types/script/messages/messages.rb +5 -6
  43. data/lib/project_types/script/script_project.rb +22 -9
  44. data/lib/project_types/script/templates/ts/as-pect.d.ts +1 -0
  45. data/lib/project_types/script/ui/error_handler.rb +5 -0
  46. data/lib/shopify-cli/admin_api.rb +1 -2
  47. data/lib/shopify-cli/admin_api/populate_resource_command.rb +10 -1
  48. data/lib/shopify-cli/api.rb +2 -0
  49. data/lib/shopify-cli/context.rb +51 -0
  50. data/lib/shopify-cli/core/entry_point.rb +6 -0
  51. data/lib/shopify-cli/core/finalize.rb +13 -0
  52. data/lib/shopify-cli/git.rb +14 -10
  53. data/lib/shopify-cli/messages/messages.rb +22 -2
  54. data/lib/shopify-cli/packager.rb +17 -25
  55. data/lib/shopify-cli/tasks.rb +1 -0
  56. data/lib/shopify-cli/tasks/select_org_and_shop.rb +77 -0
  57. data/lib/shopify-cli/tunnel.rb +33 -1
  58. data/lib/shopify-cli/version.rb +1 -1
  59. data/shopify-cli.gemspec +3 -5
  60. data/vendor/deps/cli-ui/REVISION +1 -1
  61. data/vendor/deps/cli-ui/lib/cli/ui.rb +52 -11
  62. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +11 -7
  63. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +34 -21
  64. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +107 -149
  65. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +99 -0
  66. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +119 -0
  67. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +158 -0
  68. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +112 -0
  69. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +9 -15
  70. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +47 -0
  71. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +9 -7
  72. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +39 -14
  73. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +62 -44
  74. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +7 -2
  75. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +23 -3
  76. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +34 -10
  77. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +12 -7
  78. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +26 -16
  79. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +3 -3
  80. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +75 -0
  81. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +27 -0
  82. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +61 -0
  83. metadata +30 -13
  84. data/lib/project_types/extension/features/tunnel_url.rb +0 -20
  85. data/lib/project_types/script/layers/infrastructure/assemblyscript_dependency_manager.rb +0 -73
  86. data/lib/project_types/script/layers/infrastructure/assemblyscript_wasm_builder.rb +0 -39
  87. data/lib/project_types/script/layers/infrastructure/dependency_manager.rb +0 -36
  88. data/lib/project_types/script/layers/infrastructure/script_builder.rb +0 -34
  89. data/lib/project_types/script/layers/infrastructure/test_suite_repository.rb +0 -59
  90. data/vendor/deps/cli-ui/lib/cli/ui/box.rb +0 -15
@@ -0,0 +1 @@
1
+ /// <reference types="@as-pect/assembly/types/as-pect" />
@@ -44,6 +44,11 @@ module Script
44
44
  cause_of_error: ShopifyCli::Context.message('script.error.invalid_context_cause'),
45
45
  help_suggestion: ShopifyCli::Context.message('script.error.invalid_context_help'),
46
46
  }
47
+ when Errors::InvalidScriptNameError
48
+ {
49
+ cause_of_error: ShopifyCli::Context.message('script.error.invalid_script_name_cause'),
50
+ help_suggestion: ShopifyCli::Context.message('script.error.invalid_script_name_help'),
51
+ }
47
52
  when Errors::NoExistingAppsError
48
53
  {
49
54
  cause_of_error: ShopifyCli::Context.message('script.error.no_existing_apps_cause'),
@@ -37,8 +37,7 @@ module ShopifyCli
37
37
  #
38
38
  # ShopifyCli::AdminAPI.query(@ctx, 'all_organizations')
39
39
  #
40
- def query(ctx, query_name, api_version: nil, shop: nil, **variables)
41
- shop ||= Project.current.env.shop
40
+ def query(ctx, query_name, shop:, api_version: nil, **variables)
42
41
  authenticated_req(ctx, shop) do
43
42
  api_client(ctx, api_version, shop).query(query_name, variables: variables)
44
43
  end
@@ -41,6 +41,8 @@ module ShopifyCli
41
41
  return @ctx.puts(output)
42
42
  end
43
43
 
44
+ @shop ||= Project.current.env.shop || get_shop(@ctx)
45
+
44
46
  if @silent
45
47
  spin_group = CLI::UI::SpinGroup.new
46
48
  spin_group.add(@ctx.message('core.populate.populating', @count, camel_case_resource_type)) do |spinner|
@@ -114,7 +116,7 @@ module ShopifyCli
114
116
 
115
117
  def run_mutation(data)
116
118
  kwargs = { input: data }
117
- kwargs[:shop] = @shop if @shop
119
+ kwargs[:shop] = @shop
118
120
  resp = AdminAPI.query(
119
121
  @ctx, "create_#{snake_case_resource_type}", kwargs
120
122
  )
@@ -145,6 +147,13 @@ module ShopifyCli
145
147
 
146
148
  private
147
149
 
150
+ def get_shop(ctx)
151
+ res = ShopifyCli::Tasks::SelectOrgAndShop.call(ctx)
152
+ domain = res[:shop_domain]
153
+ Project.current.env.update(ctx, :shop, domain)
154
+ domain
155
+ end
156
+
148
157
  def camel_case_resource_type
149
158
  @camel_case_resource_type ||= self.class.to_s.split('::').last
150
159
  end
@@ -32,6 +32,8 @@ module ShopifyCli
32
32
  )
33
33
  ctx.debug(resp)
34
34
  resp
35
+ rescue API::APIRequestServerError, API::APIRequestUnexpectedError
36
+ ctx.puts(ctx.message('core.api.error.internal_server_error'))
35
37
  end
36
38
 
37
39
  protected
@@ -131,6 +131,47 @@ module ShopifyCli
131
131
  File.write(ctx_path(fname), content)
132
132
  end
133
133
 
134
+ # will change directories and update the root, the filepath is relative to the command root unless absolute
135
+ #
136
+ # #### Parameters
137
+ # * `path` - the file path to a directory, relative to the context root to remove from the FS
138
+ #
139
+ def chdir(path)
140
+ Dir.chdir(ctx_path(path))
141
+ self.root = ctx_path(path)
142
+ end
143
+
144
+ # checks if a directory exists, the filepath is relative to the command root unless absolute
145
+ #
146
+ # #### Parameters
147
+ # * `path` - the file path to a directory, relative to the context root to remove from the FS
148
+ #
149
+ def exist?(path)
150
+ Dir.exist?(ctx_path(path))
151
+ end
152
+
153
+ # will recursively copy a directory from the FS, the filepath is relative to the command
154
+ # root unless absolute
155
+ #
156
+ # #### Parameters
157
+ # * `from` - the path of the original file
158
+ # * `to` - the destination path
159
+ #
160
+ def cp_r(from, to)
161
+ FileUtils.cp_r(ctx_path(from), ctx_path(to))
162
+ end
163
+
164
+ # will copy a directory from the FS, the filepath is relative to the command
165
+ # root unless absolute
166
+ #
167
+ # #### Parameters
168
+ # * `from` - the path of the original file
169
+ # * `to` - the destination path
170
+ #
171
+ def cp(from, to)
172
+ FileUtils.cp(ctx_path(from), ctx_path(to))
173
+ end
174
+
134
175
  # will rename a file from one place to another, relative to the command root
135
176
  # unless the path is absolute.
136
177
  #
@@ -162,6 +203,16 @@ module ShopifyCli
162
203
  FileUtils.rm_r(ctx_path(fname))
163
204
  end
164
205
 
206
+ # will force remove a directory from the FS, the filepath is relative to the command
207
+ # root unless absolute
208
+ #
209
+ # #### Parameters
210
+ # * `fname` - the file path to a directory, relative to the context root to remove from the FS
211
+ #
212
+ def rm_rf(fname)
213
+ FileUtils.rm_rf(ctx_path(fname))
214
+ end
215
+
165
216
  # will create a directory, recursively if it does not exist. So if you create
166
217
  # a directory `foo/bar/dun`, this will also create the directories `foo` and
167
218
  # `foo/bar` if they do not exist. The path will be made relative to the command
@@ -11,6 +11,12 @@ module ShopifyCli
11
11
  IO.open(9) { is_shell_shim = true }
12
12
  rescue Errno::EBADF
13
13
  # This is expected if the descriptor doesn't exist
14
+ rescue ArgumentError => e
15
+ # This can happen on RVM, because it can use fd 9 itself and block access to it. That only happens if the fd
16
+ # did not exist beforehand, so that means there was no fd 9 before Ruby started.
17
+ unless e.message == 'The given fd is not accessible because RubyVM reserves it'
18
+ raise e
19
+ end
14
20
  end
15
21
 
16
22
  if !ctx.testing? && is_shell_shim
@@ -0,0 +1,13 @@
1
+ module ShopifyCli
2
+ module Core
3
+ # This class is just a dummy to make sure that we don't trigger warnings on the first time the updated code runs.
4
+ # The old code would try to call the Finalizer after it is done updating, which would then trigger an autoload of
5
+ # this class and fail.
6
+ module Finalize
7
+ class << self
8
+ def deliver!
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -46,10 +46,10 @@ module ShopifyCli
46
46
  if Dir.exist?(dest)
47
47
  ctx.abort(ctx.message('core.git.error.directory_exists'))
48
48
  else
49
- CLI::UI::Frame.open(ctx.message('core.git.cloning', dest)) do
49
+ success_message = ctx.message('core.git.cloned', dest)
50
+ CLI::UI::Frame.open(ctx.message('core.git.cloning', repository, dest), success_text: success_message) do
50
51
  clone_progress('clone', '--single-branch', repository, dest, ctx: ctx)
51
52
  end
52
- ctx.done(ctx.message('core.git.cloned', dest))
53
53
  end
54
54
  end
55
55
 
@@ -121,19 +121,23 @@ module ShopifyCli
121
121
  def clone_progress(*git_command, ctx:)
122
122
  CLI::UI::Progress.progress do |bar|
123
123
  msg = []
124
- success = ctx.system('git', *git_command, '--progress') do |_out, err|
125
- if err.strip.start_with?('Receiving objects:')
126
- percent = (err.match(/Receiving objects:\s+(\d+)/)[1].to_f / 100).round(2)
124
+ require 'open3'
125
+
126
+ success = Open3.popen3('git', *git_command, '--progress') do |_stdin, _stdout, stderr, thread|
127
+ while (line = stderr.gets)
128
+ next unless line.strip.start_with?('Receiving objects:')
129
+ percent = (line.match(/Receiving objects:\s+(\d+)/)[1].to_f / 100).round(2)
127
130
  bar.tick(set_percent: percent)
128
131
  next
129
132
  end
130
- msg << err
133
+
134
+ msg << stderr
135
+ thread.value
131
136
  end.success?
132
- unless success
133
- ctx.abort(msg.join("\n"))
134
- end
137
+
138
+ ctx.abort(msg.join("\n")) unless success
135
139
  bar.tick(set_percent: 1.0)
136
- true
140
+ success
137
141
  end
138
142
  end
139
143
  end
@@ -60,8 +60,8 @@ module ShopifyCli
60
60
  no_commits_made: "No git commits have been made. Please make at least one commit.",
61
61
  },
62
62
 
63
- cloning: "Cloning into %s...",
64
- cloned: "Cloned into %s",
63
+ cloning: "Cloning %s into %s...",
64
+ cloned: "{{v}} Cloned into %s",
65
65
  },
66
66
 
67
67
  help: {
@@ -154,6 +154,12 @@ module ShopifyCli
154
154
  },
155
155
  },
156
156
 
157
+ api: {
158
+ error: {
159
+ internal_server_error: '{{red:{{x}} An unexpected error occurred on Shopify.}}',
160
+ },
161
+ },
162
+
157
163
  populate: {
158
164
  options: {
159
165
  header: "{{bold:{{cyan:%s}} options:}}",
@@ -245,6 +251,20 @@ module ShopifyCli
245
251
  "{{x}} error: For authentication issues, run {{command:%s logout}} to clear invalid credentials",
246
252
  update_prompt: "Do you want to update your application url?",
247
253
  },
254
+ select_org_and_shop: {
255
+ authentication_issue: "For authentication issues, run {{command:%s logout}} to clear invalid credentials",
256
+ create_store: "Visit {{underline:https://partners.shopify.com/%s/stores}} to create one",
257
+ development_store: "Using development store {{green:%s}}",
258
+ development_store_select: "Select a development store",
259
+ error: {
260
+ no_development_stores: "{{x}} No Development Stores available.",
261
+ no_organizations: "No partner organizations available.",
262
+ organization_not_found: "Cannot find a partner organization with that ID",
263
+ partners_notice: "Please visit https://partners.shopify.com/ to create a partners account",
264
+ },
265
+ organization: "Partner organization {{green:%s (%s)}}",
266
+ organization_select: "Select partner organization",
267
+ },
248
268
  },
249
269
 
250
270
  tunnel: {
@@ -7,22 +7,8 @@ module ShopifyCli
7
7
  FileUtils.mkdir_p(BUILDS_DIR)
8
8
  end
9
9
 
10
- def build_gem
11
- build_path = gem_path
12
- puts "\nBuilding gem"
13
-
14
- puts "Outputting gem to:\n #{build_path}\n\n"
15
- raise "Failed to build gem" unless system(
16
- 'gem',
17
- 'build',
18
- '-o',
19
- build_path,
20
- File.join(ShopifyCli::ROOT, 'shopify-cli.gemspec')
21
- )
22
- end
23
-
24
10
  def build_debian
25
- ensure_program_installed('dpkg-deb')
11
+ ensure_program_installed('dpkg-deb', 'brew install dpkg')
26
12
 
27
13
  root_dir = File.join(PACKAGING_DIR, 'debian')
28
14
  debian_dir = File.join(root_dir, 'shopify-cli', 'DEBIAN')
@@ -54,7 +40,7 @@ module ShopifyCli
54
40
  end
55
41
 
56
42
  def build_rpm
57
- ensure_program_installed('rpmbuild')
43
+ ensure_program_installed('rpmbuild', 'brew install rpm')
58
44
 
59
45
  root_dir = File.join(PACKAGING_DIR, 'rpm')
60
46
  rpm_build_dir = File.join(root_dir, 'build')
@@ -92,10 +78,14 @@ module ShopifyCli
92
78
  spec_contents = File.read(File.join(root_dir, 'shopify-cli.base.rb'))
93
79
  spec_contents = spec_contents.gsub('SHOPIFY_CLI_VERSION', ShopifyCli::VERSION)
94
80
 
95
- checksum = %x`shasum -a 256 #{gem_path}`
96
- gem_checksum = checksum.split(' ')[0]
81
+ puts "Grabbing sha256 checksum from Rubygems.org"
82
+ require 'digest/sha2'
83
+ require 'open-uri'
84
+ gem_checksum = open("https://rubygems.org/downloads/shopify-cli-#{ShopifyCli::VERSION}.gem") do |io|
85
+ Digest::SHA256.new.hexdigest(io.read)
86
+ end
97
87
 
98
- puts "Got sha256 sum for gem: #{gem_checksum}"
88
+ puts "Got sha256 checksum for gem: #{gem_checksum}"
99
89
  spec_contents = spec_contents.gsub('SHOPIFY_CLI_GEM_CHECKSUM', gem_checksum)
100
90
 
101
91
  puts "Writing generated formula\n To: #{build_path}\n\n"
@@ -104,13 +94,15 @@ module ShopifyCli
104
94
 
105
95
  private
106
96
 
107
- def ensure_program_installed(program)
108
- raise "Could not find program #{program} which is required to build the package" unless
109
- system(program, '--version', out: File::NULL, err: File::NULL)
110
- end
97
+ def ensure_program_installed(program, installation_cmd)
98
+ unless system(program, '--version', out: File::NULL, err: File::NULL)
99
+ raise <<~MESSAGE
100
+
101
+ Could not find program #{program} which is required to build the package.
102
+ You can install it by running `#{installation_cmd}`.
111
103
 
112
- def gem_path
113
- File.join(BUILDS_DIR, "shopify-cli.gem")
104
+ MESSAGE
105
+ end
114
106
  end
115
107
  end
116
108
  end
@@ -27,6 +27,7 @@ module ShopifyCli
27
27
  register :EnsureEnv, :ensure_env, 'shopify-cli/tasks/ensure_env'
28
28
  register :EnsureLoopbackURL, :ensure_loopback_url, 'shopify-cli/tasks/ensure_loopback_url'
29
29
  register :EnsureDevStore, :ensure_dev_store, 'shopify-cli/tasks/ensure_dev_store'
30
+ register :SelectOrgAndShop, :select_org_and_shop, 'shopify-cli/tasks/select_org_and_shop'
30
31
  register :UpdateDashboardURLS, :update_dashboard_urls, 'shopify-cli/tasks/update_dashboard_urls'
31
32
  end
32
33
  end
@@ -0,0 +1,77 @@
1
+ require 'shopify_cli'
2
+
3
+ module ShopifyCli
4
+ module Tasks
5
+ class SelectOrgAndShop < ShopifyCli::Task
6
+ attr_reader :ctx
7
+
8
+ def call(ctx, organization_id: nil, shop_domain: nil)
9
+ @ctx = ctx
10
+ return response(organization_id.to_i, shop_domain) unless organization_id.nil? || shop_domain.nil?
11
+ org = get_organization(organization_id)
12
+ shop_domain ||= get_shop_domain(org)
13
+ response(org["id"].to_i, shop_domain)
14
+ end
15
+
16
+ private
17
+
18
+ def response(organization_id, shop_domain)
19
+ {
20
+ organization_id: organization_id,
21
+ shop_domain: shop_domain,
22
+ }
23
+ end
24
+
25
+ def organizations
26
+ @organizations ||= ShopifyCli::PartnersAPI::Organizations.fetch_all(ctx)
27
+ end
28
+
29
+ def get_organization(organization_id)
30
+ @organization ||= if !organization_id.nil?
31
+ org = ShopifyCli::PartnersAPI::Organizations.fetch(ctx, id: organization_id)
32
+ if org.nil?
33
+ ctx.puts(ctx.message('core.tasks.select_org_and_shop.error.authentication_issue', ShopifyCli::TOOL_NAME))
34
+ ctx.abort(ctx.message('core.tasks.select_org_and_shop.error.organization_not_found'))
35
+ end
36
+ org
37
+ elsif organizations.count == 0
38
+ ctx.puts(ctx.message('core.tasks.select_org_and_shop.error.partners_notice'))
39
+ ctx.puts(ctx.message('core.tasks.select_org_and_shop.authentication_issue', ShopifyCli::TOOL_NAME))
40
+ ctx.abort(ctx.message('core.tasks.select_org_and_shop.error.no_organizations'))
41
+ elsif organizations.count == 1
42
+ org = organizations.first
43
+ ctx.puts(ctx.message('core.tasks.select_org_and_shop.organization', org['businessName'], org['id']))
44
+ org
45
+ else
46
+ org_id = CLI::UI::Prompt.ask(ctx.message('core.tasks.select_org_and_shop.organization_select')) do |handler|
47
+ organizations.each do |o|
48
+ handler.option(ctx.message('core.partners_api.org_name_and_id', o['businessName'], o['id'])) { o['id'] }
49
+ end
50
+ end
51
+ organizations.find { |o| o['id'] == org_id }
52
+ end
53
+ end
54
+
55
+ def get_shop_domain(organization)
56
+ valid_stores = organization['stores'].select do |store|
57
+ store['transferDisabled'] == true || store['convertableToPartnerTest'] == true
58
+ end
59
+
60
+ if valid_stores.count == 0
61
+ ctx.puts(ctx.message('core.tasks.select_org_and_shop.error.no_development_stores'))
62
+ ctx.puts(ctx.message('core.tasks.select_org_and_shop.create_store', organization['id']))
63
+ ctx.puts(ctx.message('core.tasks.select_org_and_shop.authentication_issue', ShopifyCli::TOOL_NAME))
64
+ elsif valid_stores.count == 1
65
+ domain = valid_stores.first['shopDomain']
66
+ ctx.puts(ctx.message('core.tasks.select_org_and_shop.development_store', domain))
67
+ domain
68
+ else
69
+ CLI::UI::Prompt.ask(
70
+ ctx.message('core.tasks.select_org_and_shop.development_store_select'),
71
+ options: valid_stores.map { |s| s['shopDomain'] }
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -11,7 +11,7 @@ module ShopifyCli
11
11
  class Tunnel
12
12
  extend SingleForwardable
13
13
 
14
- def_delegators :new, :start, :stop, :auth
14
+ def_delegators :new, :start, :stop, :auth, :stats, :urls
15
15
 
16
16
  class FetchUrlError < RuntimeError; end
17
17
  class NgrokError < RuntimeError; end
@@ -23,6 +23,10 @@ module ShopifyCli
23
23
  linux: 'https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip',
24
24
  }
25
25
 
26
+ NGROK_TUNNELS_URI = URI.parse('http://localhost:4040/api/tunnels')
27
+ TUNNELS_FIELD = 'tunnels'
28
+ PUBLIC_URL_FIELD = 'public_url'
29
+
26
30
  ##
27
31
  # will find and stop a running tunnel process. It will also output if the
28
32
  # operation was successful or not
@@ -82,6 +86,34 @@ module ShopifyCli
82
86
  ctx.system(File.join(ShopifyCli::CACHE_DIR, 'ngrok'), 'authtoken', token)
83
87
  end
84
88
 
89
+ ##
90
+ # will return the statistics of the current running tunnels
91
+ #
92
+ # #### Returns
93
+ #
94
+ # * `stats` - the hash of running statistics returning from the ngrok api
95
+ #
96
+ def stats
97
+ response = Net::HTTP.get_response(NGROK_TUNNELS_URI)
98
+ JSON.parse(response.body)
99
+ rescue
100
+ {}
101
+ end
102
+
103
+ ##
104
+ # will return the urls of the current running tunnels
105
+ #
106
+ # #### Returns
107
+ #
108
+ # * `stats` - the array of urls
109
+ #
110
+ def urls
111
+ tunnels = stats.dig(TUNNELS_FIELD)
112
+ tunnels.map { |tunnel| tunnel.dig(PUBLIC_URL_FIELD) }
113
+ rescue
114
+ []
115
+ end
116
+
85
117
  private
86
118
 
87
119
  def install(ctx)