shopify-cli 2.14.0 → 2.15.2

Sign up to get free protection for your applications and to get access to all the features.
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, {}]