shopify-cli 0.9.2 → 1.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +24 -3
  3. data/docs/_config.yml +3 -0
  4. data/docs/_data/nav.yml +9 -0
  5. data/docs/getting-started/index.md +5 -40
  6. data/docs/getting-started/install/index.md +75 -0
  7. data/docs/getting-started/migrate/index.md +96 -0
  8. data/docs/getting-started/uninstall/index.md +37 -0
  9. data/docs/getting-started/upgrade/index.md +37 -0
  10. data/docs/index.md +5 -6
  11. data/lib/project_types/extension/cli.rb +3 -2
  12. data/lib/project_types/extension/commands/build.rb +1 -1
  13. data/lib/project_types/extension/commands/tunnel.rb +1 -1
  14. data/lib/project_types/extension/forms/register.rb +2 -3
  15. data/lib/project_types/extension/graphql/get_app_by_api_key.graphql +9 -0
  16. data/lib/project_types/extension/tasks/converters/app_converter.rb +27 -0
  17. data/lib/project_types/extension/tasks/get_app.rb +22 -0
  18. data/lib/project_types/extension/tasks/get_apps.rb +1 -6
  19. data/lib/project_types/node/forms/create.rb +3 -54
  20. data/lib/project_types/node/messages/messages.rb +3 -14
  21. data/lib/project_types/rails/cli.rb +0 -1
  22. data/lib/project_types/rails/forms/create.rb +3 -52
  23. data/lib/project_types/rails/messages/messages.rb +2 -13
  24. data/lib/project_types/script/cli.rb +5 -5
  25. data/lib/project_types/script/commands/create.rb +5 -4
  26. data/lib/project_types/script/commands/disable.rb +4 -14
  27. data/lib/project_types/script/commands/enable.rb +35 -11
  28. data/lib/project_types/script/commands/push.rb +9 -9
  29. data/lib/project_types/script/config/extension_points.yml +9 -3
  30. data/lib/project_types/script/errors.rb +1 -0
  31. data/lib/project_types/script/forms/create.rb +8 -4
  32. data/lib/project_types/script/forms/script_form.rb +5 -2
  33. data/lib/project_types/script/layers/application/create_script.rb +14 -20
  34. data/lib/project_types/script/layers/application/disable_script.rb +9 -7
  35. data/lib/project_types/script/layers/application/enable_script.rb +11 -9
  36. data/lib/project_types/script/layers/application/project_dependencies.rb +0 -5
  37. data/lib/project_types/script/layers/application/push_script.rb +6 -4
  38. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +106 -0
  39. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +2 -2
  40. data/lib/project_types/script/layers/infrastructure/errors.rb +2 -1
  41. data/lib/project_types/script/layers/infrastructure/project_creator.rb +23 -0
  42. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +1 -1
  43. data/lib/project_types/script/layers/infrastructure/script_repository.rb +1 -34
  44. data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
  45. data/lib/project_types/script/messages/messages.rb +21 -25
  46. data/lib/project_types/script/script_project.rb +8 -4
  47. data/lib/project_types/script/templates/ts/as-pect.config.js +6 -0
  48. data/lib/project_types/script/templates/ts/as-pect.d.ts +1 -0
  49. data/lib/project_types/script/ui/error_handler.rb +9 -0
  50. data/lib/project_types/script/ui/printing_spinner.rb +75 -0
  51. data/lib/shopify-cli/admin_api.rb +1 -2
  52. data/lib/shopify-cli/admin_api/populate_resource_command.rb +10 -1
  53. data/lib/shopify-cli/admin_api/schema.rb +20 -8
  54. data/lib/shopify-cli/command.rb +2 -5
  55. data/lib/shopify-cli/commands.rb +1 -0
  56. data/lib/shopify-cli/commands/config.rb +44 -0
  57. data/lib/shopify-cli/commands/connect.rb +17 -10
  58. data/lib/shopify-cli/commands/create.rb +1 -1
  59. data/lib/shopify-cli/commands/help.rb +1 -1
  60. data/lib/shopify-cli/commands/system.rb +1 -1
  61. data/lib/shopify-cli/context.rb +10 -1
  62. data/lib/shopify-cli/core.rb +0 -1
  63. data/lib/shopify-cli/core/entry_point.rb +6 -0
  64. data/lib/shopify-cli/core/finalize.rb +13 -0
  65. data/lib/shopify-cli/feature.rb +97 -0
  66. data/lib/shopify-cli/messages/messages.rb +45 -2
  67. data/lib/shopify-cli/partners_api/organizations.rb +7 -7
  68. data/lib/shopify-cli/project_type.rb +2 -5
  69. data/lib/shopify-cli/tasks.rb +1 -0
  70. data/lib/shopify-cli/tasks/ensure_env.rb +0 -1
  71. data/lib/shopify-cli/tasks/select_org_and_shop.rb +77 -0
  72. data/lib/shopify-cli/tasks/update_dashboard_urls.rb +4 -3
  73. data/lib/shopify-cli/tunnel.rb +66 -10
  74. data/lib/shopify-cli/version.rb +1 -1
  75. data/lib/shopify_cli.rb +1 -0
  76. data/vendor/deps/cli-ui/REVISION +1 -1
  77. data/vendor/deps/cli-ui/lib/cli/ui.rb +52 -11
  78. data/vendor/deps/cli-ui/lib/cli/ui/color.rb +11 -7
  79. data/vendor/deps/cli-ui/lib/cli/ui/formatter.rb +34 -21
  80. data/vendor/deps/cli-ui/lib/cli/ui/frame.rb +107 -149
  81. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_stack.rb +99 -0
  82. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style.rb +119 -0
  83. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/box.rb +158 -0
  84. data/vendor/deps/cli-ui/lib/cli/ui/frame/frame_style/bracket.rb +112 -0
  85. data/vendor/deps/cli-ui/lib/cli/ui/glyph.rb +9 -15
  86. data/vendor/deps/cli-ui/lib/cli/ui/printer.rb +47 -0
  87. data/vendor/deps/cli-ui/lib/cli/ui/progress.rb +9 -7
  88. data/vendor/deps/cli-ui/lib/cli/ui/prompt.rb +39 -14
  89. data/vendor/deps/cli-ui/lib/cli/ui/prompt/interactive_options.rb +62 -44
  90. data/vendor/deps/cli-ui/lib/cli/ui/prompt/options_handler.rb +7 -2
  91. data/vendor/deps/cli-ui/lib/cli/ui/spinner.rb +23 -3
  92. data/vendor/deps/cli-ui/lib/cli/ui/spinner/spin_group.rb +34 -10
  93. data/vendor/deps/cli-ui/lib/cli/ui/stdout_router.rb +12 -7
  94. data/vendor/deps/cli-ui/lib/cli/ui/terminal.rb +26 -16
  95. data/vendor/deps/cli-ui/lib/cli/ui/truncater.rb +3 -3
  96. data/vendor/deps/cli-ui/lib/cli/ui/widgets.rb +75 -0
  97. data/vendor/deps/cli-ui/lib/cli/ui/widgets/base.rb +27 -0
  98. data/vendor/deps/cli-ui/lib/cli/ui/widgets/status.rb +61 -0
  99. metadata +25 -9
  100. data/lib/project_types/extension/features/tunnel_url.rb +0 -20
  101. data/lib/project_types/script/forms/enable.rb +0 -24
  102. data/lib/project_types/script/forms/push.rb +0 -19
  103. data/lib/project_types/script/layers/infrastructure/assemblyscript_dependency_manager.rb +0 -51
  104. data/lib/project_types/script/layers/infrastructure/dependency_manager.rb +0 -36
  105. data/lib/project_types/script/layers/infrastructure/test_suite_repository.rb +0 -62
  106. data/vendor/deps/cli-ui/lib/cli/ui/box.rb +0 -15
@@ -28,7 +28,7 @@ module ShopifyCli
28
28
  def self.all_visible_type
29
29
  ProjectType
30
30
  .load_all
31
- .select { |type| !type.hidden }
31
+ .select { |type| !type.hidden? }
32
32
  end
33
33
 
34
34
  def self.help
@@ -47,7 +47,7 @@ module ShopifyCli
47
47
 
48
48
  def core_commands
49
49
  resolved_commands
50
- .select { |_name, c| !c.hidden }
50
+ .select { |_name, c| !c.hidden? }
51
51
  .select { |name, _c| Commands.core_command?(name) }
52
52
  end
53
53
 
@@ -4,7 +4,7 @@ require 'rbconfig'
4
4
  module ShopifyCli
5
5
  module Commands
6
6
  class System < ShopifyCli::Command
7
- hidden_command
7
+ hidden_feature(feature_set: :debug)
8
8
 
9
9
  def call(args, _name)
10
10
  show_all_details = false
@@ -146,10 +146,19 @@ module ShopifyCli
146
146
  # #### Parameters
147
147
  # * `path` - the file path to a directory, relative to the context root to remove from the FS
148
148
  #
149
- def exist?(path)
149
+ def dir_exist?(path)
150
150
  Dir.exist?(ctx_path(path))
151
151
  end
152
152
 
153
+ # checks if a file exists, the filepath is relative to the command root unless absolute
154
+ #
155
+ # #### Parameters
156
+ # * `path` - the file path to a file, relative to the context root to remove from the FS
157
+ #
158
+ def file_exist?(path)
159
+ File.exist?(ctx_path(path))
160
+ end
161
+
153
162
  # will recursively copy a directory from the FS, the filepath is relative to the command
154
163
  # root unless absolute
155
164
  #
@@ -4,6 +4,5 @@ module ShopifyCli
4
4
  autoload :Executor, 'shopify-cli/core/executor'
5
5
  autoload :HelpResolver, 'shopify-cli/core/help_resolver'
6
6
  autoload :Monorail, 'shopify-cli/core/monorail'
7
- autoload :Update, 'shopify-cli/core/update'
8
7
  end
9
8
  end
@@ -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
@@ -0,0 +1,97 @@
1
+ module ShopifyCli
2
+ ##
3
+ # ShopifyCli::Feature contains the logic to hide and show features across the CLI
4
+ # These features can be either commands or project types currently.
5
+ #
6
+ # Feature flags will persist between runs so if the flag is enabled or disabled,
7
+ # it will still be in that same state on the next cli invocation.
8
+ class Feature
9
+ SECTION = 'features'
10
+
11
+ ##
12
+ # ShopifyCli::Feature::Set is included on commands and projects to allow you to hide
13
+ # and enable projects and commands based on feature flags.
14
+ module Set
15
+ ##
16
+ # will hide a feature, either a project_type or a command
17
+ #
18
+ # #### Parameters
19
+ #
20
+ # * `feature_set` - either a single, or array of symbols that represent feature sets
21
+ #
22
+ # #### Example
23
+ #
24
+ # module ShopifyCli
25
+ # module Commands
26
+ # class Config < ShopifyCli::Command
27
+ # hidden_feature(feature_set: :basic)
28
+ # ....
29
+ #
30
+ def hidden_feature(feature_set: [])
31
+ @feature_hidden = true
32
+ @hidden_feature_set = Array(feature_set).compact
33
+ end
34
+
35
+ ##
36
+ # will return if the feature has been hidden or not
37
+ #
38
+ # #### Returns
39
+ #
40
+ # * `is_hidden` - returns true if the feature has been hidden and false otherwise
41
+ #
42
+ # #### Example
43
+ #
44
+ # ShopifyCli::Commands::Config.hidden?
45
+ #
46
+ def hidden?
47
+ enabled = (@hidden_feature_set || []).any? do |feature|
48
+ Feature.enabled?(feature)
49
+ end
50
+ @feature_hidden && !enabled
51
+ end
52
+ end
53
+
54
+ class << self
55
+ ##
56
+ # will enable a feature in the CLI.
57
+ #
58
+ # #### Parameters
59
+ #
60
+ # * `feature` - a symbol representing the flag to be enabled
61
+ def enable(feature)
62
+ set(feature, true)
63
+ end
64
+
65
+ ##
66
+ # will disable a feature in the CLI.
67
+ #
68
+ # #### Parameters
69
+ #
70
+ # * `feature` - a symbol representing the flag to be disabled
71
+ def disable(feature)
72
+ set(feature, false)
73
+ end
74
+
75
+ ##
76
+ # will check if the feature has been enabled
77
+ #
78
+ # #### Parameters
79
+ #
80
+ # * `feature` - a symbol representing a flag that the status should be requested
81
+ #
82
+ # #### Returns
83
+ #
84
+ # * `is_enabled` - will be true if the feature has been enabled.
85
+ def enabled?(feature)
86
+ return false if feature.nil?
87
+ ShopifyCli::Config.get_bool(SECTION, feature.to_s)
88
+ end
89
+
90
+ private
91
+
92
+ def set(feature, value)
93
+ ShopifyCli::Config.set(SECTION, feature.to_s, value)
94
+ end
95
+ end
96
+ end
97
+ end
@@ -24,6 +24,15 @@ module ShopifyCli
24
24
  MESSAGE
25
25
  development_store_select: "Which development store would you like to use?",
26
26
  cli_yml_saved: ".shopify-cli.yml saved to project root",
27
+
28
+ no_apps: 'You have no apps to connect to, creating a new app.',
29
+ app_name: "App name",
30
+ app_type: {
31
+ select: "What type of app are you building?",
32
+ select_public: "Public: An app built for a wide merchant audience.",
33
+ select_custom: "Custom: An app custom built for a single client.",
34
+ selected: "App type {{green:%s}}",
35
+ },
27
36
  },
28
37
 
29
38
  context: {
@@ -52,6 +61,19 @@ module ShopifyCli
52
61
  saved: "%s saved to project root",
53
62
  },
54
63
 
64
+ config: {
65
+ help: <<~HELP,
66
+ Change configuration of how the CLI operates
67
+ Usage: {{command:%s config [ feature ] [ feature_name ] }}
68
+ HELP
69
+ feature: {
70
+ enabled: "{{v}} feature {{green:%s}} was enabled",
71
+ disabled: "{{v}} feature {{green:%s}} was disabled",
72
+ is_enabled: "{{v}} feature {{green:%s}} is enabled",
73
+ is_disabled: "{{v}} feature {{green:%s}} is disabled",
74
+ },
75
+ },
76
+
55
77
  git: {
56
78
  error: {
57
79
  directory_exists: "Project directory already exists. Please create a project with a new name.",
@@ -251,6 +273,20 @@ module ShopifyCli
251
273
  "{{x}} error: For authentication issues, run {{command:%s logout}} to clear invalid credentials",
252
274
  update_prompt: "Do you want to update your application url?",
253
275
  },
276
+ select_org_and_shop: {
277
+ authentication_issue: "For authentication issues, run {{command:%s logout}} to clear invalid credentials",
278
+ create_store: "Visit {{underline:https://partners.shopify.com/%s/stores}} to create one",
279
+ development_store: "Using development store {{green:%s}}",
280
+ development_store_select: "Select a development store",
281
+ error: {
282
+ no_development_stores: "{{x}} No Development Stores available.",
283
+ no_organizations: "No partner organizations available.",
284
+ organization_not_found: "Cannot find a partner organization with that ID",
285
+ partners_notice: "Please visit https://partners.shopify.com/ to create a partners account",
286
+ },
287
+ organization: "Partner organization {{green:%s (%s)}}",
288
+ organization_select: "Select partner organization",
289
+ },
254
290
  },
255
291
 
256
292
  tunnel: {
@@ -259,10 +295,17 @@ module ShopifyCli
259
295
  url_fetch_failure: "Unable to fetch external url",
260
296
  },
261
297
 
262
- stopped: "{{green:x}} ngrok tunnel stopped",
263
298
  not_running: "{{green:x}} ngrok tunnel not running",
264
- start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
299
+ signup_suggestion: <<~MESSAGE,
300
+ {{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
301
+ account at {{underline:https://ngrok.com/signup}}. After you signup, install your
302
+ personalized authorization token using {{command:%s tunnel auth <token>}}.
303
+ MESSAGE
265
304
  start: "{{v}} ngrok tunnel running at {{underline:%s}}",
305
+ start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
306
+ stopped: "{{green:x}} ngrok tunnel stopped",
307
+ timed_out: "{{x}} ngrok tunnel has timed out, restarting ...",
308
+ will_timeout: "{{*}} This tunnel will timeout in {{red:%s}}",
266
309
  },
267
310
 
268
311
  version: {
@@ -4,25 +4,25 @@ module ShopifyCli
4
4
  class << self
5
5
  def fetch_all(ctx)
6
6
  resp = PartnersAPI.query(ctx, 'all_organizations')
7
- resp['data']['organizations']['nodes'].map do |org|
8
- org['stores'] = org['stores']['nodes']
7
+ (resp.dig('data', 'organizations', 'nodes') || []).map do |org|
8
+ org['stores'] = (org.dig('stores', 'nodes') || [])
9
9
  org
10
10
  end
11
11
  end
12
12
 
13
13
  def fetch(ctx, id:)
14
14
  resp = PartnersAPI.query(ctx, 'find_organization', id: id)
15
- org = resp['data']['organizations']['nodes'].first
15
+ org = resp.dig('data', 'organizations', 'nodes').first
16
16
  return nil if org.nil?
17
- org['stores'] = org['stores']['nodes']
17
+ org['stores'] = (org.dig('stores', 'nodes') || [])
18
18
  org
19
19
  end
20
20
 
21
21
  def fetch_with_app(ctx)
22
22
  resp = PartnersAPI.query(ctx, 'all_orgs_with_apps')
23
- resp['data']['organizations']['nodes'].map do |org|
24
- org['stores'] = org['stores']['nodes']
25
- org['apps'] = org['apps']['nodes']
23
+ (resp.dig('data', 'organizations', 'nodes') || []).map do |org|
24
+ org['stores'] = (org.dig('stores', 'nodes') || [])
25
+ org['apps'] = (org.dig('apps', 'nodes') || [])
26
26
  org
27
27
  end
28
28
  end
@@ -1,11 +1,12 @@
1
1
  module ShopifyCli
2
2
  class ProjectType
3
+ extend Feature::Set
4
+
3
5
  class << self
4
6
  attr_accessor :project_type,
5
7
  :project_name,
6
8
  :project_creator_command_class,
7
9
  :project_load_shallow
8
- attr_reader :hidden
9
10
 
10
11
  def repository
11
12
  @repository ||= []
@@ -53,10 +54,6 @@ module ShopifyCli
53
54
  const_get(@project_creator_command_class)
54
55
  end
55
56
 
56
- def hidden_project_type
57
- @hidden = true
58
- end
59
-
60
57
  def register_command(const, cmd)
61
58
  return if project_load_shallow
62
59
  Context.new.abort(
@@ -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
@@ -14,7 +14,6 @@ module ShopifyCli
14
14
  api_key = CLI::UI.ask(@ctx.message('core.tasks.ensure_env.api_key_question'))
15
15
  api_secret = CLI::UI.ask(@ctx.message('core.tasks.ensure_env.api_secret_key_question'))
16
16
  shop = CLI::UI.ask(@ctx.message('core.tasks.ensure_env.development_store_question'))
17
-
18
17
  shop.gsub!(/https?\:\/\//, '')
19
18
 
20
19
  env = Resources::EnvFile.new(
@@ -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
@@ -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
@@ -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
@@ -58,14 +62,19 @@ module ShopifyCli
58
62
  #
59
63
  def start(ctx, port: PORT)
60
64
  install(ctx)
61
- process = ShopifyCli::ProcessSupervision.start(:ngrok, ngrok_command(port))
62
- log = fetch_url(ctx, process.log_path)
63
- if log.account
64
- ctx.puts(ctx.message('core.tunnel.start_with_account', log.url, log.account))
65
+ url, account, seconds_remaining = start_ngrok(ctx, port)
66
+ if account
67
+ ctx.puts(ctx.message('core.tunnel.start_with_account', url, account))
65
68
  else
66
- ctx.puts(ctx.message('core.tunnel.start', log.url))
69
+ if seconds_remaining <= 0
70
+ ctx.puts(ctx.message('core.tunnel.timed_out'))
71
+ url, _account, seconds_remaining = restart_ngrok(ctx, port)
72
+ end
73
+ ctx.puts(ctx.message('core.tunnel.start', url))
74
+ ctx.puts(ctx.message('core.tunnel.will_timeout', seconds_to_hm(seconds_remaining)))
75
+ ctx.puts(ctx.message('core.tunnel.signup_suggestion', ShopifyCli::TOOL_NAME))
67
76
  end
68
- log.url
77
+ url
69
78
  end
70
79
 
71
80
  ##
@@ -82,6 +91,34 @@ module ShopifyCli
82
91
  ctx.system(File.join(ShopifyCli::CACHE_DIR, 'ngrok'), 'authtoken', token)
83
92
  end
84
93
 
94
+ ##
95
+ # will return the statistics of the current running tunnels
96
+ #
97
+ # #### Returns
98
+ #
99
+ # * `stats` - the hash of running statistics returning from the ngrok api
100
+ #
101
+ def stats
102
+ response = Net::HTTP.get_response(NGROK_TUNNELS_URI)
103
+ JSON.parse(response.body)
104
+ rescue
105
+ {}
106
+ end
107
+
108
+ ##
109
+ # will return the urls of the current running tunnels
110
+ #
111
+ # #### Returns
112
+ #
113
+ # * `stats` - the array of urls
114
+ #
115
+ def urls
116
+ tunnels = stats.dig(TUNNELS_FIELD)
117
+ tunnels.map { |tunnel| tunnel.dig(PUBLIC_URL_FIELD) }
118
+ rescue
119
+ []
120
+ end
121
+
85
122
  private
86
123
 
87
124
  def install(ctx)
@@ -106,13 +143,30 @@ module ShopifyCli
106
143
  end
107
144
 
108
145
  def ngrok_command(port)
109
- "exec #{File.join(ShopifyCli::CACHE_DIR, 'ngrok')} http -log=stdout -log-level=debug #{port}"
146
+ "exec #{File.join(ShopifyCli::CACHE_DIR, 'ngrok')} http -inspect=false -log=stdout -log-level=debug #{port}"
147
+ end
148
+
149
+ def seconds_to_hm(seconds)
150
+ format("%d hours %d minutes", seconds / 3600, seconds / 60 % 60)
110
151
  end
111
152
 
153
+ def start_ngrok(ctx, port)
154
+ process = ShopifyCli::ProcessSupervision.start(:ngrok, ngrok_command(port))
155
+ log = fetch_url(ctx, process.log_path)
156
+ seconds_remaining = (process.time.to_i + log.timeout) - Time.now.to_i
157
+ [log.url, log.account, seconds_remaining]
158
+ end
159
+
160
+ def restart_ngrok(ctx, port)
161
+ unless ShopifyCli::ProcessSupervision.stop(:ngrok)
162
+ ctx.abort(ctx.message('core.tunnel.error.stop'))
163
+ end
164
+ start_ngrok(ctx, port)
165
+ end
112
166
  class LogParser # :nodoc:
113
167
  TIMEOUT = 10
114
168
 
115
- attr_reader :url, :account
169
+ attr_reader :url, :account, :timeout
116
170
 
117
171
  def initialize(log_path)
118
172
  @log_path = log_path
@@ -141,7 +195,9 @@ module ShopifyCli
141
195
  end
142
196
 
143
197
  def parse_account
144
- @account, _ = @log.match(/AccountName:([\w\s]+) SessionDuration/)&.captures
198
+ account, timeout, _ = @log.match(/AccountName:([\w\s]*) SessionDuration:([\d]+) PlanName/)&.captures
199
+ @account = account&.empty? ? nil : account
200
+ @timeout = timeout&.empty? ? 0 : timeout.to_i
145
201
  end
146
202
 
147
203
  def error