shopify-cli 2.14.0 → 2.15.2

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 (64) hide show
  1. checksums.yaml +4 -4
  2. data/.github/CONTRIBUTING.md +1 -1
  3. data/.github/workflows/stale.yml +46 -0
  4. data/CHANGELOG.md +24 -0
  5. data/Gemfile +1 -0
  6. data/Gemfile.lock +39 -7
  7. data/Rakefile +40 -0
  8. data/ext/shopify-extensions/version +1 -1
  9. data/lib/project_types/extension/forms/questions/ask_template.rb +5 -8
  10. data/lib/project_types/extension/messages/messages.rb +11 -1
  11. data/lib/project_types/extension/models/development_server_requirements.rb +13 -7
  12. data/lib/project_types/extension/models/server_config/root.rb +2 -0
  13. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +13 -0
  14. data/lib/project_types/script/config/extension_points.yml +18 -0
  15. data/lib/project_types/script/layers/infrastructure/errors.rb +17 -0
  16. data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
  17. data/lib/project_types/script/messages/messages.rb +3 -0
  18. data/lib/project_types/script/ui/error_handler.rb +11 -0
  19. data/lib/project_types/theme/commands/pull.rb +2 -2
  20. data/lib/project_types/theme/commands/push.rb +2 -2
  21. data/lib/project_types/theme/commands/serve.rb +1 -0
  22. data/lib/project_types/theme/conversions/base_glob.rb +20 -5
  23. data/lib/project_types/theme/messages/messages.rb +47 -8
  24. data/lib/shopify_cli/changelog.rb +76 -0
  25. data/lib/shopify_cli/command.rb +8 -7
  26. data/lib/shopify_cli/environment.rb +19 -11
  27. data/lib/shopify_cli/git.rb +36 -0
  28. data/lib/shopify_cli/messages/messages.rb +16 -9
  29. data/lib/shopify_cli/release.rb +194 -0
  30. data/lib/shopify_cli/sed.rb +19 -0
  31. data/lib/shopify_cli/services/app/create/node_service.rb +2 -14
  32. data/lib/shopify_cli/services/app/create/php_service.rb +1 -6
  33. data/lib/shopify_cli/services/app/create/rails_service.rb +4 -12
  34. data/lib/shopify_cli/theme/dev_server/hot-reload.js +40 -13
  35. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +1 -1
  36. data/lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb +51 -0
  37. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +6 -1
  38. data/lib/shopify_cli/theme/dev_server/local_assets.rb +1 -1
  39. data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +34 -0
  40. data/lib/shopify_cli/theme/dev_server/remote_watcher.rb +44 -0
  41. data/lib/shopify_cli/theme/dev_server/watcher.rb +1 -1
  42. data/lib/shopify_cli/theme/dev_server.rb +15 -3
  43. data/lib/shopify_cli/theme/file.rb +15 -4
  44. data/lib/shopify_cli/theme/syncer/checksums.rb +60 -0
  45. data/lib/shopify_cli/theme/syncer/forms/apply_to_all.rb +39 -0
  46. data/lib/shopify_cli/theme/syncer/forms/apply_to_all_form.rb +35 -0
  47. data/lib/shopify_cli/theme/syncer/forms/base_strategy_form.rb +62 -0
  48. data/lib/shopify_cli/theme/syncer/forms/select_delete_strategy.rb +27 -0
  49. data/lib/shopify_cli/theme/syncer/forms/select_update_strategy.rb +28 -0
  50. data/lib/shopify_cli/theme/syncer/ignore_helper.rb +33 -0
  51. data/lib/shopify_cli/theme/syncer/json_delete_handler.rb +51 -0
  52. data/lib/shopify_cli/theme/syncer/json_update_handler.rb +82 -0
  53. data/lib/shopify_cli/theme/syncer/merger.rb +53 -0
  54. data/lib/shopify_cli/theme/syncer/operation.rb +1 -1
  55. data/lib/shopify_cli/theme/syncer.rb +79 -63
  56. data/lib/shopify_cli/theme/theme.rb +12 -4
  57. data/lib/shopify_cli/theme/theme_admin_api.rb +24 -23
  58. data/lib/shopify_cli/thread_pool/job.rb +10 -2
  59. data/lib/shopify_cli/thread_pool.rb +15 -3
  60. data/lib/shopify_cli/tunnel.rb +9 -0
  61. data/lib/shopify_cli/version.rb +1 -1
  62. data/shopify-cli.gemspec +3 -1
  63. metadata +21 -4
  64. data/lib/project_types/rails/ruby.rb +0 -17
@@ -0,0 +1,76 @@
1
+ require "shopify_cli/sed"
2
+
3
+ module ShopifyCLI
4
+ class Changelog
5
+ CHANGELOG_FILE = File.join(ShopifyCLI::ROOT, "CHANGELOG.md")
6
+
7
+ def initialize
8
+ load(File.read(CHANGELOG_FILE))
9
+ end
10
+
11
+ def update_version!(new_version)
12
+ Sed.new.replace_inline(
13
+ CHANGELOG_FILE,
14
+ "## \\[Unreleased\\]",
15
+ "## [Unreleased]\\n\\n## Version #{new_version}"
16
+ )
17
+ end
18
+
19
+ def release_notes(version)
20
+ changes[version].map do |change_category, changes|
21
+ <<~CHANGES
22
+ ### #{change_category}
23
+ #{changes.map { |change| entry(**change) }.join("\n")}
24
+ CHANGES
25
+ end.join("\n")
26
+ end
27
+
28
+ def entry(pr_id:, desc:)
29
+ "* [##{pr_id}](https://github.com/Shopify/shopify-cli/pull/#{pr_id}): #{desc}"
30
+ end
31
+
32
+ private
33
+
34
+ def changes
35
+ @changes ||= Hash.new do |h, k|
36
+ h[k] = Hash.new do |h2, k2|
37
+ h2[k2] = []
38
+ end
39
+ end
40
+ end
41
+
42
+ def load(log)
43
+ state = :initial
44
+ change_category = nil
45
+ current_version = nil
46
+ @remainder = ""
47
+ log.each_line do |line|
48
+ case state
49
+ when :initial
50
+ next unless line.chomp == "\#\# [Unreleased]"
51
+ state = :unreleased
52
+ current_version = "Unreleased"
53
+ when :unreleased, :last_version
54
+ if /\A\#\#\# (?<category>\w+)/ =~ line
55
+ 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
59
+ current_version = version
60
+ state =
61
+ case state
62
+ when :unreleased
63
+ :last_version
64
+ else
65
+ :finished
66
+ end
67
+ elsif !line.match?(/\s*\n/)
68
+ raise "Unrecognized line: #{line.inspect}"
69
+ end
70
+ when :finished
71
+ @remainder << line
72
+ end
73
+ end
74
+ end
75
+ end
76
+ 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
@@ -102,16 +109,10 @@ module ShopifyCLI
102
109
  def check_node_version
103
110
  return unless @compatible_node_range
104
111
 
105
- context = Context.new
106
- if context.which("node").nil?
107
- raise ShopifyCLI::Abort, context.message("core.errors.missing_node")
108
- end
109
-
110
112
  check_version(
111
113
  Environment.node_version,
112
114
  range: @compatible_node_range,
113
- runtime: "Node",
114
- context: context
115
+ runtime: "Node"
115
116
  )
116
117
  end
117
118
 
@@ -12,23 +12,31 @@ module ShopifyCLI
12
12
  ]
13
13
 
14
14
  def self.ruby_version(context: Context.new)
15
- out, err, stat = context.capture3('ruby -e "puts RUBY_VERSION"')
16
- raise ShopifyCLI::Abort, err unless stat.success?
17
- out = out.gsub('"', "")
18
- ::Semantic::Version.new(out.chomp)
15
+ output, status = context.capture2e("ruby", "--version")
16
+ raise ShopifyCLI::Abort, context.message("core.errors.missing_ruby") unless status.success?
17
+ version = output.match(/ruby (\d+\.\d+\.\d+)/)[1]
18
+ ::Semantic::Version.new(version)
19
19
  end
20
20
 
21
21
  def self.node_version(context: Context.new)
22
- out, err, stat = context.capture3("node", "--version")
23
- raise ShopifyCLI::Abort, err unless stat.success?
24
- out = out.gsub("v", "")
25
- ::Semantic::Version.new(out.chomp)
22
+ output, status = context.capture2e("node", "--version")
23
+ raise ShopifyCLI::Abort, context.message("core.errors.missing_node") unless status.success?
24
+ version = output.match(/v(\d+\.\d+\.\d+)/)[1]
25
+ ::Semantic::Version.new(version)
26
26
  end
27
27
 
28
28
  def self.npm_version(context: Context.new)
29
- out, err, stat = context.capture3("npm", "--version")
30
- raise ShopifyCLI::Abort, err unless stat.success?
31
- ::Semantic::Version.new(out.chomp)
29
+ output, status = context.capture2e("npm", "--version")
30
+ raise ShopifyCLI::Abort, context.message("core.errors.missing_npm") unless status.success?
31
+ version = output.match(/(\d+\.\d+\.\d+)/)[1]
32
+ ::Semantic::Version.new(version)
33
+ end
34
+
35
+ def self.rails_version(context: Context.new)
36
+ output, status = context.capture2e("rails", "--version")
37
+ context.abort(context.message("core.app.create.rails.error.install_failure", "rails")) unless status.success?
38
+ version = output.match(/Rails (\d+\.\d+\.\d+)/)[1]
39
+ ::Semantic::Version.new(version)
32
40
  end
33
41
 
34
42
  def self.interactive=(interactive)
@@ -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.
@@ -15,9 +15,17 @@ module ShopifyCLI
15
15
  },
16
16
  core: {
17
17
  errors: {
18
- missing_node: "Node is required to continue. Install node here: https://nodejs.org/en/download.",
18
+ missing_node: "Node.js is required to continue. Install Node.js here: https://nodejs.org/en/download.",
19
+ missing_npm: "npm is required to continue. Install npm here: https://www.npmjs.com/get-npm.",
20
+ missing_ruby: "Ruby is required to continue. Install Ruby here: https://www.ruby-lang.org/en/downloads.",
19
21
  option_parser: {
20
22
  invalid_option: "The option {{command:%s}} is not supported.",
23
+ invalid_option_store_equals: <<~MESSAGE,
24
+ The option {{command:%s}} isn't recognized.
25
+
26
+ Try this:
27
+ {{command:--store=%s}}.
28
+ MESSAGE
21
29
  missing_argument: "The required argument {{command:%s}} is missing.",
22
30
  },
23
31
  },
@@ -95,8 +103,6 @@ module ShopifyCLI
95
103
  HELP
96
104
  error: {
97
105
  node_required: "node is required to create an app project. Download at https://nodejs.org/en/download.",
98
- node_version_failure: "Failed to get the current node version. Please make sure it is installed as " \
99
- "per the instructions at https://nodejs.org/en.",
100
106
  npm_required: "npm is required to create an app project. Download at https://www.npmjs.com/get-npm.",
101
107
  npm_version_failure: "Failed to get the current npm version. Please make sure it is installed as per " \
102
108
  "the instructions at https://www.npmjs.com/get-npm.",
@@ -131,8 +137,6 @@ module ShopifyCLI
131
137
  {{underline:https://getcomposer.org/download/}}
132
138
  COMPOSER
133
139
  npm_required: "npm is required to create an app project. Download at https://www.npmjs.com/get-npm.",
134
- npm_version_failure: "Failed to get the current npm version. Please make sure it is installed as per " \
135
- "the instructions at https://www.npmjs.com/get-npm.",
136
140
  app_setup: "Failed to set up the app",
137
141
  },
138
142
 
@@ -283,6 +287,8 @@ module ShopifyCLI
283
287
  localization: {
284
288
  error: {
285
289
  bundle_too_large: "Total size of all locale files must be less than %s.",
290
+ duplicate_locale_code: "Duplicate locale found: `%s`; locale codes"\
291
+ " should be unique and are case insensitive.",
286
292
  file_empty: "Locale file `%s` is empty.",
287
293
  file_too_large: "Locale file `%s` too large; size must be less than %s.",
288
294
  invalid_file_extension: "Invalid locale filename: `%s`; only .json files are allowed.",
@@ -382,6 +388,7 @@ module ShopifyCLI
382
388
  sparse_checkout_not_set: "Sparse checkout set command failed.",
383
389
  pull_failed: "Pull failed.",
384
390
  pull_failed_bad_branch: "Pull failed. Branch %s cannot be found. Check the branch name and try again.",
391
+ merge_failed: "The file %s merge failed.",
385
392
  },
386
393
 
387
394
  cloning: "Cloning %s into %s…",
@@ -432,7 +439,7 @@ module ShopifyCLI
432
439
  login: {
433
440
  help: <<~HELP,
434
441
  Log in to the Shopify CLI by authenticating with a store or partner organization
435
- Usage: {{command:%s login [--store=STORE]}}
442
+ Usage: {{command:%s login [--store STORE]}}
436
443
  HELP
437
444
  invalid_shop: <<~MESSAGE,
438
445
  Invalid store provided (%s). Please provide the store in the following format: my-store.myshopify.com
@@ -454,7 +461,7 @@ module ShopifyCLI
454
461
  switch: {
455
462
  help: <<~HELP,
456
463
  Switch between development stores in your partner organization
457
- Usage: {{command:%s switch [--store=STORE]}}
464
+ Usage: {{command:%s switch [--store STORE]}}
458
465
  HELP
459
466
  disabled_as_shopify_org: "Can't switch development stores logged in as {{green:Shopify partners org}}",
460
467
  success: "Switched development store to {{green:%s}}",
@@ -567,7 +574,7 @@ module ShopifyCLI
567
574
  HELP
568
575
 
569
576
  error: {
570
- no_shop: "No store found. Please run {{command:%s login --store=STORE}} to login to a specific store",
577
+ no_shop: "No store found. Please run {{command:%s login --store STORE}} to login to a specific store",
571
578
  },
572
579
 
573
580
  customer: {
@@ -803,7 +810,7 @@ module ShopifyCLI
803
810
  not_logged_in: <<~MESSAGE,
804
811
  It doesn't appear that you're logged in. You must log into a partner organization or a store staff account.
805
812
 
806
- If trying to log into a store staff account, please use {{command:%s login --store=STORE}} to log in.
813
+ If trying to log into a store staff account, please use {{command:%s login --store STORE}} to log in.
807
814
  MESSAGE
808
815
  logged_in_shop_only: <<~MESSAGE,
809
816
  Logged into store {{green:%s}} as staff (no partner organizations available for this login)
@@ -0,0 +1,194 @@
1
+ require "net/http"
2
+ require "fileutils"
3
+ require "shopify_cli/sed"
4
+ require "shopify_cli/changelog"
5
+ require "octokit"
6
+
7
+ module ShopifyCLI
8
+ class Release
9
+ def initialize(new_version, github_access_token)
10
+ @new_version = new_version
11
+ @changelog = ShopifyCLI::Changelog.new
12
+ @github = Octokit::Client.new(access_token: github_access_token)
13
+ end
14
+
15
+ def prepare!
16
+ ensure_updated_main
17
+ create_release_branch
18
+ update_changelog
19
+ update_versions_in_files
20
+ commit_packaging
21
+ pr = create_pr
22
+ system("open #{pr["html_url"]}")
23
+ end
24
+
25
+ def package!
26
+ ensure_updated_main
27
+ ensure_correct_gem_version
28
+ Rake::Task["package"].invoke
29
+ update_homebrew
30
+ create_github_release
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :new_version, :changelog, :github
36
+
37
+ def ensure_updated_main
38
+ # We can't be sure what is the correct action to take if changes have been
39
+ # made but not committed. Ensure the user handles the situation before
40
+ # moving on.
41
+ unless %x(git status --porcelain).empty?
42
+ raise <<~MESSAGE
43
+ Uncommitted changes have been made to the repository.
44
+ Please make sure `git status` does not show any changes before continuing.
45
+ MESSAGE
46
+ end
47
+ system_or_fail("git checkout main", "check out main branch")
48
+ unless system("git pull")
49
+ raise "git pull failed, cannot be sure there aren't new commits!"
50
+ end
51
+ end
52
+
53
+ def create_release_branch
54
+ puts "Checking out release branch"
55
+ system_or_fail("git checkout -b #{release_branch_name}", "check out release branch")
56
+ end
57
+
58
+ def update_changelog
59
+ if release_notes("Unreleased").empty?
60
+ puts "No unreleased CHANGELOG updates found!"
61
+ else
62
+ puts "Updating CHANGELOG"
63
+ changelog.update_version!(new_version)
64
+ end
65
+ end
66
+
67
+ def update_versions_in_files
68
+ version_file = File.join(ShopifyCLI::ROOT, "lib/shopify_cli/version.rb")
69
+ puts "Updating version.rb"
70
+ ShopifyCLI::Sed.new.replace_inline(version_file, ShopifyCLI::VERSION, new_version)
71
+ gemfile_lock = File.join(ShopifyCLI::ROOT, "Gemfile.lock")
72
+ puts "Updating Gemfile.lock"
73
+ ShopifyCLI::Sed.new.replace_inline(
74
+ gemfile_lock,
75
+ "shopify-cli (#{ShopifyCLI::VERSION})",
76
+ "shopify-cli (#{new_version})",
77
+ )
78
+ end
79
+
80
+ def commit_packaging
81
+ puts "Committing"
82
+ system_or_fail("git commit -am 'Packaging for release v#{new_version}'", "commit")
83
+ system_or_fail("git push -u origin #{release_branch_name}", "push branch")
84
+ end
85
+
86
+ def create_pr
87
+ repo = "Shopify/shopify-cli"
88
+ github.create_pull_request(
89
+ repo,
90
+ "main",
91
+ release_branch_name,
92
+ "Packaging for release v#{new_version}",
93
+ release_notes("Unreleased")
94
+ ).tap { |results| puts "Created #{repo} PR ##{results["number"]}" }
95
+ end
96
+
97
+ def ensure_correct_gem_version
98
+ response = Net::HTTP.get(URI("https://rubygems.org/api/v1/versions/shopify-cli/latest.json"))
99
+ latest_version = JSON.parse(response)["version"]
100
+ unless latest_version == new_version
101
+ raise "Attempted to update to #{new_version}, but latest on RubyGems is #{latest_version}"
102
+ end
103
+ end
104
+
105
+ def update_homebrew
106
+ ensure_updated_homebrew_repo
107
+ update_homebrew_repo
108
+ pr = create_homebrew_pr
109
+ system("open #{pr["html_url"]}")
110
+ end
111
+
112
+ def ensure_updated_homebrew_repo
113
+ unless File.exist?(homebrew_path)
114
+ system_or_fail("/opt/dev/bin/dev clone homebrew-shopify", "clone homebrew-shopify repo")
115
+ end
116
+
117
+ Dir.chdir(homebrew_path) do
118
+ system_or_fail("git checkout master && git pull", "pull latest homebrew-shopify")
119
+ system_or_fail("git checkout -b #{homebrew_release_branch}", "check out homebrew branch")
120
+ end
121
+ end
122
+
123
+ def update_homebrew_repo
124
+ source_file = File.join(package_dir, "shopify-cli.rb")
125
+ FileUtils.copy(source_file, homebrew_path)
126
+ Dir.chdir(homebrew_path) do
127
+ system_or_fail("git commit -am '#{homebrew_update_message}'", "commit homebrew update")
128
+ system_or_fail("git push -u origin #{homebrew_release_branch}", "push homebrew branch")
129
+ end
130
+ end
131
+
132
+ def create_homebrew_pr
133
+ repo = "Shopify/homebrew-shopify"
134
+ github.create_pull_request(
135
+ repo,
136
+ "master",
137
+ homebrew_release_branch,
138
+ homebrew_update_message,
139
+ homebrew_release_notes
140
+ ).tap { |results| puts "Created #{repo} PR ##{results["number"]}" }
141
+ end
142
+
143
+ def create_github_release
144
+ release = github.create_release(
145
+ "Shopify/shopify-cli",
146
+ "v#{new_version}",
147
+ {
148
+ name: "Version #{new_version}",
149
+ body: release_notes(new_version),
150
+ }
151
+ )
152
+ %w(.deb -1.noarch.rpm).each do |suffix|
153
+ github.upload_asset(
154
+ release["url"],
155
+ File.join(package_dir, "shopify-cli-#{new_version}#{suffix}")
156
+ )
157
+ end
158
+ system("open #{release["html_url"]}")
159
+ end
160
+
161
+ def homebrew_path
162
+ @homebrew_path ||= %x(/opt/dev/bin/dev project-path homebrew-shopify).chomp
163
+ end
164
+
165
+ def homebrew_update_message
166
+ @homebrew_update_message ||= "Update Shopify CLI to #{new_version}"
167
+ end
168
+
169
+ def package_dir
170
+ @package_dir ||= File.join(ShopifyCLI::ROOT, "packaging", "builds", new_version)
171
+ end
172
+
173
+ def homebrew_release_branch
174
+ "release_#{new_version.split(".").join("_")}_of_shopify-cli"
175
+ end
176
+
177
+ def homebrew_release_notes
178
+ "I'm releasing a new version of the Shopify CLI, " \
179
+ "[#{new_version}](https://github.com/Shopify/shopify-cli/releases/tag/v#{new_version})"
180
+ end
181
+
182
+ def release_branch_name
183
+ @release_branch_name ||= "release_#{new_version.split(".").join("_")}"
184
+ end
185
+
186
+ def release_notes(version)
187
+ changelog.release_notes(version)
188
+ end
189
+
190
+ def system_or_fail(command, action)
191
+ raise "Failed to #{action}!" unless system(command)
192
+ end
193
+ end
194
+ end
@@ -0,0 +1,19 @@
1
+ module ShopifyCLI
2
+ class Sed
3
+ class SedError < StandardError; end
4
+
5
+ def replace_inline(filename, pattern, output)
6
+ command =
7
+ case CLI::Kit::System.os
8
+ when :mac
9
+ "sed -i ''"
10
+ when :linux
11
+ "sed -i"
12
+ else
13
+ raise "Unrecognized system!"
14
+ end
15
+ success = system("#{command} 's/#{pattern}/#{output}/' #{filename}")
16
+ raise SedError unless success
17
+ end
18
+ end
19
+ end
@@ -88,24 +88,12 @@ module ShopifyCLI
88
88
  end
89
89
 
90
90
  def check_node
91
- cmd_path = context.which("node")
92
- context.abort(context.message("core.app.create.node.error.node_required")) if cmd_path.nil?
93
-
94
- version, stat = context.capture2e("node", "-v")
95
- unless stat.success?
96
- context.abort(context.message("core.app.create.node.error.node_version_failure"))
97
- end
98
-
91
+ version = ShopifyCLI::Environment.node_version(context: context)
99
92
  context.done(context.message("core.app.create.node.node_version", version))
100
93
  end
101
94
 
102
95
  def check_npm
103
- cmd_path = context.which("npm")
104
- context.abort(context.message("core.app.create.node.error.npm_required")) if cmd_path.nil?
105
-
106
- version, stat = context.capture2e("npm", "-v")
107
- context.abort(context.message("core.app.create.node.error.npm_version_failure")) unless stat.success?
108
-
96
+ version = ShopifyCLI::Environment.npm_version(context: context)
109
97
  context.done(context.message("core.app.create.node.npm_version", version))
110
98
  end
111
99
 
@@ -69,12 +69,7 @@ module ShopifyCLI
69
69
  end
70
70
 
71
71
  def check_npm
72
- cmd_path = context.which("npm")
73
- context.abort(context.message("core.app.create.php.error.npm_required")) if cmd_path.nil?
74
-
75
- version, stat = context.capture2e("npm", "-v")
76
- context.abort(context.message("core.app.create.php.error.npm_version_failure")) unless stat.success?
77
-
72
+ version = ShopifyCLI::Environment.npm_version(context: context)
78
73
  context.done(context.message("core.app.create.php.npm_version", version))
79
74
  end
80
75
 
@@ -108,7 +108,7 @@ module ShopifyCLI
108
108
  end
109
109
 
110
110
  def check_ruby
111
- ruby_version = Rails::Ruby.version(context)
111
+ ruby_version = Environment.ruby_version(context: context)
112
112
  return if ruby_version.satisfies?("~>2.5") || ruby_version.satisfies?("~>3.1.0")
113
113
  context.abort(context.message("core.app.create.rails.error.invalid_ruby_version"))
114
114
  end
@@ -219,18 +219,10 @@ module ShopifyCLI
219
219
  end
220
220
 
221
221
  def install_webpacker?
222
- rails_version < ::Semantic::Version.new("7.0.0") &&
223
- !File.exist?(File.join(context.root, "config/webpacker.yml"))
224
- end
225
-
226
- def rails_version
227
- output, status = context.capture2e("rails", "--version")
228
- unless status.success?
229
- context.abort(context.message("core.app.create.rails.error.install_failure", "rails"))
230
- end
222
+ rails_version = Environment.rails_version(context: context)
223
+ webpacker_config = File.exist?(File.join(context.root, "config/webpacker.yml"))
231
224
 
232
- version = output.scan(/Rails \d+\.\d+\.\d+/).first.split(" ").last
233
- ::Semantic::Version.new(version)
225
+ rails_version < ::Semantic::Version.new("7.0.0") && !webpacker_config
234
226
  end
235
227
  end
236
228
  end
@@ -15,17 +15,37 @@
15
15
  eventSource.onerror = () => eventSource.close();
16
16
  }
17
17
 
18
+ function sectionNamesByType(type) {
19
+ const namespace = window.__SHOPIFY_CLI_ENV__;
20
+ return namespace.section_names_by_type[type] || [];
21
+ }
22
+
18
23
  function reloadMode() {
19
- var namespace = window.__SHOPIFY_CLI_ENV__;
24
+ const namespace = window.__SHOPIFY_CLI_ENV__;
20
25
  return namespace.mode;
21
26
  }
22
27
 
28
+ function querySelectDOMSections(idSuffix) {
29
+ const elements = document.querySelectorAll(`[id^='shopify-section'][id$='${idSuffix}']`);
30
+ return Array.from(elements);
31
+ }
32
+
33
+ function fetchDOMSections(name) {
34
+ const domSections = sectionNamesByType(name).flatMap((n) => querySelectDOMSections(n));
35
+
36
+ if (domSections.length > 0) {
37
+ return domSections;
38
+ }
39
+
40
+ return querySelectDOMSections(name);
41
+ }
42
+
23
43
  function isFullPageReloadMode(){
24
- return reloadMode() === "full-page";
44
+ return reloadMode() === 'full-page';
25
45
  }
26
46
 
27
47
  function isReloadModeActive(){
28
- return reloadMode() !== "off";
48
+ return reloadMode() !== 'off';
29
49
  }
30
50
 
31
51
  function isRefreshRequired(files) {
@@ -104,26 +124,28 @@
104
124
  constructor(filename) {
105
125
  this.filename = filename;
106
126
  this.name = filename.split('/').pop().replace('.liquid', '');
107
- this.element = document.querySelector(`[id^='shopify-section'][id$='${this.name}']`);
127
+ this.elements = fetchDOMSections(this.name);
108
128
  }
109
129
 
110
130
  valid() {
111
- return this.filename.startsWith('sections/') && this.element;
131
+ return this.filename.startsWith('sections/') && this.elements.length > 0;
112
132
  }
113
133
 
114
- async refresh() {
115
- var url = new URL(window.location.href);
116
- url.searchParams.append('section_id', this.name);
134
+ async refreshElement(element) {
135
+
136
+ const sectionId = element.id.replace(/^shopify-section-/, '');
137
+ const url = new URL(window.location.href);
138
+
139
+ url.searchParams.append('section_id', sectionId);
140
+
141
+ const response = await fetch(url);
117
142
 
118
143
  try {
119
- const response = await fetch(url);
120
144
  if (response.headers.get('x-templates-from-params') == '1') {
121
145
  const html = await response.text();
122
- this.element.outerHTML = html;
123
-
124
- console.log(`[HotReload] Reloaded ${this.name} section`);
146
+ element.outerHTML = html;
125
147
  } else {
126
- window.location.reload()
148
+ window.location.reload();
127
149
 
128
150
  console.log(`[HotReload] Hot-reloading not supported, fully reloading ${this.name} section`);
129
151
  }
@@ -132,6 +154,11 @@
132
154
  console.log(`[HotReload] Failed to reload ${this.name} section: ${e.message}`);
133
155
  }
134
156
  }
157
+
158
+ async refresh() {
159
+ console.log(`[HotReload] Reloaded ${this.name} sections`);
160
+ this.elements.forEach(this.refreshElement);
161
+ }
135
162
  }
136
163
 
137
164
  if (isReloadModeActive()) {
@@ -51,7 +51,7 @@ module ShopifyCLI
51
51
  def fetch_asset(file)
52
52
  api_client.get(
53
53
  path: "themes/#{@theme.id}/assets.json",
54
- query: URI.encode_www_form("asset[key]" => file.relative_path.to_s),
54
+ query: URI.encode_www_form("asset[key]" => file.relative_path),
55
55
  )
56
56
  rescue ShopifyCLI::API::APIRequestNotFoundError
57
57
  [404, {}]