shopify-cli 2.15.2 → 2.15.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +4 -4
  2. data/.vscode/settings.json +1 -2
  3. data/CHANGELOG.md +69 -22
  4. data/Gemfile.lock +1 -1
  5. data/Rakefile +8 -0
  6. data/ext/javy/hashes/javy-arm-macos-v0.3.0.gz.sha256 +1 -0
  7. data/ext/javy/hashes/javy-x86_64-linux-v0.3.0.gz.sha256 +1 -0
  8. data/ext/javy/hashes/javy-x86_64-macos-v0.3.0.gz.sha256 +1 -0
  9. data/ext/javy/hashes/javy-x86_64-windows-v0.3.0.gz.sha256 +1 -0
  10. data/ext/javy/version +1 -1
  11. data/ext/shopify-extensions/version +1 -1
  12. data/lib/project_types/extension/cli.rb +4 -0
  13. data/lib/project_types/extension/commands/check.rb +6 -1
  14. data/lib/project_types/extension/forms/questions/ask_template.rb +1 -2
  15. data/lib/project_types/extension/messages/messages.rb +0 -2
  16. data/lib/project_types/extension/models/development_server_requirements.rb +1 -0
  17. data/lib/project_types/extension/models/specification_handlers/beacon_extension.rb +57 -0
  18. data/lib/project_types/extension/models/specification_handlers/beacon_extension_utils/script_config.rb +33 -0
  19. data/lib/project_types/extension/models/specification_handlers/beacon_extension_utils/script_config_repository.rb +75 -0
  20. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +16 -1
  21. data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +4 -1
  22. data/lib/project_types/extension/tasks/configure_options.rb +2 -1
  23. data/lib/project_types/extension/tasks/convert_server_config.rb +13 -2
  24. data/lib/project_types/extension/tasks/merge_server_config.rb +5 -2
  25. data/lib/project_types/script/cli.rb +1 -0
  26. data/lib/project_types/script/layers/application/create_script.rb +14 -6
  27. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +6 -21
  28. data/lib/project_types/script/layers/infrastructure/sparse_checkout_details.rb +35 -0
  29. data/lib/project_types/theme/cli.rb +1 -0
  30. data/lib/project_types/theme/commands/check.rb +4 -1
  31. data/lib/project_types/theme/commands/open.rb +2 -2
  32. data/lib/project_types/theme/commands/push.rb +1 -3
  33. data/lib/project_types/theme/commands/share.rb +56 -0
  34. data/lib/project_types/theme/messages/messages.rb +24 -3
  35. data/lib/shopify_cli/changelog.rb +97 -25
  36. data/lib/shopify_cli/command_options/command_serve_options.rb +10 -0
  37. data/lib/shopify_cli/commands/app/serve.rb +7 -7
  38. data/lib/shopify_cli/commands/login.rb +5 -2
  39. data/lib/shopify_cli/context.rb +13 -0
  40. data/lib/shopify_cli/identity_auth.rb +24 -4
  41. data/lib/shopify_cli/messages/messages.rb +17 -7
  42. data/lib/shopify_cli/release.rb +1 -1
  43. data/lib/shopify_cli/services/app/create/rails_service.rb +9 -1
  44. data/lib/shopify_cli/services/app/serve/node_service.rb +2 -25
  45. data/lib/shopify_cli/services/app/serve/php_service.rb +2 -25
  46. data/lib/shopify_cli/services/app/serve/rails_service.rb +8 -28
  47. data/lib/shopify_cli/services/app/serve/serve_service.rb +57 -0
  48. data/lib/shopify_cli/services.rb +1 -0
  49. data/lib/shopify_cli/tasks/update_dashboard_urls.rb +7 -9
  50. data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +1 -0
  51. data/lib/shopify_cli/theme/dev_server/watcher.rb +2 -8
  52. data/lib/shopify_cli/theme/dev_server.rb +3 -2
  53. data/lib/shopify_cli/theme/theme.rb +21 -7
  54. data/lib/shopify_cli/theme/theme_admin_api.rb +23 -8
  55. data/lib/shopify_cli/tunnel.rb +3 -13
  56. data/lib/shopify_cli/version.rb +1 -1
  57. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +8 -0
  58. metadata +12 -2
@@ -23,19 +23,23 @@ module Script
23
23
  )
24
24
 
25
25
  # remove the need to pass the whole extension-point object to the infra layer
26
- sparse_checkout_repo = extension_point.libraries.for(language).repo
27
26
  type = extension_point.dasherize_type
28
27
  domain = extension_point.domain
29
28
 
29
+ sparse_checkout_details = Infrastructure::SparseCheckoutDetails.new(
30
+ repo: extension_point.libraries.for(language).repo,
31
+ branch: sparse_checkout_branch,
32
+ path: "#{domain}/#{language}/#{type}/default",
33
+ input_queries_enabled: input_queries_enabled?,
34
+ )
35
+
30
36
  project_creator = Infrastructure::Languages::ProjectCreator.for(
31
37
  ctx: ctx,
32
38
  language: language,
33
39
  type: type,
34
40
  project_name: title,
35
41
  path_to_project: project.id,
36
- sparse_checkout_repo: sparse_checkout_repo,
37
- sparse_checkout_branch: sparse_checkout_branch,
38
- sparse_checkout_set_path: "#{domain}/#{language}/#{type}/default"
42
+ sparse_checkout_details: sparse_checkout_details,
39
43
  )
40
44
 
41
45
  install_dependencies(ctx, language, title, project_creator)
@@ -49,12 +53,12 @@ module Script
49
53
  task_runner = Infrastructure::Languages::TaskRunner.for(ctx, language)
50
54
  CLI::UI::Frame.open(ctx.message(
51
55
  "core.git.pulling_from_to",
52
- project_creator.sparse_checkout_repo,
56
+ project_creator.sparse_checkout_details.repo,
53
57
  title,
54
58
  )) do
55
59
  UI::StrictSpinner.spin(ctx.message(
56
60
  "core.git.pulling",
57
- project_creator.sparse_checkout_repo,
61
+ project_creator.sparse_checkout_details.repo,
58
62
  title,
59
63
  )) do |spinner|
60
64
  project_creator.setup_dependencies
@@ -75,6 +79,10 @@ module Script
75
79
  ensure
76
80
  script_project_repo.change_to_initial_directory
77
81
  end
82
+
83
+ def input_queries_enabled?
84
+ ShopifyCLI::Feature.enabled?(:scripts_beta_input_queries)
85
+ end
78
86
  end
79
87
  end
80
88
  end
@@ -10,9 +10,7 @@ module Script
10
10
  property! :type, accepts: String
11
11
  property! :project_name, accepts: String
12
12
  property! :path_to_project, accepts: String
13
- property! :sparse_checkout_repo, accepts: String
14
- property! :sparse_checkout_branch, accepts: String
15
- property! :sparse_checkout_set_path, accepts: String
13
+ property! :sparse_checkout_details, accepts: SparseCheckoutDetails
16
14
 
17
15
  def self.for(
18
16
  ctx:,
@@ -20,9 +18,7 @@ module Script
20
18
  type:,
21
19
  project_name:,
22
20
  path_to_project:,
23
- sparse_checkout_repo:,
24
- sparse_checkout_branch:,
25
- sparse_checkout_set_path:
21
+ sparse_checkout_details:
26
22
  )
27
23
 
28
24
  project_creators = {
@@ -36,33 +32,22 @@ module Script
36
32
  type: type,
37
33
  project_name: project_name,
38
34
  path_to_project: path_to_project,
39
- sparse_checkout_repo: sparse_checkout_repo,
40
- sparse_checkout_branch: sparse_checkout_branch,
41
- sparse_checkout_set_path: sparse_checkout_set_path
35
+ sparse_checkout_details: sparse_checkout_details,
42
36
  )
43
37
  end
44
38
 
45
39
  # the sparse checkout process is common to all script types
46
40
  def setup_dependencies
47
- setup_sparse_checkout
41
+ sparse_checkout_details.setup(ctx)
48
42
  clean
49
43
  end
50
44
 
51
45
  private
52
46
 
53
- def setup_sparse_checkout
54
- ShopifyCLI::Git.sparse_checkout(
55
- sparse_checkout_repo,
56
- sparse_checkout_set_path,
57
- sparse_checkout_branch,
58
- ctx
59
- )
60
- end
61
-
62
47
  def clean
63
- source = File.join(path_to_project, sparse_checkout_set_path, ".")
48
+ source = File.join(path_to_project, sparse_checkout_details.path, ".")
64
49
  FileUtils.cp_r(source, path_to_project)
65
- ctx.rm_rf(sparse_checkout_set_path.split("/")[0])
50
+ ctx.rm_rf(sparse_checkout_details.path.split("/")[0])
66
51
  ctx.rm_rf(".git")
67
52
  end
68
53
 
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Script
4
+ module Layers
5
+ module Infrastructure
6
+ class SparseCheckoutDetails
7
+ include SmartProperties
8
+ property! :repo, accepts: String
9
+ property! :branch, accepts: String
10
+ property! :path, accepts: String
11
+ property! :input_queries_enabled, accepts: [true, false]
12
+
13
+ def ==(other)
14
+ self.class == other.class &&
15
+ self.class.properties.all? { |name, _| self[name] == other[name] }
16
+ end
17
+
18
+ def setup(ctx)
19
+ ShopifyCLI::Git.sparse_checkout(repo, patterns_to_checkout, branch, ctx)
20
+ end
21
+
22
+ private
23
+
24
+ def patterns_to_checkout
25
+ paths = [path]
26
+ unless input_queries_enabled
27
+ paths << "!#{path}/#{ScriptProjectRepository::INPUT_QUERY_PATH}"
28
+ paths << "!#{path}/schema.graphql"
29
+ end
30
+ paths.join(" ")
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -16,6 +16,7 @@ module Theme
16
16
  subcommand :Package, "package", Project.project_filepath("commands/package")
17
17
  subcommand :Open, "open", Project.project_filepath("commands/open")
18
18
  subcommand :List, "list", Project.project_filepath("commands/list")
19
+ subcommand :Share, "share", Project.project_filepath("commands/share")
19
20
  subcommand :LanguageServer, "language-server", Project.project_filepath("commands/language_server")
20
21
  end
21
22
  ShopifyCLI::Commands.register("Theme::Command", "theme")
@@ -24,7 +24,10 @@ module Theme
24
24
  end
25
25
 
26
26
  def call(*)
27
- @theme_check.run
27
+ @theme_check.run!
28
+ rescue ThemeCheck::Cli::Abort, ThemeCheck::ThemeCheckError => e
29
+ raise ShopifyCLI::Abort,
30
+ ShopifyCLI::Context.message("theme.check.error", e.full_message)
28
31
  end
29
32
 
30
33
  def self.help
@@ -17,8 +17,8 @@ module Theme
17
17
  def call(_args, _name)
18
18
  theme = find_theme(**options.flags)
19
19
 
20
- @ctx.puts(@ctx.message("theme.open.details", theme.name, theme.editor_url))
21
- @ctx.open_url!(theme.preview_url)
20
+ @ctx.puts(@ctx.message("theme.open.details", theme.name, theme.preview_url, theme.editor_url))
21
+ @ctx.open_browser_url!(theme.preview_url)
22
22
  end
23
23
 
24
24
  def self.help
@@ -104,9 +104,7 @@ module Theme
104
104
 
105
105
  if unpublished
106
106
  name = theme || ask_theme_name
107
- new_theme = ShopifyCLI::Theme::Theme.new(@ctx, root: root, name: name, role: "unpublished")
108
- new_theme.create
109
- return new_theme
107
+ return ShopifyCLI::Theme::Theme.create_unpublished(@ctx, root: root, name: name)
110
108
  end
111
109
 
112
110
  if theme
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/theme/theme"
4
+ require "shopify_cli/theme/syncer"
5
+ require "project_types/theme/commands/common/root_helper"
6
+
7
+ module Theme
8
+ class Command
9
+ class Share < ShopifyCLI::Command::SubCommand
10
+ include Common::RootHelper
11
+
12
+ recommend_default_ruby_range
13
+
14
+ def call(_args, name)
15
+ root = root_value(options, name)
16
+ theme = create_theme(root)
17
+
18
+ upload(theme)
19
+
20
+ @ctx.done(done_message(theme))
21
+ end
22
+
23
+ def self.help
24
+ tool = ShopifyCLI::TOOL_NAME
25
+ @ctx.message("theme.share.help", tool, tool)
26
+ end
27
+
28
+ private
29
+
30
+ def create_theme(root)
31
+ ShopifyCLI::Theme::Theme.create_unpublished(@ctx, root: root)
32
+ end
33
+
34
+ def upload(theme)
35
+ syncer = ShopifyCLI::Theme::Syncer.new(@ctx, theme: theme)
36
+ syncer.start_threads
37
+
38
+ CLI::UI::Frame.open(upload_message(theme)) do
39
+ UI::SyncProgressBar.new(syncer).progress(:upload_theme!)
40
+ end
41
+
42
+ raise ShopifyCLI::AbortSilent if syncer.has_any_error?
43
+ ensure
44
+ syncer.shutdown
45
+ end
46
+
47
+ def upload_message(theme)
48
+ @ctx.message("theme.share.upload", theme.name, theme.id, theme.shop)
49
+ end
50
+
51
+ def done_message(theme)
52
+ @ctx.message("theme.share.done", theme.name, theme.preview_url, theme.editor_url)
53
+ end
54
+ end
55
+ end
56
+ end
@@ -8,8 +8,9 @@ module Theme
8
8
  Usage: {{command:%1$s theme [ %2$s ]}}
9
9
  HELP
10
10
  ensure_user_error: "You are not authorized to edit themes on %s.",
11
- ensure_user_try_this: "Make sure you are a user of that store, and allowed to edit themes.",
12
-
11
+ ensure_user_try_this: <<~ENSURE_USER,
12
+ Check if your user is activated, has permission to edit themes at the store, and try to re-login.
13
+ ENSURE_USER
13
14
  init: {
14
15
  help: <<~HELP,
15
16
  {{command:%s theme init}}: Clones a Git repository to use as a starting point for building a new theme.
@@ -181,7 +182,7 @@ module Theme
181
182
  CUSTOMIZE_OR_PREVIEW
182
183
  ensure_user: <<~ENSURE_USER,
183
184
  You are not authorized to edit themes on %s.
184
- Make sure you are a user of that store, and allowed to edit themes.
185
+ Check if your user is activated, has permission to edit themes at the store, and try to re-login.
185
186
  ENSURE_USER
186
187
  address_already_in_use: "The address \"%s\" is already in use.",
187
188
  try_port_option: "Use the --port=PORT option to serve the theme in a different port.",
@@ -191,6 +192,7 @@ module Theme
191
192
  Check your theme for errors, suggestions, and best practices.
192
193
  Usage: {{command:%s check}}
193
194
  HELP
195
+ error: "Theme check failed with error:\n%s",
194
196
  },
195
197
  delete: {
196
198
  help: <<~HELP,
@@ -262,6 +264,9 @@ module Theme
262
264
  details: <<~DETAILS,
263
265
  {{*}} {{bold:%s}}
264
266
 
267
+ Preview your theme:
268
+ {{green:%s}}
269
+
265
270
  Customize your theme in the Theme Editor:
266
271
  {{green:%s}}
267
272
 
@@ -285,6 +290,22 @@ module Theme
285
290
  Usage: {{command:%s theme list}}
286
291
  HELP
287
292
  },
293
+ share: {
294
+ help: <<~HELP,
295
+ {{command:%s theme share}}: Creates a shareable, unpublished, and new theme on your theme library with a randomized name.
296
+ Works like an alias to {{command:theme push -u -t=RANDOMIZED_NAME}}.
297
+
298
+ Usage: {{command:%s theme share [ ROOT ]}}
299
+ HELP
300
+ done: <<~DONE,
301
+ {{green:The {{bold:%s}} theme was pushed successfully}}
302
+
303
+ {{info:Share your theme preview:}}
304
+ {{underline:%s}}
305
+
306
+ DONE
307
+ upload: "Pushing theme files to %s (#%s) on %s",
308
+ },
288
309
  },
289
310
  }.freeze
290
311
  end
@@ -1,23 +1,32 @@
1
+ require "date"
1
2
  require "shopify_cli/sed"
3
+ require "octokit"
2
4
 
3
5
  module ShopifyCLI
4
6
  class Changelog
5
7
  CHANGELOG_FILE = File.join(ShopifyCLI::ROOT, "CHANGELOG.md")
8
+ CHANGE_CATEGORIES = %w(Added Changed Deprecated Removed Fixed Security)
6
9
 
7
10
  def initialize
8
11
  load(File.read(CHANGELOG_FILE))
9
12
  end
10
13
 
11
14
  def update_version!(new_version)
12
- Sed.new.replace_inline(
13
- CHANGELOG_FILE,
14
- "## \\[Unreleased\\]",
15
- "## [Unreleased]\\n\\n## Version #{new_version}"
16
- )
15
+ changes[new_version] = changes["Unreleased"]
16
+ changes[new_version][:date] = Date.today.iso8601
17
+ changes["Unreleased"] = { changes: [], date: nil }
18
+ save!
19
+ end
20
+
21
+ def update!
22
+ pr = pr_for_current_branch
23
+ category = CLI::UI::Prompt.ask("What type of change?", options: CHANGE_CATEGORIES)
24
+ add_change(category, { pr_id: pr.number, desc: pr.title })
25
+ save!
17
26
  end
18
27
 
19
28
  def release_notes(version)
20
- changes[version].map do |change_category, changes|
29
+ changes[version][:changes].map do |change_category, changes|
21
30
  <<~CHANGES
22
31
  ### #{change_category}
23
32
  #{changes.map { |change| entry(**change) }.join("\n")}
@@ -25,17 +34,61 @@ module ShopifyCLI
25
34
  end.join("\n")
26
35
  end
27
36
 
37
+ def add_change(category, change)
38
+ changes["Unreleased"][:changes][category] << change
39
+ end
40
+
28
41
  def entry(pr_id:, desc:)
29
42
  "* [##{pr_id}](https://github.com/Shopify/shopify-cli/pull/#{pr_id}): #{desc}"
30
43
  end
31
44
 
45
+ def full_contents
46
+ sorted_changes = changes.each_key.sort_by do |change|
47
+ if change == "Unreleased"
48
+ [Float::INFINITY] * 3 # end of the list
49
+ else
50
+ major, minor, patch = change.split(".").map(&:to_i)
51
+ [major, minor, patch]
52
+ end
53
+ end.reverse
54
+ [
55
+ heading,
56
+ *sorted_changes.each.map { |version| release_notes_with_header(version) }.join,
57
+ remainder,
58
+ ].map { |section| section.chomp << "\n" }.join
59
+ end
60
+
61
+ def save!
62
+ File.write(CHANGELOG_FILE, full_contents)
63
+ end
64
+
32
65
  private
33
66
 
67
+ attr_reader :heading, :remainder
68
+
69
+ def release_notes_with_header(version)
70
+ header_line =
71
+ if version == "Unreleased"
72
+ "[Unreleased]"
73
+ else
74
+ date = changes[version][:date]
75
+ "Version #{version}#{" - #{date}" if date}"
76
+ end
77
+
78
+ [
79
+ "## #{header_line}",
80
+ release_notes(version),
81
+ ].reject(&:empty?).map { |section| section.chomp << "\n\n" }.join
82
+ end
83
+
34
84
  def changes
35
85
  @changes ||= Hash.new do |h, k|
36
- h[k] = Hash.new do |h2, k2|
37
- h2[k2] = []
38
- end
86
+ h[k] = {
87
+ date: nil,
88
+ changes: Hash.new do |h2, k2|
89
+ h2[k2] = []
90
+ end,
91
+ }
39
92
  end
40
93
  end
41
94
 
@@ -43,34 +96,53 @@ module ShopifyCLI
43
96
  state = :initial
44
97
  change_category = nil
45
98
  current_version = nil
99
+ @heading = ""
46
100
  @remainder = ""
47
101
  log.each_line do |line|
48
102
  case state
49
103
  when :initial
50
- next unless line.chomp == "\#\# [Unreleased]"
51
- state = :unreleased
52
- current_version = "Unreleased"
53
- when :unreleased, :last_version
104
+ if line.chomp == "\#\# [Unreleased]"
105
+ state = :unreleased
106
+ current_version = "Unreleased"
107
+ # Ensure Unreleased changeset exists even if no changes have happened yet
108
+ changes["Unreleased"]
109
+ else
110
+ @heading << line
111
+ end
112
+ when :unreleased, :prior_versions
54
113
  if /\A\#\#\# (?<category>\w+)/ =~ line
55
114
  change_category = category
56
- elsif %r{\A\* \[\#(?<pr_id>\d+)\]\(https://github.com/Shopify/shopify-cli/pull/\d+\): (?<desc>.+)\n} =~ line
57
- changes[current_version][change_category] << { pr_id: pr_id, desc: desc }
58
- elsif /\A\#\# Version (?<version>\d+\.\d+\.\d+)/ =~ line
115
+ elsif %r{\A\* \[\#(?<id>\d+)\]\(https://github.com/Shopify/shopify-cli/pull/\k<id>\): (?<desc>.+)\n} =~ line
116
+ changes[current_version][:changes][change_category] << { pr_id: id, desc: desc }
117
+ elsif /\A\#\# Version (?<version>\d+\.\d+\.\d+)( - (?<date>\d{4}-\d{2}-\d{2}))?/ =~ line
59
118
  current_version = version
60
- state =
61
- case state
62
- when :unreleased
63
- :last_version
64
- else
65
- :finished
66
- end
119
+ state = :prior_versions
120
+ major, minor, _patch = current_version.split(".")
121
+ if major.to_i <= 2 && minor.to_i < 7
122
+ # Changelog starts to become irregular in 2.6.x
123
+ state = :finished
124
+ end
125
+ changes[current_version][:date] = date unless state == :finished
67
126
  elsif !line.match?(/\s*\n/)
68
127
  raise "Unrecognized line: #{line.inspect}"
69
128
  end
70
- when :finished
71
- @remainder << line
72
129
  end
130
+ @remainder << line if state == :finished
73
131
  end
74
132
  end
133
+
134
+ def pr_for_current_branch
135
+ current_branch = %x(git branch --show-current).chomp
136
+ search_term = "repo:Shopify/shopify-cli is:pr is:open head:#{current_branch}"
137
+ results = Octokit::Client.new.search_issues(search_term)
138
+ case results.total_count
139
+ when 0
140
+ raise "PR not opened yet!"
141
+ when (2..)
142
+ raise "Multiple open PRs, not sure which one to use for changelog!"
143
+ end
144
+
145
+ results.items.first
146
+ end
75
147
  end
76
148
  end
@@ -20,6 +20,10 @@ module ShopifyCLI
20
20
  end
21
21
  host
22
22
  end
23
+
24
+ def no_update
25
+ options.flags[:no_update] || false
26
+ end
23
27
  end
24
28
  end
25
29
 
@@ -37,6 +41,12 @@ module ShopifyCLI
37
41
  parser.on("--port=PORT") { |port| flags[:port] = port }
38
42
  end
39
43
  end
44
+
45
+ def parse_no_update_option
46
+ options do |parser, flags|
47
+ parser.on("--no-update") { flags[:no_update] = true }
48
+ end
49
+ end
40
50
  end
41
51
  end
42
52
  end
@@ -8,12 +8,9 @@ module ShopifyCLI
8
8
 
9
9
  recommend_default_ruby_range
10
10
 
11
- options do |parser, flags|
12
- parser.on("--host=HOST") do |h|
13
- flags[:host] = h.gsub('"', "")
14
- end
15
- parser.on("--port=PORT") { |port| flags[:port] = port }
16
- end
11
+ parse_host_option
12
+ parse_port_option
13
+ parse_no_update_option
17
14
 
18
15
  def call(*)
19
16
  case detect_app
@@ -21,18 +18,21 @@ module ShopifyCLI
21
18
  Services::App::Serve::RailsService.call(
22
19
  host: host,
23
20
  port: port,
21
+ no_update: no_update,
24
22
  context: @ctx
25
23
  )
26
24
  when :node
27
25
  Services::App::Serve::NodeService.call(
28
26
  host: host,
29
27
  port: port,
28
+ no_update: no_update,
30
29
  context: @ctx
31
30
  )
32
31
  when :php
33
32
  Services::App::Serve::PHPService.call(
34
33
  host: host,
35
34
  port: port,
35
+ no_update: no_update,
36
36
  context: @ctx
37
37
  )
38
38
  end
@@ -43,7 +43,7 @@ module ShopifyCLI
43
43
  end
44
44
 
45
45
  def self.extended_help
46
- ShopifyCLI::Context.message("app.core.serve.extended_help")
46
+ ShopifyCLI::Context.message("core.app.serve.extended_help")
47
47
  end
48
48
  end
49
49
  end
@@ -30,7 +30,7 @@ module ShopifyCLI
30
30
  if @ctx.ci? && (password = options.flags[:password] || @ctx.getenv("SHOPIFY_PASSWORD"))
31
31
  ShopifyCLI::DB.set(shopify_exchange_token: password)
32
32
  else
33
- IdentityAuth.new(ctx: @ctx).authenticate
33
+ IdentityAuth.new(ctx: @ctx).authenticate(spinner: true)
34
34
  org = select_organization
35
35
  ShopifyCLI::DB.set(organization_id: org["id"].to_i) unless org.nil?
36
36
  Whoami.call([], "whoami")
@@ -75,7 +75,10 @@ module ShopifyCLI
75
75
  private
76
76
 
77
77
  def select_organization
78
- organizations = ShopifyCLI::PartnersAPI::Organizations.fetch_all(@ctx)
78
+ organizations = []
79
+ CLI::UI::Spinner.spin(@ctx.message("core.login.spinner.loading_organizations")) do
80
+ organizations = ShopifyCLI::PartnersAPI::Organizations.fetch_all(@ctx)
81
+ end
79
82
 
80
83
  if organizations.count == 0
81
84
  nil
@@ -4,6 +4,7 @@ require "fileutils"
4
4
  require "rbconfig"
5
5
  require "net/http"
6
6
  require "json"
7
+ require "bundler"
7
8
 
8
9
  module ShopifyCLI
9
10
  ##
@@ -642,6 +643,18 @@ module ShopifyCLI
642
643
  end
643
644
  end
644
645
 
646
+ # Uses bundle to grab the version of a gem
647
+ #
648
+ # #### Parameters
649
+ # - gem: the name of the gem to check
650
+ #
651
+ # #### Returns
652
+ # - version: a Semantic::Version object with the gem version
653
+ def ruby_gem_version(gem)
654
+ version = Bundler.load.specs.find { |s| s.name == gem }.version
655
+ ::Semantic::Version.new(version.to_s)
656
+ end
657
+
645
658
  private
646
659
 
647
660
  def ctx_path(fname)
@@ -56,8 +56,10 @@ module ShopifyCLI
56
56
 
57
57
  attr_accessor :response_query
58
58
 
59
- def authenticate
60
- return if refresh_exchange_tokens || refresh_access_tokens
59
+ def authenticate(spinner: false)
60
+ return if with_spinner(spinner, ctx.message("core.login.spinner.initiating")) do
61
+ attempt_reauthenticate
62
+ end
61
63
 
62
64
  initiate_authentication
63
65
 
@@ -66,7 +68,21 @@ module ShopifyCLI
66
68
  rescue IdentityAuth::Timeout => e
67
69
  ctx.abort(e.message)
68
70
  end
69
- request_exchange_tokens
71
+ with_spinner(spinner, ctx.message("core.login.spinner.finalizing")) do
72
+ request_exchange_tokens
73
+ end
74
+ end
75
+
76
+ def with_spinner(spinner, message, &block)
77
+ result = nil
78
+ if spinner
79
+ CLI::UI::Spinner.spin(message) do
80
+ result = block.call
81
+ end
82
+ else
83
+ result = block.call
84
+ end
85
+ result
70
86
  end
71
87
 
72
88
  def fetch_or_auth_partners_token
@@ -100,10 +116,14 @@ module ShopifyCLI
100
116
  end
101
117
 
102
118
  def reauthenticate
103
- return if refresh_exchange_tokens || refresh_access_tokens
119
+ return if attempt_reauthenticate
104
120
  ctx.abort(ctx.message("core.identity_auth.error.reauthenticate", ShopifyCLI::TOOL_NAME))
105
121
  end
106
122
 
123
+ def attempt_reauthenticate
124
+ refresh_exchange_tokens || refresh_access_tokens
125
+ end
126
+
107
127
  def code_challenge
108
128
  @code_challenge ||= Base64.urlsafe_encode64(
109
129
  OpenSSL::Digest::SHA256.digest(code_verifier),