shopify-cli 2.19.0 → 2.21.0

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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.github/ISSUE_TEMPLATE/bug_report.yaml +2 -1
  3. data/.github/ISSUE_TEMPLATE/config.yml +9 -0
  4. data/.github/workflows/cla.yml +22 -0
  5. data/CHANGELOG.md +34 -2
  6. data/Gemfile.lock +1 -1
  7. data/README.md +7 -6
  8. data/docs/users/installation.md +1 -1
  9. data/lib/project_types/extension/messages/messages.rb +1 -1
  10. data/lib/project_types/extension/tasks/fetch_specifications.rb +4 -1
  11. data/lib/project_types/theme/commands/push.rb +3 -1
  12. data/lib/project_types/theme/commands/serve.rb +1 -0
  13. data/lib/project_types/theme/messages/messages.rb +39 -2
  14. data/lib/shopify_cli/assets/post_auth_page/index.html.erb +34 -0
  15. data/lib/shopify_cli/assets/post_auth_page/style.css +58 -0
  16. data/lib/shopify_cli/constants.rb +1 -0
  17. data/lib/shopify_cli/context.rb +3 -2
  18. data/lib/shopify_cli/environment.rb +4 -0
  19. data/lib/shopify_cli/identity_auth/servlet.rb +4 -20
  20. data/lib/shopify_cli/messages/messages.rb +6 -8
  21. data/lib/shopify_cli/theme/dev_server/hot-reload-no-script.html +27 -0
  22. data/lib/shopify_cli/theme/dev_server/hot-reload.js +38 -11
  23. data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_deleter.rb +62 -0
  24. data/lib/shopify_cli/theme/dev_server/hot_reload.rb +25 -1
  25. data/lib/shopify_cli/theme/dev_server/proxy.rb +1 -0
  26. data/lib/shopify_cli/theme/dev_server.rb +3 -2
  27. data/lib/shopify_cli/theme/file.rb +5 -0
  28. data/lib/shopify_cli/theme/syncer/json_update_handler.rb +21 -7
  29. data/lib/shopify_cli/theme/syncer/operation.rb +7 -6
  30. data/lib/shopify_cli/theme/syncer/unsupported_script_warning.rb +90 -0
  31. data/lib/shopify_cli/theme/syncer.rb +86 -32
  32. data/lib/shopify_cli/theme/theme.rb +5 -0
  33. data/lib/shopify_cli/theme/theme_admin_api.rb +16 -11
  34. data/lib/shopify_cli/theme/theme_admin_api_throttler/bulk.rb +102 -0
  35. data/lib/shopify_cli/theme/theme_admin_api_throttler/bulk_job.rb +75 -0
  36. data/lib/shopify_cli/theme/theme_admin_api_throttler/errors.rb +7 -0
  37. data/lib/shopify_cli/theme/theme_admin_api_throttler/put_request.rb +52 -0
  38. data/lib/shopify_cli/theme/theme_admin_api_throttler/request_parser.rb +39 -0
  39. data/lib/shopify_cli/theme/theme_admin_api_throttler/response_parser.rb +21 -0
  40. data/lib/shopify_cli/theme/theme_admin_api_throttler.rb +62 -0
  41. data/lib/shopify_cli/version.rb +1 -1
  42. metadata +16 -3
  43. data/.github/probots.yml +0 -3
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "bulk_job"
4
+ require "shopify_cli/thread_pool"
5
+
6
+ module ShopifyCLI
7
+ module Theme
8
+ class ThemeAdminAPIThrottler
9
+ class Bulk
10
+ MAX_BULK_BYTESIZE = 10_485_760 # 10MB
11
+ MAX_BULK_FILES = 20 # files
12
+ QUEUE_TIMEOUT = 0.2 # 200ms
13
+
14
+ attr_accessor :admin_api
15
+
16
+ def initialize(ctx, admin_api, pool_size: 20)
17
+ @ctx = ctx
18
+ @admin_api = admin_api
19
+ @latest_enqueued_at = now
20
+
21
+ @thread_pool = ShopifyCLI::ThreadPool.new(pool_size: pool_size)
22
+
23
+ pool_size.times do
24
+ @thread_pool.schedule(
25
+ BulkJob.new(ctx, self)
26
+ )
27
+ end
28
+
29
+ @put_requests = []
30
+ @mut = Mutex.new
31
+ end
32
+
33
+ def enqueue(put_request)
34
+ @mut.synchronize do
35
+ @latest_enqueued_at = now
36
+ @put_requests << put_request
37
+ end
38
+ end
39
+
40
+ def shutdown
41
+ wait_put_requests
42
+ @thread_pool.shutdown
43
+ end
44
+
45
+ def consume_put_requests
46
+ to_batch = []
47
+ to_batch_size_bytes = 0
48
+ @mut.synchronize do
49
+ # sort requests to perform less retries at the `bulk_job` level
50
+ @put_requests.sort_by! { |r| r.liquid? ? 0 : 1 }
51
+
52
+ is_ready = false
53
+ until is_ready || @put_requests.empty?
54
+ request = @put_requests.first
55
+ if to_batch.empty? && request.size > MAX_BULK_BYTESIZE
56
+ is_ready = true
57
+ to_batch << request
58
+ to_batch_size_bytes += request.size
59
+ @put_requests.shift
60
+ elsif to_batch.size + 1 > MAX_BULK_FILES || to_batch_size_bytes + request.size > MAX_BULK_BYTESIZE
61
+ is_ready = true
62
+ else
63
+ to_batch << request
64
+ to_batch_size_bytes += request.size
65
+ @put_requests.shift
66
+ end
67
+ end
68
+ end
69
+ [to_batch, to_batch_size_bytes]
70
+ end
71
+
72
+ def ready?
73
+ queue_timeout? || bulk_size >= MAX_BULK_FILES || bulk_bytesize >= MAX_BULK_BYTESIZE
74
+ end
75
+
76
+ def bulk_bytesize
77
+ @put_requests.map(&:size).reduce(:+).to_i
78
+ end
79
+
80
+ private
81
+
82
+ def bulk_size
83
+ @put_requests.size
84
+ end
85
+
86
+ def queue_timeout?
87
+ return false if bulk_size.zero?
88
+ elapsed_time = now - @latest_enqueued_at
89
+ elapsed_time > QUEUE_TIMEOUT
90
+ end
91
+
92
+ def wait_put_requests
93
+ sleep(0.2) until @put_requests.empty?
94
+ end
95
+
96
+ def now
97
+ Time.now.to_f
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/thread_pool/job"
4
+ require_relative "request_parser"
5
+ require_relative "response_parser"
6
+ require_relative "errors"
7
+
8
+ module ShopifyCLI
9
+ module Theme
10
+ class ThemeAdminAPIThrottler
11
+ class BulkJob < ShopifyCLI::ThreadPool::Job
12
+ JOB_TIMEOUT = 0.2 # 200ms
13
+ MAX_RETRIES = 10
14
+
15
+ attr_reader :bulk
16
+
17
+ def initialize(ctx, bulk)
18
+ super(JOB_TIMEOUT)
19
+ @ctx = ctx
20
+ @bulk = bulk
21
+
22
+ # Mutex used to coordinate changes performed by the bulk item block
23
+ @block_mutex = Mutex.new
24
+ end
25
+
26
+ def perform!
27
+ return unless bulk.ready?
28
+ put_requests, bulk_size = bulk.consume_put_requests
29
+ return if put_requests.empty?
30
+
31
+ @ctx.debug("[BulkJob] size: #{put_requests.size}, bytesize: #{bulk_size}")
32
+ bulk_status, bulk_body, response = rest_request(put_requests)
33
+
34
+ if bulk_status == 207
35
+ responses(bulk_body).each_with_index do |tuple, index|
36
+ status, body = tuple
37
+ put_request = put_requests[index]
38
+ if status == 200 || put_request.retries >= MAX_RETRIES
39
+ @block_mutex.synchronize do
40
+ if status == 200
41
+ @ctx.debug("[BulkJob] asset saved: #{put_request.key}")
42
+ put_request.block.call(status, body, response)
43
+ else
44
+ @ctx.debug("[BulkJob] asset continuing with error: #{put_request.key}")
45
+ err = AssetUploadError.new(body, response: { body: body })
46
+ put_request.block.call(status, {}, err)
47
+ end
48
+ end
49
+ else
50
+ @ctx.debug("[BulkJob] asset error: #{put_request.key}")
51
+ @block_mutex.synchronize do
52
+ put_request.retries += 1
53
+ bulk.enqueue(put_request)
54
+ end
55
+ end
56
+ end
57
+ else
58
+ @ctx.puts(@ctx.message("theme.stable_flag_suggestion"))
59
+ end
60
+ end
61
+
62
+ private
63
+
64
+ def rest_request(put_requests)
65
+ request = RequestParser.new(put_requests).parse
66
+ bulk.admin_api.rest_request(**request)
67
+ end
68
+
69
+ def responses(response_body)
70
+ ResponseParser.new(response_body).parse
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,7 @@
1
+ module ShopifyCLI
2
+ module Theme
3
+ class ThemeAdminAPIThrottler
4
+ class AssetUploadError < ShopifyCLI::API::APIRequestError; end
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "shopify_cli/thread_pool/job"
4
+ require_relative "request_parser"
5
+ require_relative "response_parser"
6
+
7
+ module ShopifyCLI
8
+ module Theme
9
+ class ThemeAdminAPIThrottler
10
+ class PutRequest
11
+ attr_reader :method, :body, :path, :block
12
+ attr_accessor :retries
13
+
14
+ def initialize(path, body, &block)
15
+ @method = "PUT"
16
+ @path = path
17
+ @body = body
18
+ @block = block
19
+ @retries = 0
20
+ end
21
+
22
+ def to_h
23
+ {
24
+ method: method,
25
+ path: path,
26
+ body: body,
27
+ }
28
+ end
29
+
30
+ def to_s
31
+ "#{key}, retries: #{retries}"
32
+ end
33
+
34
+ def liquid?
35
+ key.end_with?(".liquid")
36
+ end
37
+
38
+ def key
39
+ @key ||= JSON.parse(body)["asset"]["key"]
40
+ end
41
+
42
+ def bulk_path
43
+ path.gsub(/.json$/, "/bulk.json")
44
+ end
45
+
46
+ def size
47
+ @size ||= body.bytesize
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ class ThemeAdminAPIThrottler
6
+ class RequestParser
7
+ def initialize(requests)
8
+ @requests = requests
9
+ end
10
+
11
+ def parse
12
+ {
13
+ path: path,
14
+ method: method,
15
+ body: JSON.generate({ assets: assets }),
16
+ }
17
+ end
18
+
19
+ private
20
+
21
+ def method
22
+ @requests.sample.method
23
+ end
24
+
25
+ def path
26
+ @requests.sample.bulk_path
27
+ end
28
+
29
+ def assets
30
+ @requests.map do |request|
31
+ body = JSON.parse(request.body)
32
+ body = body.is_a?(Hash) ? body : JSON.parse(body)
33
+ body["asset"]
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ class ThemeAdminAPIThrottler
6
+ class ResponseParser
7
+ def initialize(response_body)
8
+ @response_body = response_body
9
+ end
10
+
11
+ def parse
12
+ result = []
13
+ @response_body["results"]&.each do |resp|
14
+ result << [resp["code"], resp["body"]]
15
+ end
16
+ result
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "forwardable"
4
+
5
+ require_relative "theme_admin_api_throttler/bulk"
6
+ require_relative "theme_admin_api_throttler/put_request"
7
+
8
+ module ShopifyCLI
9
+ module Theme
10
+ class ThemeAdminAPIThrottler
11
+ extend Forwardable
12
+
13
+ attr_reader :bulk, :admin_api
14
+
15
+ def_delegators :@admin_api, :get, :post, :delete
16
+
17
+ def initialize(ctx, admin_api, active = true)
18
+ @ctx = ctx
19
+ @admin_api = admin_api
20
+ @active = active
21
+ @bulk = Bulk.new(ctx, admin_api)
22
+ end
23
+
24
+ def put(path:, **args, &block)
25
+ request = PutRequest.new(path, args[:body], &block)
26
+ if active?
27
+ bulk_request(request)
28
+ else
29
+ rest_request(request)
30
+ end
31
+ end
32
+
33
+ def activate_throttler!
34
+ @active = true
35
+ end
36
+
37
+ def deactivate_throttler!
38
+ @active = false
39
+ end
40
+
41
+ def active?
42
+ @active
43
+ end
44
+
45
+ def shutdown
46
+ bulk.shutdown
47
+ end
48
+
49
+ private
50
+
51
+ def rest_request(request)
52
+ request.block.call(admin_api.rest_request(**request.to_h))
53
+ rescue StandardError => error
54
+ request.block.call(500, {}, error)
55
+ end
56
+
57
+ def bulk_request(request)
58
+ bulk.enqueue(request)
59
+ end
60
+ end
61
+ end
62
+ end
@@ -1,3 +1,3 @@
1
1
  module ShopifyCLI
2
- VERSION = "2.19.0"
2
+ VERSION = "2.21.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: shopify-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.19.0
4
+ version: 2.21.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shopify
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-06-17 00:00:00.000000000 Z
11
+ date: 2022-08-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -120,10 +120,11 @@ files:
120
120
  - ".github/DESIGN.md"
121
121
  - ".github/ISSUE_TEMPLATE.md"
122
122
  - ".github/ISSUE_TEMPLATE/bug_report.yaml"
123
+ - ".github/ISSUE_TEMPLATE/config.yml"
123
124
  - ".github/ISSUE_TEMPLATE/enhancement.yaml"
124
125
  - ".github/ISSUE_TEMPLATE/feature.yaml"
125
126
  - ".github/PULL_REQUEST_TEMPLATE.md"
126
- - ".github/probots.yml"
127
+ - ".github/workflows/cla.yml"
127
128
  - ".github/workflows/shopify.yml"
128
129
  - ".github/workflows/stale.yml"
129
130
  - ".github/workflows/triage.yml"
@@ -383,6 +384,8 @@ files:
383
384
  - lib/shopify_cli/admin_api/schema.rb
384
385
  - lib/shopify_cli/api.rb
385
386
  - lib/shopify_cli/app_type_detector.rb
387
+ - lib/shopify_cli/assets/post_auth_page/index.html.erb
388
+ - lib/shopify_cli/assets/post_auth_page/style.css
386
389
  - lib/shopify_cli/changelog.rb
387
390
  - lib/shopify_cli/command.rb
388
391
  - lib/shopify_cli/command/app_sub_command.rb
@@ -501,8 +504,10 @@ files:
501
504
  - lib/shopify_cli/theme/dev_server/cdn_fonts.rb
502
505
  - lib/shopify_cli/theme/dev_server/certificate_manager.rb
503
506
  - lib/shopify_cli/theme/dev_server/header_hash.rb
507
+ - lib/shopify_cli/theme/dev_server/hot-reload-no-script.html
504
508
  - lib/shopify_cli/theme/dev_server/hot-reload.js
505
509
  - lib/shopify_cli/theme/dev_server/hot_reload.rb
510
+ - lib/shopify_cli/theme/dev_server/hot_reload/remote_file_deleter.rb
506
511
  - lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb
507
512
  - lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb
508
513
  - lib/shopify_cli/theme/dev_server/local_assets.rb
@@ -534,8 +539,16 @@ files:
534
539
  - lib/shopify_cli/theme/syncer/merger.rb
535
540
  - lib/shopify_cli/theme/syncer/operation.rb
536
541
  - lib/shopify_cli/theme/syncer/standard_reporter.rb
542
+ - lib/shopify_cli/theme/syncer/unsupported_script_warning.rb
537
543
  - lib/shopify_cli/theme/theme.rb
538
544
  - lib/shopify_cli/theme/theme_admin_api.rb
545
+ - lib/shopify_cli/theme/theme_admin_api_throttler.rb
546
+ - lib/shopify_cli/theme/theme_admin_api_throttler/bulk.rb
547
+ - lib/shopify_cli/theme/theme_admin_api_throttler/bulk_job.rb
548
+ - lib/shopify_cli/theme/theme_admin_api_throttler/errors.rb
549
+ - lib/shopify_cli/theme/theme_admin_api_throttler/put_request.rb
550
+ - lib/shopify_cli/theme/theme_admin_api_throttler/request_parser.rb
551
+ - lib/shopify_cli/theme/theme_admin_api_throttler/response_parser.rb
539
552
  - lib/shopify_cli/thread_pool.rb
540
553
  - lib/shopify_cli/thread_pool/job.rb
541
554
  - lib/shopify_cli/transform_data_structure.rb
data/.github/probots.yml DELETED
@@ -1,3 +0,0 @@
1
- # .github/probots.yml
2
- enabled:
3
- - cla