shopify-cli 2.15.2 → 2.15.5

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 (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),