shopify-cli 1.0.2 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -69,7 +69,7 @@ module ShopifyCli
69
69
  deps = parse_dependencies
70
70
  errors = nil
71
71
 
72
- spinner_title = ctx.message('core.js_deps.installing_deps', deps.size)
72
+ spinner_title = ctx.message('core.js_deps.installing', @system.package_manager)
73
73
  success = CLI::UI::Spinner.spin(spinner_title, auto_debrief: false) do |spinner|
74
74
  _, errors, status = CLI::Kit::System.capture3(*cmd, env: @ctx.env, chdir: ctx.root)
75
75
  update_spinner_title_and_status(spinner, status, deps)
@@ -81,7 +81,7 @@ module ShopifyCli
81
81
 
82
82
  def update_spinner_title_and_status(spinner, status, deps)
83
83
  if status.success?
84
- spinner.update_title(ctx.message('core.js_deps.installed_deps', deps.size))
84
+ spinner.update_title(ctx.message('core.js_deps.installed', deps.size))
85
85
  else
86
86
  spinner.update_title(ctx.message('core.js_deps.error.install_spinner_error', deps.size))
87
87
  CLI::UI::Spinner::TASK_FAILED
@@ -64,8 +64,8 @@ module ShopifyCli
64
64
  #
65
65
  def yarn?
66
66
  @has_yarn ||= begin
67
- _, status = CLI::Kit::System.capture2('which', 'yarn')
68
- File.exist?(File.join(ctx.root, 'yarn.lock')) && status.success?
67
+ cmd_path = @ctx.which('yarn')
68
+ File.exist?(File.join(ctx.root, 'yarn.lock')) && !cmd_path.nil?
69
69
  end
70
70
  end
71
71
 
@@ -16,13 +16,6 @@ module ShopifyCli
16
16
  already_connected_warning: "{{yellow:! This app appears to be already connected}}",
17
17
  connected: "{{v}} Project now connected to {{green:%s}}",
18
18
  project_type_select: "What type of project would you like to connect?",
19
- organization_select: "To which partner organization does this project belong?",
20
- app_select: "To which app does this project belong?",
21
- no_development_stores: <<~MESSAGE,
22
- No development stores available.
23
- Visit {{underline:https://partners.shopify.com/%d/stores}} to create one
24
- MESSAGE
25
- development_store_select: "Which development store would you like to use?",
26
19
  cli_yml_saved: ".shopify-cli.yml saved to project root",
27
20
  },
28
21
 
@@ -52,6 +45,19 @@ module ShopifyCli
52
45
  saved: "%s saved to project root",
53
46
  },
54
47
 
48
+ config: {
49
+ help: <<~HELP,
50
+ Change configuration of how the CLI operates
51
+ Usage: {{command:%s config [ feature ] [ feature_name ] }}
52
+ HELP
53
+ feature: {
54
+ enabled: "{{v}} feature {{green:%s}} was enabled",
55
+ disabled: "{{v}} feature {{green:%s}} was disabled",
56
+ is_enabled: "{{v}} feature {{green:%s}} is enabled",
57
+ is_disabled: "{{v}} feature {{green:%s}} is disabled",
58
+ },
59
+ },
60
+
55
61
  git: {
56
62
  error: {
57
63
  directory_exists: "Project directory already exists. Please create a project with a new name.",
@@ -232,9 +238,21 @@ module ShopifyCli
232
238
 
233
239
  tasks: {
234
240
  ensure_env: {
235
- api_key_question: "What is your Shopify API key?",
236
- api_secret_key_question: "What is your Shopify API secret key?",
237
- development_store_question: "What is your development store URL? (Example: my-dev-store.myshopify.com)",
241
+ organization_select: "To which partner organization does this project belong?",
242
+ no_development_stores: <<~MESSAGE,
243
+ No development stores available.
244
+ Visit {{underline:https://partners.shopify.com/%d/stores}} to create one
245
+ MESSAGE
246
+ development_store_select: "Which development store would you like to use?",
247
+ app_select: "To which app does this project belong?",
248
+ no_apps: 'You have no apps to connect to, creating a new app.',
249
+ app_name: "App name",
250
+ app_type: {
251
+ select: "What type of app are you building?",
252
+ select_public: "Public: An app built for a wide merchant audience.",
253
+ select_custom: "Custom: An app custom built for a single client.",
254
+ selected: "App type {{green:%s}}",
255
+ },
238
256
  },
239
257
  ensure_dev_store: {
240
258
  could_not_verify_store: "Couldn't verify your store %s",
@@ -271,12 +289,22 @@ module ShopifyCli
271
289
  error: {
272
290
  stop: "ngrok tunnel could not be stopped. Try running {{command:killall -9 ngrok}}",
273
291
  url_fetch_failure: "Unable to fetch external url",
292
+ prereq_command_required: "%1$s is required for installing ngrok. Please install %1$s using the appropriate"\
293
+ " package manager for your system.",
274
294
  },
275
295
 
276
- stopped: "{{green:x}} ngrok tunnel stopped",
277
296
  not_running: "{{green:x}} ngrok tunnel not running",
278
- start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
297
+ signup_suggestion: <<~MESSAGE,
298
+ {{*}} To avoid tunnels that timeout, it is recommended to signup for a free ngrok
299
+ account at {{underline:https://ngrok.com/signup}}. After you signup, install your
300
+ personalized authorization token using {{command:%s tunnel auth <token>}}.
301
+ MESSAGE
279
302
  start: "{{v}} ngrok tunnel running at {{underline:%s}}",
303
+ start_with_account: "{{v}} ngrok tunnel running at {{underline:%s}}, with account %s",
304
+ stopped: "{{green:x}} ngrok tunnel stopped",
305
+ timed_out: "{{x}} ngrok tunnel has timed out, restarting ...",
306
+ will_timeout: "{{*}} This tunnel will timeout in {{red:%s}}",
307
+ prereq_command_location: "%s @ %s",
280
308
  },
281
309
 
282
310
  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
@@ -5,13 +5,10 @@ module ShopifyCli
5
5
  # ProcessSupervision wraps a running process spawned by `exec` and keeps track
6
6
  # if its `pid` and keeps a log file for it as well
7
7
  class ProcessSupervision
8
- # is the directory where the pid and logfile are kept
9
- RUN_DIR = File.join(ShopifyCli::CACHE_DIR, 'sv')
10
-
11
8
  # a string or a symbol to identify this process by
12
9
  attr_reader :identifier
13
10
  # process ID for the running process
14
- attr_reader :pid
11
+ attr_accessor :pid
15
12
  # starttime of the process
16
13
  attr_reader :time
17
14
  # filepath to the pidfile for this process
@@ -20,6 +17,11 @@ module ShopifyCli
20
17
  attr_reader :log_path
21
18
 
22
19
  class << self
20
+ def run_dir
21
+ # is the directory where the pid and logfile are kept
22
+ File.join(ShopifyCli.cache_dir, 'sv')
23
+ end
24
+
23
25
  ##
24
26
  # Will find and create a new instance of ProcessSupervision for a running process
25
27
  # if it is currently running. It will return nil if the process is not running.
@@ -34,7 +36,7 @@ module ShopifyCli
34
36
  # will be nil if the process is not running.
35
37
  #
36
38
  def for_ident(identifier)
37
- pid, time = File.read(File.join(RUN_DIR, "#{identifier}.pid")).split(':')
39
+ pid, time = File.read(File.join(ShopifyCli::ProcessSupervision.run_dir, "#{identifier}.pid")).split(':')
38
40
  new(identifier, pid: Integer(pid), time: time)
39
41
  rescue Errno::ENOENT
40
42
  nil
@@ -56,15 +58,37 @@ module ShopifyCli
56
58
  #
57
59
  def start(identifier, args)
58
60
  return for_ident(identifier) if running?(identifier)
59
- fork do
60
- pid_file = new(identifier, pid: Process.pid)
61
+
62
+ # Windows doesn't support forking process without extra gems, so we resort to spawning a new child process -
63
+ # that means that it dies along with the original process if it is interrupted. On UNIX, we fork the process so
64
+ # that it doesn't have to be restarted on every run.
65
+ if Context.new.windows?
66
+ pid_file = new(identifier)
67
+
68
+ # Make sure the file exists and is empty, otherwise Windows fails
69
+ File.open(pid_file.log_path, 'w') {}
70
+ pid = Process.spawn(
71
+ *args,
72
+ out: pid_file.log_path,
73
+ err: pid_file.log_path,
74
+ in: Context.new.windows? ? "nul" : "/dev/null",
75
+ )
76
+ pid_file.pid = pid
61
77
  pid_file.write
62
- STDOUT.reopen(pid_file.log_path, "w")
63
- STDERR.reopen(pid_file.log_path, "w")
64
- STDIN.reopen("/dev/null", "r")
65
- Process.setsid
66
- exec(*args)
78
+
79
+ Process.detach(pid)
80
+ else
81
+ fork do
82
+ pid_file = new(identifier, pid: Process.pid)
83
+ pid_file.write
84
+ STDOUT.reopen(pid_file.log_path, "w")
85
+ STDERR.reopen(pid_file.log_path, "w")
86
+ STDIN.reopen("/dev/null", "r")
87
+ Process.setsid
88
+ exec(*args)
89
+ end
67
90
  end
91
+
68
92
  sleep(0.1)
69
93
  for_ident(identifier)
70
94
  end
@@ -104,24 +128,29 @@ module ShopifyCli
104
128
  end
105
129
  end
106
130
 
107
- def initialize(identifier, pid:, time: Time.now.strftime('%s')) # :nodoc:
131
+ def initialize(identifier, pid: nil, time: Time.now.strftime('%s')) # :nodoc:
108
132
  @identifier = identifier
109
133
  @pid = pid
110
134
  @time = time
111
- @pid_path = File.join(RUN_DIR, "#{identifier}.pid")
112
- @log_path = File.join(RUN_DIR, "#{identifier}.log")
135
+ FileUtils.mkdir_p(ShopifyCli::ProcessSupervision.run_dir)
136
+ @pid_path = File.join(ShopifyCli::ProcessSupervision.run_dir, "#{identifier}.pid")
137
+ @log_path = File.join(ShopifyCli::ProcessSupervision.run_dir, "#{identifier}.log")
113
138
  end
114
139
 
115
140
  ##
116
141
  # will attempt to shutdown a running process
117
142
  #
143
+ # #### Parameters
144
+ #
145
+ # * `ctx` - the context of this command
146
+ #
118
147
  # #### Returns
119
148
  #
120
149
  # * `stopped` - [true, false]
121
150
  #
122
151
  def stop
123
- unlink
124
152
  kill_proc
153
+ unlink
125
154
  true
126
155
  rescue
127
156
  false
@@ -157,7 +186,11 @@ module ShopifyCli
157
186
  end
158
187
 
159
188
  def kill_proc
160
- kill(-pid) # process group
189
+ if Context.new.windows?
190
+ kill(pid)
191
+ else
192
+ kill(-pid) # process group
193
+ end
161
194
  rescue Errno::ESRCH
162
195
  begin
163
196
  kill(pid)
@@ -167,12 +200,18 @@ module ShopifyCli
167
200
  end
168
201
 
169
202
  def kill(id)
170
- Process.kill('TERM', id)
171
- 50.times do
172
- sleep 0.1
173
- break unless stat(id)
203
+ ctx = Context.new
204
+
205
+ # Windows does not handle SIGTERM, go straight to SIGKILL
206
+ unless ctx.windows?
207
+ Process.kill('TERM', id)
208
+ 50.times do
209
+ sleep 0.1
210
+ break unless stat(id)
211
+ end
174
212
  end
175
213
  Process.kill('KILL', id) if stat(id)
214
+ sleep(0.1) if ctx.windows? # Give Windows a second to actually close the process
176
215
  end
177
216
 
178
217
  def stat(id)
@@ -15,6 +15,10 @@ module ShopifyCli
15
15
  # will get an instance of the project that the user is currently operating
16
16
  # on. This is used for access to project resources.
17
17
  #
18
+ # #### Parameters
19
+ #
20
+ # * `force_reload` - whether to force a reload of the project files
21
+ #
18
22
  # #### Returns
19
23
  #
20
24
  # * `project` - a Project instance if the user is currently in the project.
@@ -29,8 +33,8 @@ module ShopifyCli
29
33
  #
30
34
  # project = ShopifyCli::Project.current
31
35
  #
32
- def current
33
- at(Dir.pwd)
36
+ def current(force_reload: false)
37
+ at(Dir.pwd, force_reload: force_reload)
34
38
  end
35
39
 
36
40
  ##
@@ -93,13 +97,17 @@ module ShopifyCli
93
97
 
94
98
  private
95
99
 
96
- def directory(dir)
100
+ def directory(dir, force_reload: false)
101
+ @dir = nil if force_reload
102
+
97
103
  @dir ||= Hash.new { |h, k| h[k] = __directory(k) }
98
104
  @dir[dir]
99
105
  end
100
106
 
101
- def at(dir)
102
- proj_dir = directory(dir)
107
+ def at(dir, force_reload: false)
108
+ @at = nil if force_reload
109
+
110
+ proj_dir = directory(dir, force_reload: force_reload)
103
111
  unless proj_dir
104
112
  raise(ShopifyCli::Abort, Context.message('core.project.error.not_in_project'))
105
113
  end
@@ -109,7 +117,7 @@ module ShopifyCli
109
117
 
110
118
  def __directory(curr)
111
119
  loop do
112
- return nil if curr == '/'
120
+ return nil if curr == '/' || /^[A-Z]:\/$/.match?(curr)
113
121
  file = File.join(curr, '.shopify-cli.yml')
114
122
  return curr if File.exist?(file)
115
123
  curr = File.dirname(curr)
@@ -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 ||= []
@@ -13,6 +14,7 @@ module ShopifyCli
13
14
  alias_method :all_loaded, :repository
14
15
 
15
16
  def inherited(klass)
17
+ super
16
18
  repository << klass
17
19
  klass.project_type = @current_type
18
20
  klass.project_load_shallow = @shallow_load
@@ -31,7 +33,7 @@ module ShopifyCli
31
33
 
32
34
  def load_all
33
35
  Dir.glob(File.join(ShopifyCli::ROOT, 'lib', 'project_types', '*', 'cli.rb')).map do |filepath|
34
- load_type(filepath.split(File::Separator)[-2], true)
36
+ load_type(filepath.split(File::Separator)[-2].to_sym, true)
35
37
  end
36
38
  end
37
39
 
@@ -53,10 +55,6 @@ module ShopifyCli
53
55
  const_get(@project_creator_command_class)
54
56
  end
55
57
 
56
- def hidden_project_type
57
- @hidden = true
58
- end
59
-
60
58
  def register_command(const, cmd)
61
59
  return if project_load_shallow
62
60
  Context.new.abort(
@@ -67,7 +65,7 @@ module ShopifyCli
67
65
 
68
66
  def register_task(task, name)
69
67
  return if project_load_shallow
70
- Task::Registry.add(const_get(task), name)
68
+ ShopifyCli::Task.register(task, name)
71
69
  end
72
70
 
73
71
  def register_messages(messages)
@@ -8,6 +8,7 @@ module ShopifyCli
8
8
  cmd = new(@ctx)
9
9
  args = cmd.options.parse(@_options, args[1..-1] || [])
10
10
  return call_help(parent_command, command_name) if cmd.options.help
11
+ run_prerequisites
11
12
  cmd.call(args, command_name)
12
13
  end
13
14
  end
@@ -2,9 +2,9 @@ require 'shopify_cli'
2
2
 
3
3
  module ShopifyCli
4
4
  class Task
5
- def self.call(*args)
5
+ def self.call(*args, **kwargs)
6
6
  task = new
7
- task.call(*args)
7
+ task.call(*args, **kwargs)
8
8
  end
9
9
  end
10
10
  end
@@ -12,15 +12,22 @@ module ShopifyCli
12
12
  end
13
13
 
14
14
  def [](name)
15
- @tasks[name]
15
+ class_or_proc = @tasks[name]
16
+ if class_or_proc.is_a?(Class)
17
+ class_or_proc
18
+ elsif class_or_proc.respond_to?(:call)
19
+ class_or_proc.call
20
+ else
21
+ class_or_proc
22
+ end
16
23
  end
17
24
  end
18
25
 
19
26
  Registry = TaskRegistry.new
20
27
 
21
- def self.register(task, name, path)
22
- autoload(task, path)
23
- Registry.add(const_get(task), name)
28
+ def self.register(task, name, path = nil)
29
+ autoload(task, path) if path
30
+ Registry.add(-> () { const_get(task) }, name)
24
31
  end
25
32
 
26
33
  register :CreateApiClient, :create_api_client, 'shopify-cli/tasks/create_api_client'
@@ -3,28 +3,84 @@ require 'shopify_cli'
3
3
  module ShopifyCli
4
4
  module Tasks
5
5
  class EnsureEnv < ShopifyCli::Task
6
- def call(ctx)
6
+ def call(ctx, regenerate: false, required: [:api_key, :secret])
7
7
  @ctx = ctx
8
- Resources::EnvFile.read(ctx.root)
9
- rescue Errno::ENOENT
10
- ask
8
+ env_data =
9
+ begin
10
+ Resources::EnvFile.parse_external_env
11
+ rescue Errno::ENOENT
12
+ {}
13
+ end
14
+
15
+ return {} if !regenerate && required.all? { |property| env_data[property] }
16
+
17
+ org = fetch_org
18
+ write_env(env_data, org)
19
+ org
20
+ end
21
+
22
+ private
23
+
24
+ def fetch_org
25
+ orgs = PartnersAPI::Organizations.fetch_with_app(@ctx)
26
+ org_id = if orgs.count == 1
27
+ orgs.first["id"]
28
+ else
29
+ CLI::UI::Prompt.ask(@ctx.message('core.tasks.ensure_env.organization_select')) do |handler|
30
+ orgs.each do |org|
31
+ handler.option(
32
+ @ctx.message('core.partners_api.org_name_and_id', org['businessName'], org['id'])
33
+ ) { org["id"] }
34
+ end
35
+ end
36
+ end
37
+ orgs.find { |o| o["id"] == org_id }
38
+ end
39
+
40
+ def get_app(org_id, apps)
41
+ if apps.count == 1
42
+ apps.first
43
+ elsif apps.count == 0
44
+ @ctx.puts(@ctx.message('core.tasks.ensure_env.no_apps'))
45
+ title = CLI::UI::Prompt.ask(@ctx.message('core.tasks.ensure_env.app_name'))
46
+ type = CLI::UI::Prompt.ask(@ctx.message('core.tasks.ensure_env.app_type.select')) do |handler|
47
+ handler.option(@ctx.message('core.tasks.ensure_env.app_type.select_public')) { 'public' }
48
+ handler.option(@ctx.message('core.tasks.ensure_env.app_type.select_custom')) { 'custom' }
49
+ end
50
+ ShopifyCli::Tasks::CreateApiClient.call(@ctx, org_id: org_id, title: title, type: type)
51
+ else
52
+ CLI::UI::Prompt.ask(@ctx.message('core.tasks.ensure_env.app_select')) do |handler|
53
+ apps.each { |app| handler.option(app["title"]) { app } }
54
+ end
55
+ end
56
+ end
57
+
58
+ def get_shop(shops, id)
59
+ if shops.count == 1
60
+ shop = shops.first["shopDomain"]
61
+ elsif shops.count == 0
62
+ @ctx.puts(@ctx.message('core.tasks.ensure_env.no_development_stores', id))
63
+ else
64
+ shop = CLI::UI::Prompt.ask(@ctx.message('core.tasks.ensure_env.development_store_select')) do |handler|
65
+ shops.each { |s| handler.option(s["shopName"]) { s["shopDomain"] } }
66
+ end
67
+ end
68
+ shop
11
69
  end
12
70
 
13
- def ask
14
- api_key = CLI::UI.ask(@ctx.message('core.tasks.ensure_env.api_key_question'))
15
- api_secret = CLI::UI.ask(@ctx.message('core.tasks.ensure_env.api_secret_key_question'))
16
- shop = CLI::UI.ask(@ctx.message('core.tasks.ensure_env.development_store_question'))
71
+ def write_env(env_data, org)
72
+ id = org['id']
73
+ app = get_app(id, org['apps'])
17
74
 
18
- shop.gsub!(/https?\:\/\//, '')
75
+ env_data[:shop] = get_shop(org['stores'], id)
76
+ env_data[:api_key] = app["apiKey"]
77
+ env_data[:secret] = app["apiSecretKeys"].first["secret"]
78
+ env_data[:scopes] = 'write_products,write_customers,write_draft_orders' if env_data[:scopes].nil?
79
+ env_data[:extra] = {} if env_data[:extra].nil?
19
80
 
20
- env = Resources::EnvFile.new(
21
- api_key: api_key,
22
- secret: api_secret,
23
- shop: shop,
24
- host: ShopifyCli::Tunnel.start(@ctx),
25
- scopes: 'write_products,write_customers,write_draft_orders',
81
+ Resources::EnvFile.new(
82
+ env_data
26
83
  ).write(@ctx)
27
- env
28
84
  end
29
85
  end
30
86
  end