shopify-cli 2.15.0 → 2.15.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/stale.yml +7 -2
  3. data/.vscode/settings.json +1 -2
  4. data/CHANGELOG.md +70 -19
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +39 -7
  7. data/Rakefile +48 -0
  8. data/ext/javy/hashes/javy-arm-macos-v0.3.0.gz.sha256 +1 -0
  9. data/ext/javy/hashes/javy-x86_64-linux-v0.3.0.gz.sha256 +1 -0
  10. data/ext/javy/hashes/javy-x86_64-macos-v0.3.0.gz.sha256 +1 -0
  11. data/ext/javy/hashes/javy-x86_64-windows-v0.3.0.gz.sha256 +1 -0
  12. data/ext/javy/version +1 -1
  13. data/ext/shopify-extensions/version +1 -1
  14. data/lib/project_types/extension/commands/check.rb +6 -1
  15. data/lib/project_types/extension/forms/questions/ask_template.rb +5 -8
  16. data/lib/project_types/extension/messages/messages.rb +11 -3
  17. data/lib/project_types/extension/models/development_server_requirements.rb +14 -7
  18. data/lib/project_types/extension/models/server_config/root.rb +2 -0
  19. data/lib/project_types/extension/models/specification_handlers/beacon_extension.rb +57 -0
  20. data/lib/project_types/extension/models/specification_handlers/beacon_extension_utils/script_config.rb +33 -0
  21. data/lib/project_types/extension/models/specification_handlers/beacon_extension_utils/script_config_repository.rb +75 -0
  22. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +16 -1
  23. data/lib/project_types/extension/tasks/configure_options.rb +2 -1
  24. data/lib/project_types/extension/tasks/convert_server_config.rb +13 -2
  25. data/lib/project_types/extension/tasks/merge_server_config.rb +5 -2
  26. data/lib/project_types/script/cli.rb +1 -0
  27. data/lib/project_types/script/config/extension_points.yml +18 -0
  28. data/lib/project_types/script/layers/application/create_script.rb +14 -6
  29. data/lib/project_types/script/layers/infrastructure/errors.rb +17 -0
  30. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +6 -21
  31. data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
  32. data/lib/project_types/script/layers/infrastructure/sparse_checkout_details.rb +35 -0
  33. data/lib/project_types/script/messages/messages.rb +3 -0
  34. data/lib/project_types/script/ui/error_handler.rb +11 -0
  35. data/lib/project_types/theme/cli.rb +1 -0
  36. data/lib/project_types/theme/commands/check.rb +4 -1
  37. data/lib/project_types/theme/commands/open.rb +2 -2
  38. data/lib/project_types/theme/commands/push.rb +1 -3
  39. data/lib/project_types/theme/commands/serve.rb +1 -0
  40. data/lib/project_types/theme/commands/share.rb +56 -0
  41. data/lib/project_types/theme/messages/messages.rb +71 -11
  42. data/lib/shopify_cli/changelog.rb +148 -0
  43. data/lib/shopify_cli/command.rb +7 -0
  44. data/lib/shopify_cli/command_options/command_serve_options.rb +10 -0
  45. data/lib/shopify_cli/commands/app/serve.rb +7 -7
  46. data/lib/shopify_cli/commands/login.rb +5 -2
  47. data/lib/shopify_cli/context.rb +13 -0
  48. data/lib/shopify_cli/git.rb +36 -0
  49. data/lib/shopify_cli/identity_auth.rb +24 -4
  50. data/lib/shopify_cli/messages/messages.rb +26 -5
  51. data/lib/shopify_cli/release.rb +194 -0
  52. data/lib/shopify_cli/sed.rb +19 -0
  53. data/lib/shopify_cli/services/app/create/rails_service.rb +10 -2
  54. data/lib/shopify_cli/services/app/serve/node_service.rb +2 -25
  55. data/lib/shopify_cli/services/app/serve/php_service.rb +2 -25
  56. data/lib/shopify_cli/services/app/serve/rails_service.rb +8 -28
  57. data/lib/shopify_cli/services/app/serve/serve_service.rb +57 -0
  58. data/lib/shopify_cli/services.rb +1 -0
  59. data/lib/shopify_cli/tasks/update_dashboard_urls.rb +7 -9
  60. data/lib/shopify_cli/theme/dev_server/hot-reload.js +40 -13
  61. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +1 -1
  62. data/lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb +51 -0
  63. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +6 -1
  64. data/lib/shopify_cli/theme/dev_server/local_assets.rb +1 -1
  65. data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +35 -0
  66. data/lib/shopify_cli/theme/dev_server/remote_watcher.rb +44 -0
  67. data/lib/shopify_cli/theme/dev_server/watcher.rb +2 -8
  68. data/lib/shopify_cli/theme/dev_server.rb +18 -5
  69. data/lib/shopify_cli/theme/file.rb +15 -4
  70. data/lib/shopify_cli/theme/syncer/checksums.rb +60 -0
  71. data/lib/shopify_cli/theme/syncer/forms/apply_to_all.rb +39 -0
  72. data/lib/shopify_cli/theme/syncer/forms/apply_to_all_form.rb +35 -0
  73. data/lib/shopify_cli/theme/syncer/forms/base_strategy_form.rb +62 -0
  74. data/lib/shopify_cli/theme/syncer/forms/select_delete_strategy.rb +27 -0
  75. data/lib/shopify_cli/theme/syncer/forms/select_update_strategy.rb +28 -0
  76. data/lib/shopify_cli/theme/syncer/ignore_helper.rb +33 -0
  77. data/lib/shopify_cli/theme/syncer/json_delete_handler.rb +51 -0
  78. data/lib/shopify_cli/theme/syncer/json_update_handler.rb +82 -0
  79. data/lib/shopify_cli/theme/syncer/merger.rb +53 -0
  80. data/lib/shopify_cli/theme/syncer/operation.rb +1 -1
  81. data/lib/shopify_cli/theme/syncer.rb +79 -63
  82. data/lib/shopify_cli/theme/theme.rb +26 -4
  83. data/lib/shopify_cli/theme/theme_admin_api.rb +23 -8
  84. data/lib/shopify_cli/thread_pool/job.rb +10 -2
  85. data/lib/shopify_cli/thread_pool.rb +15 -3
  86. data/lib/shopify_cli/tunnel.rb +9 -0
  87. data/lib/shopify_cli/version.rb +1 -1
  88. data/shopify-cli.gemspec +3 -1
  89. data/vendor/deps/cli-ui/lib/cli/ui/os.rb +8 -0
  90. metadata +30 -3
@@ -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.
@@ -101,13 +102,14 @@ module Theme
101
102
  Usage: {{command:%s theme serve [ ROOT ]}}
102
103
 
103
104
  Options:
104
- {{command:--port=PORT}} Local port to serve theme preview from.
105
- {{command:--poll}} Force polling to detect file changes.
106
- {{command:--host=HOST}} Set which network interface the web server listens on. The default value is 127.0.0.1.
107
- {{command:--live-reload=MODE}} The live reload mode switches the server behavior when a file is modified:
108
- - {{command:hot-reload}} Hot reloads local changes to CSS and sections (default)
109
- - {{command:full-page}} Always refreshes the entire page
110
- - {{command:off}} Deactivate live reload
105
+ {{command:--port=PORT}} Local port to serve theme preview from.
106
+ {{command:--poll}} Force polling to detect file changes.
107
+ {{command:--host=HOST}} Set which network interface the web server listens on. The default value is 127.0.0.1.
108
+ {{command:--theme-editor-sync}} Synchronize Theme Editor updates in the local theme files.
109
+ {{command:--live-reload=MODE}} The live reload mode switches the server behavior when a file is modified:
110
+ - {{command:hot-reload}} Hot reloads local changes to CSS and sections (default)
111
+ - {{command:full-page}} Always refreshes the entire page
112
+ - {{command:off}} Deactivate live reload
111
113
  HELP
112
114
  reload_mode_is_not_valid: "The live reload mode `%s` is not valid.",
113
115
  try_a_valid_reload_mode: "Try a valid live reload mode: %s.",
@@ -121,18 +123,56 @@ module Theme
121
123
  fixed: "Fixed",
122
124
  },
123
125
  },
126
+ syncer: {
127
+ forms: {
128
+ apply_to_all: {
129
+ title: "Would like apply this to all the other %s files?",
130
+ yes: "Yes",
131
+ no: "No",
132
+ },
133
+ update_strategy: {
134
+ title_context: <<~TITLE,
135
+
136
+ The local file {{command:%s}} is different from the remote version in the development theme."
137
+ TITLE
138
+ title_question: "What would you like to do?",
139
+ keep_remote: "Keep the remote version",
140
+ keep_local: "Keep the local version",
141
+ union_merge: "Merge files (it may break the local file)",
142
+ exit: "Exit",
143
+ },
144
+ delete_strategy: {
145
+ title_context: <<~TITLE,
146
+
147
+ The local file {{command:%s}} has been recently removed, but it's present on your remote development theme.",
148
+ TITLE
149
+ title_question: "What would you like to do?",
150
+ delete: "Delete permanently",
151
+ restore: "Restore with the remote version",
152
+ exit: "Exit",
153
+ },
154
+ },
155
+ },
124
156
  error: {
125
157
  address_binding_error: "Couldn't bind to localhost."\
126
158
  " To serve your theme, set a different address with {{command:%s theme serve --host=<address>}}",
159
+ invalid_subdirectory: <<~MESSAGE,
160
+ The presence of %s in the directory structure isn't supported.
161
+
162
+ Move any files to a parent folder, then delete unsupported subdirectories.
163
+
164
+ • Required directory structure: https://shopify.dev/themes/architecture#directory-structure-and-component-types
165
+ MESSAGE
127
166
  },
128
167
  serving: <<~SERVING,
129
168
 
130
169
  Serving %s
131
170
 
132
171
  SERVING
172
+ download_changes: ", and use 'theme pull' to get the changes",
133
173
  customize_or_preview: <<~CUSTOMIZE_OR_PREVIEW,
134
174
 
135
- Customize this theme in the Theme Editor:
175
+ Customize this theme in the Theme Editor%s:
136
176
  {{green:%s}}
137
177
 
138
178
  Share this theme preview:
@@ -142,7 +182,7 @@ module Theme
142
182
  CUSTOMIZE_OR_PREVIEW
143
183
  ensure_user: <<~ENSURE_USER,
144
184
  You are not authorized to edit themes on %s.
145
- 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.
146
186
  ENSURE_USER
147
187
  address_already_in_use: "The address \"%s\" is already in use.",
148
188
  try_port_option: "Use the --port=PORT option to serve the theme in a different port.",
@@ -152,6 +192,7 @@ module Theme
152
192
  Check your theme for errors, suggestions, and best practices.
153
193
  Usage: {{command:%s check}}
154
194
  HELP
195
+ error: "Theme check failed with error:\n%s",
155
196
  },
156
197
  delete: {
157
198
  help: <<~HELP,
@@ -223,6 +264,9 @@ module Theme
223
264
  details: <<~DETAILS,
224
265
  {{*}} {{bold:%s}}
225
266
 
267
+ Preview your theme:
268
+ {{green:%s}}
269
+
226
270
  Customize your theme in the Theme Editor:
227
271
  {{green:%s}}
228
272
 
@@ -246,6 +290,22 @@ module Theme
246
290
  Usage: {{command:%s theme list}}
247
291
  HELP
248
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
+ },
249
309
  },
250
310
  }.freeze
251
311
  end
@@ -0,0 +1,148 @@
1
+ require "date"
2
+ require "shopify_cli/sed"
3
+ require "octokit"
4
+
5
+ module ShopifyCLI
6
+ class Changelog
7
+ CHANGELOG_FILE = File.join(ShopifyCLI::ROOT, "CHANGELOG.md")
8
+ CHANGE_CATEGORIES = %w(Added Changed Deprecated Removed Fixed Security)
9
+
10
+ def initialize
11
+ load(File.read(CHANGELOG_FILE))
12
+ end
13
+
14
+ def update_version!(new_version)
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!
26
+ end
27
+
28
+ def release_notes(version)
29
+ changes[version][:changes].map do |change_category, changes|
30
+ <<~CHANGES
31
+ ### #{change_category}
32
+ #{changes.map { |change| entry(**change) }.join("\n")}
33
+ CHANGES
34
+ end.join("\n")
35
+ end
36
+
37
+ def add_change(category, change)
38
+ changes["Unreleased"][:changes][category] << change
39
+ end
40
+
41
+ def entry(pr_id:, desc:)
42
+ "* [##{pr_id}](https://github.com/Shopify/shopify-cli/pull/#{pr_id}): #{desc}"
43
+ end
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
+
65
+ private
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
+
84
+ def changes
85
+ @changes ||= Hash.new do |h, k|
86
+ h[k] = {
87
+ date: nil,
88
+ changes: Hash.new do |h2, k2|
89
+ h2[k2] = []
90
+ end,
91
+ }
92
+ end
93
+ end
94
+
95
+ def load(log)
96
+ state = :initial
97
+ change_category = nil
98
+ current_version = nil
99
+ @heading = ""
100
+ @remainder = ""
101
+ log.each_line do |line|
102
+ case state
103
+ when :initial
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
113
+ if /\A\#\#\# (?<category>\w+)/ =~ line
114
+ change_category = category
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
118
+ current_version = version
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
126
+ elsif !line.match?(/\s*\n/)
127
+ raise "Unrecognized line: #{line.inspect}"
128
+ end
129
+ end
130
+ @remainder << line if state == :finished
131
+ end
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
147
+ end
148
+ end
@@ -36,6 +36,13 @@ module ShopifyCLI
36
36
  end
37
37
  rescue OptionParser::InvalidOption => error
38
38
  arg = error.args.first
39
+ store_name = arg.match(/\A--(?<store_name>.*\.myshopify\.com)\z/)&.[](:store_name)
40
+ if store_name && !arg.match?(/\A--(store|shop)=/)
41
+ # Sometimes it may look like --invalidoption=https://storename.myshopify.com
42
+ store_name = store_name.sub(%r{\A(.*=)?(https?://)?}, "")
43
+ raise ShopifyCLI::Abort,
44
+ @ctx.message("core.errors.option_parser.invalid_option_store_equals", arg, store_name)
45
+ end
39
46
  raise ShopifyCLI::Abort, @ctx.message("core.errors.option_parser.invalid_option", arg)
40
47
  rescue OptionParser::MissingArgument => error
41
48
  arg = error.args.first
@@ -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)
@@ -102,6 +102,42 @@ module ShopifyCLI
102
102
  branches
103
103
  end
104
104
 
105
+ ##
106
+ # Run git three-way file merge (it doesn't require an initialized git repository)
107
+ #
108
+ # #### Parameters
109
+ #
110
+ # * `current_file - string path of the current file
111
+ # * `base_file` - string path of the base file
112
+ # * `other_file` - string path of the other file
113
+ # * `opts` - list of "git merge-file" options. Valid values:
114
+ # - "-q" - do not warn about conflicts
115
+ # - "--diff3" - show conflicts
116
+ # - "--ours" - resolve conflicts favoring lines from `current_file`
117
+ # - "--theirs" - resolve conflicts favoring lines from `other_file`
118
+ # - "--union" - resolve conflicts favoring lines from both files
119
+ # - "-p" - send results to standard output instead of
120
+ # overwriting the `current_file`
121
+ # * `ctx` - the current running context of your command, defaults to a new context
122
+ #
123
+ # #### Returns
124
+ #
125
+ # * standard output from git
126
+ #
127
+ # #### Example
128
+ #
129
+ # output = ShopifyCLI::Git.merge_file(current_file, base_file, other_file, opts, ctx: ctx)
130
+ #
131
+ def merge_file(current_file, base_file, other_file, opts = [], ctx: Context.new)
132
+ output, status = ctx.capture2e("git", "merge-file", current_file, base_file, other_file, *opts)
133
+
134
+ unless status.success?
135
+ ctx.abort(ctx.message("core.git.error.merge_failed"))
136
+ end
137
+
138
+ output
139
+ end
140
+
105
141
  ##
106
142
  # will initialize a new repo in the current directory. This will output
107
143
  # if it was successful or not.
@@ -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),
@@ -18,8 +18,15 @@ module ShopifyCLI
18
18
  missing_node: "Node.js is required to continue. Install Node.js here: https://nodejs.org/en/download.",
19
19
  missing_npm: "npm is required to continue. Install npm here: https://www.npmjs.com/get-npm.",
20
20
  missing_ruby: "Ruby is required to continue. Install Ruby here: https://www.ruby-lang.org/en/downloads.",
21
+ bundle_info_failure: "Error getting version for %s gem",
21
22
  option_parser: {
22
23
  invalid_option: "The option {{command:%s}} is not supported.",
24
+ invalid_option_store_equals: <<~MESSAGE,
25
+ The option {{command:%s}} isn't recognized.
26
+
27
+ Try this:
28
+ {{command:--store=%s}}.
29
+ MESSAGE
23
30
  missing_argument: "The required argument {{command:%s}} is missing.",
24
31
  },
25
32
  },
@@ -257,6 +264,7 @@ module ShopifyCLI
257
264
  {{bold:Options:}}
258
265
  {{cyan:--host=HOST}}: Bypass running tunnel and use custom host. HOST must be HTTPS url.
259
266
  {{cyan:--port=PORT}}: Use custom port.
267
+ {{cyan:--no-update}}: Skips the dashboard URL update step
260
268
  HELP
261
269
  open_info: <<~MESSAGE,
262
270
  {{*}} To install and start using your app, open this URL in your browser:
@@ -277,6 +285,13 @@ module ShopifyCLI
277
285
  },
278
286
  extension: {
279
287
  push: {
288
+ beacon_extension: {
289
+ error: {
290
+ file_read_error: "There was a problem reading %s",
291
+ missing_config_key_error: "Configuration is missing key: %s",
292
+ invalid_config_value_error: "Configuration value is invalid: %s",
293
+ },
294
+ },
280
295
  checkout_ui_extension: {
281
296
  localization: {
282
297
  error: {
@@ -288,6 +303,7 @@ module ShopifyCLI
288
303
  invalid_file_extension: "Invalid locale filename: `%s`; only .json files are allowed.",
289
304
  invalid_locale_code: "Invalid locale filename: `%s`; locale code should be 2 or 3 letters,"\
290
305
  " optionally followed by a two-letter region code, e.g. `fr-CA`.",
306
+ invalid_file_encoding: "Invalid file encoding for `%s`; file encoding should be UTF-8.",
291
307
  single_default_locale: "There must be one and only one locale identified as the default locale,"\
292
308
  " e.g. `en.default.json`",
293
309
  },
@@ -382,6 +398,7 @@ module ShopifyCLI
382
398
  sparse_checkout_not_set: "Sparse checkout set command failed.",
383
399
  pull_failed: "Pull failed.",
384
400
  pull_failed_bad_branch: "Pull failed. Branch %s cannot be found. Check the branch name and try again.",
401
+ merge_failed: "The file %s merge failed.",
385
402
  },
386
403
 
387
404
  cloning: "Cloning %s into %s…",
@@ -432,7 +449,7 @@ module ShopifyCLI
432
449
  login: {
433
450
  help: <<~HELP,
434
451
  Log in to the Shopify CLI by authenticating with a store or partner organization
435
- Usage: {{command:%s login [--store=STORE]}}
452
+ Usage: {{command:%s login [--store STORE]}}
436
453
  HELP
437
454
  invalid_shop: <<~MESSAGE,
438
455
  Invalid store provided (%s). Please provide the store in the following format: my-store.myshopify.com
@@ -440,6 +457,11 @@ module ShopifyCLI
440
457
  shop_prompt: <<~PROMPT,
441
458
  What store are you connecting to? (e.g. my-store.myshopify.com; do {{bold:NOT}} include protocol part, e.g., https://)
442
459
  PROMPT
460
+ spinner: {
461
+ initiating: "Initiating authentication",
462
+ finalizing: "Finalizing authentication",
463
+ loading_organizations: "Loading available partner organizations",
464
+ },
443
465
  },
444
466
 
445
467
  logout: {
@@ -454,7 +476,7 @@ module ShopifyCLI
454
476
  switch: {
455
477
  help: <<~HELP,
456
478
  Switch between development stores in your partner organization
457
- Usage: {{command:%s switch [--store=STORE]}}
479
+ Usage: {{command:%s switch [--store STORE]}}
458
480
  HELP
459
481
  disabled_as_shopify_org: "Can't switch development stores logged in as {{green:Shopify partners org}}",
460
482
  success: "Switched development store to {{green:%s}}",
@@ -567,7 +589,7 @@ module ShopifyCLI
567
589
  HELP
568
590
 
569
591
  error: {
570
- no_shop: "No store found. Please run {{command:%s login --store=STORE}} to login to a specific store",
592
+ no_shop: "No store found. Please run {{command:%s login --store STORE}} to login to a specific store",
571
593
  },
572
594
 
573
595
  customer: {
@@ -707,7 +729,6 @@ module ShopifyCLI
707
729
  updated: "{{v}} Whitelist URLS updated in Partners Dashboard}}",
708
730
  update_error:
709
731
  "{{x}} error: For authentication issues, run {{command:%s logout}} to clear invalid credentials",
710
- update_prompt: "Do you want to update your application url?",
711
732
  },
712
733
  select_org_and_shop: {
713
734
  authentication_issue: "For authentication issues, run {{command:%s logout}} to clear invalid credentials",
@@ -803,7 +824,7 @@ module ShopifyCLI
803
824
  not_logged_in: <<~MESSAGE,
804
825
  It doesn't appear that you're logged in. You must log into a partner organization or a store staff account.
805
826
 
806
- If trying to log into a store staff account, please use {{command:%s login --store=STORE}} to log in.
827
+ If trying to log into a store staff account, please use {{command:%s login --store STORE}} to log in.
807
828
  MESSAGE
808
829
  logged_in_shop_only: <<~MESSAGE,
809
830
  Logged into store {{green:%s}} as staff (no partner organizations available for this login)