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.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.yaml +2 -1
- data/.github/ISSUE_TEMPLATE/config.yml +9 -0
- data/.github/workflows/cla.yml +22 -0
- data/CHANGELOG.md +34 -2
- data/Gemfile.lock +1 -1
- data/README.md +7 -6
- data/docs/users/installation.md +1 -1
- data/lib/project_types/extension/messages/messages.rb +1 -1
- data/lib/project_types/extension/tasks/fetch_specifications.rb +4 -1
- data/lib/project_types/theme/commands/push.rb +3 -1
- data/lib/project_types/theme/commands/serve.rb +1 -0
- data/lib/project_types/theme/messages/messages.rb +39 -2
- data/lib/shopify_cli/assets/post_auth_page/index.html.erb +34 -0
- data/lib/shopify_cli/assets/post_auth_page/style.css +58 -0
- data/lib/shopify_cli/constants.rb +1 -0
- data/lib/shopify_cli/context.rb +3 -2
- data/lib/shopify_cli/environment.rb +4 -0
- data/lib/shopify_cli/identity_auth/servlet.rb +4 -20
- data/lib/shopify_cli/messages/messages.rb +6 -8
- data/lib/shopify_cli/theme/dev_server/hot-reload-no-script.html +27 -0
- data/lib/shopify_cli/theme/dev_server/hot-reload.js +38 -11
- data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_deleter.rb +62 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload.rb +25 -1
- data/lib/shopify_cli/theme/dev_server/proxy.rb +1 -0
- data/lib/shopify_cli/theme/dev_server.rb +3 -2
- data/lib/shopify_cli/theme/file.rb +5 -0
- data/lib/shopify_cli/theme/syncer/json_update_handler.rb +21 -7
- data/lib/shopify_cli/theme/syncer/operation.rb +7 -6
- data/lib/shopify_cli/theme/syncer/unsupported_script_warning.rb +90 -0
- data/lib/shopify_cli/theme/syncer.rb +86 -32
- data/lib/shopify_cli/theme/theme.rb +5 -0
- data/lib/shopify_cli/theme/theme_admin_api.rb +16 -11
- data/lib/shopify_cli/theme/theme_admin_api_throttler/bulk.rb +102 -0
- data/lib/shopify_cli/theme/theme_admin_api_throttler/bulk_job.rb +75 -0
- data/lib/shopify_cli/theme/theme_admin_api_throttler/errors.rb +7 -0
- data/lib/shopify_cli/theme/theme_admin_api_throttler/put_request.rb +52 -0
- data/lib/shopify_cli/theme/theme_admin_api_throttler/request_parser.rb +39 -0
- data/lib/shopify_cli/theme/theme_admin_api_throttler/response_parser.rb +21 -0
- data/lib/shopify_cli/theme/theme_admin_api_throttler.rb +62 -0
- data/lib/shopify_cli/version.rb +1 -1
- metadata +16 -3
- 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,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
|
data/lib/shopify_cli/version.rb
CHANGED
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.
|
|
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-
|
|
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/
|
|
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