shopify-cli 2.7.1 → 2.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +2 -2
- data/.github/workflows/shopify.yml +1 -1
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +43 -0
- data/Codespace.dockerfile +2 -2
- data/Gemfile.lock +4 -4
- data/Tests.dockerfile +2 -2
- data/dev.yml +3 -3
- data/ext/javy/hashes/javy-arm-macos-v0.1.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-linux-v0.1.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-macos-v0.1.0.gz.sha256 +1 -0
- data/ext/javy/hashes/javy-x86_64-windows-v0.1.0.gz.sha256 +1 -0
- data/ext/javy/javy.rb +30 -12
- data/lib/graphql/get_extension_registrations.graphql +27 -0
- data/lib/project_types/extension/cli.rb +27 -2
- data/lib/project_types/extension/commands/build.rb +10 -15
- data/lib/project_types/extension/commands/create.rb +3 -6
- data/lib/project_types/extension/commands/push.rb +36 -8
- data/lib/project_types/extension/extension_project.rb +1 -1
- data/lib/project_types/extension/features/argo_serve.rb +6 -5
- data/lib/project_types/extension/forms/questions/ask_registration.rb +6 -2
- data/lib/project_types/extension/loaders/project.rb +29 -0
- data/lib/project_types/extension/loaders/specification_handler.rb +22 -0
- data/lib/project_types/extension/messages/messages.rb +4 -2
- data/lib/project_types/extension/models/app.rb +1 -1
- data/lib/project_types/extension/models/development_server.rb +2 -2
- data/lib/project_types/extension/models/specification_handlers/default.rb +4 -0
- data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
- data/lib/project_types/extension/tasks/execute_commands/base.rb +13 -0
- data/lib/project_types/extension/tasks/execute_commands/build.rb +29 -0
- data/lib/project_types/extension/tasks/execute_commands/create.rb +33 -0
- data/lib/project_types/extension/tasks/execute_commands/serve.rb +35 -0
- data/lib/project_types/extension/tasks/merge_server_config.rb +33 -22
- data/lib/project_types/rails/commands/create.rb +2 -4
- data/lib/project_types/script/cli.rb +8 -1
- data/lib/project_types/script/commands/connect.rb +19 -0
- data/lib/project_types/script/commands/create.rb +1 -3
- data/lib/project_types/script/commands/javy.rb +0 -2
- data/lib/project_types/script/commands/push.rb +2 -1
- data/lib/project_types/script/config/extension_points.yml +10 -28
- data/lib/project_types/script/forms/ask_app.rb +32 -0
- data/lib/project_types/script/forms/ask_org.rb +30 -0
- data/lib/project_types/script/forms/ask_script_uuid.rb +22 -0
- data/lib/project_types/script/forms/run_against_shopify_org.rb +14 -0
- data/lib/project_types/script/graphql/app_script_set.graphql +2 -2
- data/lib/project_types/script/layers/application/build_script.rb +0 -1
- data/lib/project_types/script/layers/application/connect_app.rb +79 -0
- data/lib/project_types/script/layers/application/create_script.rb +17 -17
- data/lib/project_types/script/layers/application/push_script.rb +1 -1
- data/lib/project_types/script/layers/domain/errors.rb +1 -4
- data/lib/project_types/script/layers/domain/push_package.rb +3 -3
- data/lib/project_types/script/layers/domain/{script_json.rb → script_config.rb} +2 -2
- data/lib/project_types/script/layers/domain/script_project.rb +5 -1
- data/lib/project_types/script/layers/infrastructure/errors.rb +28 -6
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +0 -4
- data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +0 -4
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -2
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +125 -27
- data/lib/project_types/script/layers/infrastructure/script_service.rb +11 -11
- data/lib/project_types/script/messages/messages.rb +20 -5
- data/lib/project_types/script/ui/error_handler.rb +30 -20
- data/lib/project_types/theme/commands/pull.rb +3 -0
- data/lib/project_types/theme/commands/push.rb +7 -1
- data/lib/project_types/theme/commands/serve.rb +1 -1
- data/lib/project_types/theme/messages/messages.rb +10 -0
- data/lib/project_types/theme/ui/sync_progress_bar.rb +2 -2
- data/lib/shopify_cli/command/project_command.rb +20 -7
- data/lib/shopify_cli/command.rb +6 -0
- data/lib/shopify_cli/commands/app/create/node.rb +1 -3
- data/lib/shopify_cli/commands/app/create/rails.rb +1 -3
- data/lib/shopify_cli/commands/login.rb +1 -1
- data/lib/shopify_cli/commands/switch.rb +1 -1
- data/lib/shopify_cli/constants.rb +7 -0
- data/lib/shopify_cli/context.rb +11 -1
- data/lib/shopify_cli/environment.rb +4 -0
- data/lib/shopify_cli/form.rb +2 -0
- data/lib/shopify_cli/git.rb +2 -0
- data/lib/shopify_cli/identity_auth.rb +18 -0
- data/lib/shopify_cli/messages/messages.rb +8 -1
- data/lib/shopify_cli/partners_api/app_extensions/job.rb +36 -0
- data/lib/shopify_cli/partners_api/app_extensions.rb +46 -0
- data/lib/shopify_cli/partners_api/organizations.rb +2 -5
- data/lib/shopify_cli/partners_api.rb +2 -8
- data/lib/shopify_cli/project.rb +8 -7
- data/lib/shopify_cli/resources/env_file.rb +13 -5
- data/lib/shopify_cli/services/app/create/rails_service.rb +1 -1
- data/lib/shopify_cli/services/app/serve/node_service.rb +1 -1
- data/lib/shopify_cli/services/app/serve/rails_service.rb +1 -1
- data/lib/shopify_cli/tasks/ensure_authenticated.rb +9 -3
- data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +73 -0
- data/lib/shopify_cli/theme/dev_server/hot-reload.js +38 -9
- data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
- data/lib/shopify_cli/theme/dev_server/proxy.rb +9 -15
- data/lib/shopify_cli/theme/dev_server.rb +6 -4
- data/lib/shopify_cli/theme/syncer/error_reporter.rb +45 -0
- data/lib/shopify_cli/theme/syncer/operation.rb +56 -0
- data/lib/shopify_cli/theme/syncer/standard_reporter.rb +32 -0
- data/lib/shopify_cli/theme/syncer.rb +40 -39
- data/lib/shopify_cli/theme/theme.rb +31 -19
- data/lib/shopify_cli/thread_pool/job.rb +27 -0
- data/lib/shopify_cli/thread_pool.rb +37 -0
- data/lib/shopify_cli/tunnel.rb +9 -10
- data/lib/shopify_cli/version.rb +1 -1
- data/shopify-cli.gemspec +1 -1
- data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
- metadata +31 -8
- data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
- data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
- data/lib/project_types/script/tasks/ensure_env.rb +0 -106
@@ -17,19 +17,48 @@
|
|
17
17
|
|
18
18
|
connect();
|
19
19
|
|
20
|
+
function isRefreshRequired(files) {
|
21
|
+
return files.some((file) => !isCssFile(file) && !isSectionFile(file));
|
22
|
+
}
|
23
|
+
|
24
|
+
function refreshFile(file) {
|
25
|
+
if (isCssFile(file)) {
|
26
|
+
reloadCssFile(file);
|
27
|
+
return;
|
28
|
+
}
|
29
|
+
|
30
|
+
if (isSectionFile(file)) {
|
31
|
+
reloadSection(file);
|
32
|
+
return;
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
function setHotReloadCookie(files) {
|
37
|
+
var date = new Date();
|
38
|
+
|
39
|
+
// Hot reload cookie expires in 3 seconds
|
40
|
+
date.setSeconds(date.getSeconds() + 3);
|
41
|
+
|
42
|
+
var sections = files.join(',');
|
43
|
+
var expires = date.toUTCString();
|
44
|
+
|
45
|
+
document.cookie = `hot_reload_sections=${sections}; expires=${expires}; path=/`;
|
46
|
+
}
|
47
|
+
|
48
|
+
function refreshPage(files) {
|
49
|
+
setHotReloadCookie(files);
|
50
|
+
console.log('[HotReload] Refreshing entire page');
|
51
|
+
window.location.reload();
|
52
|
+
}
|
53
|
+
|
20
54
|
function handleUpdate(message) {
|
21
55
|
var data = JSON.parse(message.data);
|
56
|
+
var modifiedFiles = data.modified;
|
22
57
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
if (isCssFile(modified)) {
|
27
|
-
reloadCssFile(modified)
|
28
|
-
} else if (isSectionFile(modified)) {
|
29
|
-
reloadSection(modified);
|
58
|
+
if (isRefreshRequired(modifiedFiles)) {
|
59
|
+
refreshPage(modifiedFiles);
|
30
60
|
} else {
|
31
|
-
|
32
|
-
window.location.reload();
|
61
|
+
modifiedFiles.forEach(refreshFile);
|
33
62
|
}
|
34
63
|
}
|
35
64
|
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "cgi"
|
4
|
+
|
5
|
+
module ShopifyCLI
|
6
|
+
module Theme
|
7
|
+
module DevServer
|
8
|
+
class Proxy
|
9
|
+
class TemplateParamBuilder
|
10
|
+
def build
|
11
|
+
# Core doesn't support replace_templates
|
12
|
+
return {} if core?(current_path)
|
13
|
+
|
14
|
+
(syncer_templates + request_templates)
|
15
|
+
.select { |file| file.liquid? || file.json? }
|
16
|
+
.uniq(&:relative_path)
|
17
|
+
.map { |file| as_param(file) }
|
18
|
+
.to_h
|
19
|
+
end
|
20
|
+
|
21
|
+
def with_core_endpoints(core_endpoints)
|
22
|
+
@core_endpoints = core_endpoints
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def with_syncer(syncer)
|
27
|
+
@syncer = syncer
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
def with_rack_env(rack_env)
|
32
|
+
@rack_env = rack_env
|
33
|
+
self
|
34
|
+
end
|
35
|
+
|
36
|
+
def with_theme(theme)
|
37
|
+
@theme = theme
|
38
|
+
self
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def as_param(file)
|
44
|
+
["replace_templates[#{file.relative_path}]", file.read]
|
45
|
+
end
|
46
|
+
|
47
|
+
def syncer_templates
|
48
|
+
@syncer&.pending_updates || []
|
49
|
+
end
|
50
|
+
|
51
|
+
def request_templates
|
52
|
+
cookie_sections
|
53
|
+
.map { |section| @theme[section] unless @theme.nil? }
|
54
|
+
.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def cookie_sections
|
58
|
+
CGI::Cookie.parse(cookie)["hot_reload_sections"].join.split(",") || []
|
59
|
+
end
|
60
|
+
|
61
|
+
def core?(path)
|
62
|
+
core_endpoints.include?(path)
|
63
|
+
end
|
64
|
+
|
65
|
+
def current_path
|
66
|
+
rack_env["PATH_INFO"]
|
67
|
+
end
|
68
|
+
|
69
|
+
def cookie
|
70
|
+
rack_env["HTTP_COOKIE"]
|
71
|
+
end
|
72
|
+
|
73
|
+
def core_endpoints
|
74
|
+
@core_endpoints || []
|
75
|
+
end
|
76
|
+
|
77
|
+
def rack_env
|
78
|
+
@rack_env || {}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -2,6 +2,9 @@
|
|
2
2
|
require "net/http"
|
3
3
|
require "stringio"
|
4
4
|
require "time"
|
5
|
+
require "cgi"
|
6
|
+
|
7
|
+
require_relative "proxy/template_param_builder"
|
5
8
|
|
6
9
|
module ShopifyCLI
|
7
10
|
module Theme
|
@@ -112,21 +115,12 @@ module ShopifyCLI
|
|
112
115
|
end
|
113
116
|
|
114
117
|
def build_replace_templates_param(env)
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
# Only replace Liquid or JSON files
|
122
|
-
file.liquid? || file.json?
|
123
|
-
end
|
124
|
-
|
125
|
-
pending_templates.each do |path|
|
126
|
-
params["replace_templates[#{path.relative_path}]"] = path.read
|
127
|
-
end
|
128
|
-
|
129
|
-
params
|
118
|
+
TemplateParamBuilder.new
|
119
|
+
.with_core_endpoints(@core_endpoints)
|
120
|
+
.with_syncer(@syncer)
|
121
|
+
.with_theme(@theme)
|
122
|
+
.with_rack_env(env)
|
123
|
+
.build
|
130
124
|
end
|
131
125
|
|
132
126
|
def add_session_cookie(cookie_header)
|
@@ -3,6 +3,7 @@ require_relative "development_theme"
|
|
3
3
|
require_relative "ignore_filter"
|
4
4
|
require_relative "syncer"
|
5
5
|
|
6
|
+
require_relative "dev_server/cdn_fonts"
|
6
7
|
require_relative "dev_server/hot_reload"
|
7
8
|
require_relative "dev_server/header_hash"
|
8
9
|
require_relative "dev_server/local_assets"
|
@@ -24,7 +25,7 @@ module ShopifyCLI
|
|
24
25
|
class << self
|
25
26
|
attr_accessor :ctx
|
26
27
|
|
27
|
-
def start(ctx, root,
|
28
|
+
def start(ctx, root, host: "127.0.0.1", port: 9292, poll: false)
|
28
29
|
@ctx = ctx
|
29
30
|
theme = DevelopmentTheme.new(ctx, root: root)
|
30
31
|
ignore_filter = IgnoreFilter.from_path(root)
|
@@ -33,10 +34,11 @@ module ShopifyCLI
|
|
33
34
|
|
34
35
|
# Setup the middleware stack. Mimics Rack::Builder / config.ru, but in reverse order
|
35
36
|
@app = Proxy.new(ctx, theme: theme, syncer: @syncer)
|
37
|
+
@app = CdnFonts.new(@app, theme: theme)
|
36
38
|
@app = LocalAssets.new(ctx, @app, theme: theme)
|
37
39
|
@app = HotReload.new(ctx, @app, theme: theme, watcher: watcher, ignore_filter: ignore_filter)
|
38
40
|
stopped = false
|
39
|
-
address = "http://#{
|
41
|
+
address = "http://#{host}:#{port}"
|
40
42
|
|
41
43
|
theme.ensure_exists!
|
42
44
|
|
@@ -70,7 +72,7 @@ module ShopifyCLI
|
|
70
72
|
watcher.start
|
71
73
|
WebServer.run(
|
72
74
|
@app,
|
73
|
-
BindAddress:
|
75
|
+
BindAddress: host,
|
74
76
|
Port: port,
|
75
77
|
Logger: logger,
|
76
78
|
AccessLog: [],
|
@@ -83,7 +85,7 @@ module ShopifyCLI
|
|
83
85
|
rescue Errno::EADDRINUSE
|
84
86
|
abort_address_already_in_use(address)
|
85
87
|
rescue Errno::EADDRNOTAVAIL
|
86
|
-
raise AddressBindingError, "Error binding to the address #{
|
88
|
+
raise AddressBindingError, "Error binding to the address #{host}."
|
87
89
|
end
|
88
90
|
|
89
91
|
def stop
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
class Syncer
|
6
|
+
##
|
7
|
+
# ShopifyCLI::Theme::Syncer::ErrorReporter allows delaying log of errors,
|
8
|
+
# mainly to not break the progress bar.
|
9
|
+
#
|
10
|
+
class ErrorReporter
|
11
|
+
attr_reader :ctx, :delayed_errors
|
12
|
+
|
13
|
+
def initialize(ctx)
|
14
|
+
@ctx = ctx
|
15
|
+
@has_any_error = false
|
16
|
+
@delay_errors = false
|
17
|
+
@delayed_errors = []
|
18
|
+
end
|
19
|
+
|
20
|
+
def disable!
|
21
|
+
@delay_errors = true
|
22
|
+
end
|
23
|
+
|
24
|
+
def enable!
|
25
|
+
@delay_errors = false
|
26
|
+
@delayed_errors.each { |error| report(error) }
|
27
|
+
@delayed_errors.clear
|
28
|
+
end
|
29
|
+
|
30
|
+
def report(error_message)
|
31
|
+
if @delay_errors
|
32
|
+
@delayed_errors << error_message
|
33
|
+
else
|
34
|
+
@has_any_error = true
|
35
|
+
@ctx.error(error_message)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def has_any_error?
|
40
|
+
@has_any_error
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
class Syncer
|
6
|
+
class Operation
|
7
|
+
attr_accessor :method, :file
|
8
|
+
|
9
|
+
COLOR_BY_STATUS = {
|
10
|
+
error: :red,
|
11
|
+
synced: :green,
|
12
|
+
fixed: :cyan,
|
13
|
+
}
|
14
|
+
|
15
|
+
def initialize(ctx, method, file)
|
16
|
+
@ctx = ctx
|
17
|
+
@method = method
|
18
|
+
@file = file
|
19
|
+
end
|
20
|
+
|
21
|
+
def to_s
|
22
|
+
"#{method} #{file_path}"
|
23
|
+
end
|
24
|
+
|
25
|
+
def as_error_message
|
26
|
+
as_message_with(status: :error)
|
27
|
+
end
|
28
|
+
|
29
|
+
def as_synced_message
|
30
|
+
as_message_with(status: :synced)
|
31
|
+
end
|
32
|
+
|
33
|
+
def as_fix_message
|
34
|
+
as_message_with(status: :fixed)
|
35
|
+
end
|
36
|
+
|
37
|
+
def file_path
|
38
|
+
file&.relative_path.to_s
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def as_message_with(status:)
|
44
|
+
status_color = COLOR_BY_STATUS[status]
|
45
|
+
status_text = @ctx.message("theme.serve.operation.status.#{status}").ljust(6)
|
46
|
+
|
47
|
+
"#{timestamp} {{#{status_color}:#{status_text}}} {{>}} {{blue:#{self}}}"
|
48
|
+
end
|
49
|
+
|
50
|
+
def timestamp
|
51
|
+
Time.now.strftime("%T")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
class Syncer
|
6
|
+
##
|
7
|
+
# ShopifyCLI::Theme::Syncer::StdReporter allows disabling/enabling
|
8
|
+
# messages reported in the standard output (ShopifyCLI::Context#puts).
|
9
|
+
#
|
10
|
+
class StandardReporter
|
11
|
+
attr_reader :ctx
|
12
|
+
|
13
|
+
def initialize(ctx)
|
14
|
+
@enabled = true
|
15
|
+
@ctx = ctx
|
16
|
+
end
|
17
|
+
|
18
|
+
def disable!
|
19
|
+
@enabled = false
|
20
|
+
end
|
21
|
+
|
22
|
+
def enable!
|
23
|
+
@enabled = true
|
24
|
+
end
|
25
|
+
|
26
|
+
def report(message)
|
27
|
+
ctx.puts(message) if @enabled
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -2,24 +2,31 @@
|
|
2
2
|
require "thread"
|
3
3
|
require "json"
|
4
4
|
require "base64"
|
5
|
+
require "forwardable"
|
6
|
+
|
7
|
+
require_relative "syncer/error_reporter"
|
8
|
+
require_relative "syncer/standard_reporter"
|
9
|
+
require_relative "syncer/operation"
|
5
10
|
|
6
11
|
module ShopifyCLI
|
7
12
|
module Theme
|
8
13
|
class Syncer
|
9
|
-
|
10
|
-
|
11
|
-
"#{method} #{file&.relative_path}"
|
12
|
-
end
|
13
|
-
end
|
14
|
+
extend Forwardable
|
15
|
+
|
14
16
|
API_VERSION = "unstable"
|
15
17
|
|
16
18
|
attr_reader :checksums
|
17
19
|
attr_accessor :ignore_filter
|
18
20
|
|
21
|
+
def_delegators :@error_reporter, :has_any_error?
|
22
|
+
|
19
23
|
def initialize(ctx, theme:, ignore_filter: nil)
|
20
24
|
@ctx = ctx
|
21
25
|
@theme = theme
|
22
26
|
@ignore_filter = ignore_filter
|
27
|
+
@error_reporter = ErrorReporter.new(ctx)
|
28
|
+
@standard_reporter = StandardReporter.new(ctx)
|
29
|
+
@reporters = [@error_reporter, @standard_reporter]
|
23
30
|
|
24
31
|
# Queue of `Operation`s waiting to be picked up from a thread for processing.
|
25
32
|
@queue = Queue.new
|
@@ -30,12 +37,19 @@ module ShopifyCLI
|
|
30
37
|
# Mutex used to pause all threads when backing-off when hitting API rate limits
|
31
38
|
@backoff_mutex = Mutex.new
|
32
39
|
|
33
|
-
# Allows delaying log of errors, mainly to not break the progress bar.
|
34
|
-
@delay_errors = false
|
35
|
-
@delayed_errors = []
|
36
|
-
|
37
40
|
# Latest theme assets checksums. Updated on each upload.
|
38
41
|
@checksums = {}
|
42
|
+
|
43
|
+
# Checksums of assets with errors.
|
44
|
+
@error_checksums = []
|
45
|
+
end
|
46
|
+
|
47
|
+
def lock_io!
|
48
|
+
@reporters.each { |reporter| reporter.disable! }
|
49
|
+
end
|
50
|
+
|
51
|
+
def unlock_io!
|
52
|
+
@reporters.each { |reporter| reporter.enable! }
|
39
53
|
end
|
40
54
|
|
41
55
|
def enqueue_updates(files)
|
@@ -103,25 +117,14 @@ module ShopifyCLI
|
|
103
117
|
break if operation.nil? # shutdown was called
|
104
118
|
perform(operation)
|
105
119
|
rescue Exception => e
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
)
|
120
|
+
error_suffix = ": #{e}"
|
121
|
+
error_suffix += + "\n\t#{e.backtrace.join("\n\t")}" if @ctx.debug?
|
122
|
+
report_error(operation, error_suffix)
|
110
123
|
end
|
111
124
|
end
|
112
125
|
end
|
113
126
|
end
|
114
127
|
|
115
|
-
def delay_errors!
|
116
|
-
@delay_errors = true
|
117
|
-
end
|
118
|
-
|
119
|
-
def report_errors!
|
120
|
-
@delay_errors = false
|
121
|
-
@delayed_errors.each { |error| report_error(error) }
|
122
|
-
@delayed_errors.clear
|
123
|
-
end
|
124
|
-
|
125
128
|
def upload_theme!(delay_low_priority_files: false, delete: true, &block)
|
126
129
|
fetch_checksums!
|
127
130
|
|
@@ -177,21 +180,27 @@ module ShopifyCLI
|
|
177
180
|
|
178
181
|
private
|
179
182
|
|
183
|
+
def report_error(operation, error_suffix = "")
|
184
|
+
@error_checksums << @checksums[operation.file_path]
|
185
|
+
@error_reporter.report("#{operation.as_error_message}#{error_suffix}")
|
186
|
+
end
|
187
|
+
|
180
188
|
def enqueue(method, file)
|
181
189
|
raise ArgumentError, "file required" unless file
|
182
190
|
|
183
|
-
operation = Operation.new(method, @theme[file])
|
191
|
+
operation = Operation.new(@ctx, method, @theme[file])
|
184
192
|
|
185
193
|
# Already enqueued
|
186
194
|
return if @pending.include?(operation)
|
187
195
|
|
188
|
-
if @ignore_filter&.ignore?(operation.
|
189
|
-
@ctx.debug("ignore #{operation.
|
196
|
+
if @ignore_filter&.ignore?(operation.file_path)
|
197
|
+
@ctx.debug("ignore #{operation.file_path}")
|
190
198
|
return
|
191
199
|
end
|
192
200
|
|
193
201
|
if [:update, :get].include?(method) && operation.file.exist? && !file_has_changed?(operation.file)
|
194
|
-
|
202
|
+
is_fixed = !!@error_checksums.delete(operation.file.checksum)
|
203
|
+
@standard_reporter.report(operation.as_fix_message) if is_fixed
|
195
204
|
return
|
196
205
|
end
|
197
206
|
|
@@ -206,16 +215,16 @@ module ShopifyCLI
|
|
206
215
|
|
207
216
|
response = send(operation.method, operation.file)
|
208
217
|
|
218
|
+
@standard_reporter.report(operation.as_synced_message)
|
219
|
+
|
209
220
|
# Check if the API told us we're near the rate limit
|
210
221
|
if !backingoff? && (limit = response["x-shopify-shop-api-call-limit"])
|
211
222
|
used, total = limit.split("/").map(&:to_i)
|
212
223
|
backoff_if_near_limit!(used, total)
|
213
224
|
end
|
214
225
|
rescue ShopifyCLI::API::APIRequestError => e
|
215
|
-
|
216
|
-
|
217
|
-
parse_api_errors(e).join("\n ")
|
218
|
-
)
|
226
|
+
error_suffix = ":\n " + parse_api_errors(e).join("\n ")
|
227
|
+
report_error(operation, error_suffix)
|
219
228
|
ensure
|
220
229
|
@pending.delete(operation)
|
221
230
|
end
|
@@ -295,14 +304,6 @@ module ShopifyCLI
|
|
295
304
|
file.checksum != @checksums[file.relative_path.to_s]
|
296
305
|
end
|
297
306
|
|
298
|
-
def report_error(error)
|
299
|
-
if @delay_errors
|
300
|
-
@delayed_errors << error
|
301
|
-
else
|
302
|
-
@ctx.puts(error)
|
303
|
-
end
|
304
|
-
end
|
305
|
-
|
306
307
|
def parse_api_errors(exception)
|
307
308
|
parsed_body = JSON.parse(exception&.response&.body)
|
308
309
|
message = parsed_body.dig("errors", "asset") || parsed_body["message"] || exception.message
|
@@ -162,26 +162,38 @@ module ShopifyCLI
|
|
162
162
|
}
|
163
163
|
end
|
164
164
|
|
165
|
-
|
166
|
-
|
167
|
-
ctx
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
165
|
+
class << self
|
166
|
+
def all(ctx, root: nil)
|
167
|
+
_status, body = fetch_themes(ctx)
|
168
|
+
|
169
|
+
body["themes"]
|
170
|
+
.sort_by { |theme_attrs| Time.parse(theme_attrs["updated_at"]) }
|
171
|
+
.reverse
|
172
|
+
.map { |theme_attrs| new(ctx, root: root, **allowed_attrs(theme_attrs)) }
|
173
|
+
end
|
174
|
+
|
175
|
+
def live(ctx, root: nil)
|
176
|
+
_status, body = fetch_themes(ctx)
|
172
177
|
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
178
|
+
body["themes"]
|
179
|
+
.find { |theme_attrs| theme_attrs["role"] == "main" }
|
180
|
+
.tap { |theme_attrs| break new(ctx, root: root, **allowed_attrs(theme_attrs)) }
|
181
|
+
end
|
182
|
+
|
183
|
+
private
|
184
|
+
|
185
|
+
def allowed_attrs(attrs)
|
186
|
+
attrs.slice("id", "name", "role").transform_keys(&:to_sym)
|
187
|
+
end
|
188
|
+
|
189
|
+
def fetch_themes(ctx)
|
190
|
+
AdminAPI.rest_request(
|
191
|
+
ctx,
|
192
|
+
shop: AdminAPI.get_shop_or_abort(ctx),
|
193
|
+
path: "themes.json",
|
194
|
+
api_version: "unstable",
|
195
|
+
)
|
196
|
+
end
|
185
197
|
end
|
186
198
|
|
187
199
|
private
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
class ThreadPool
|
5
|
+
class Job
|
6
|
+
attr_reader :error
|
7
|
+
|
8
|
+
def perform!
|
9
|
+
raise "`#{self.class.name}#perform!` must be defined"
|
10
|
+
end
|
11
|
+
|
12
|
+
def call
|
13
|
+
perform!
|
14
|
+
rescue StandardError => error
|
15
|
+
@error = error
|
16
|
+
end
|
17
|
+
|
18
|
+
def success?
|
19
|
+
!@error
|
20
|
+
end
|
21
|
+
|
22
|
+
def error?
|
23
|
+
!!@error
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
class ThreadPool
|
5
|
+
attr_reader :errors
|
6
|
+
|
7
|
+
def initialize(pool_size: 10)
|
8
|
+
@jobs = Queue.new
|
9
|
+
@pool = Array.new(pool_size) { spawn_thread }
|
10
|
+
end
|
11
|
+
|
12
|
+
def schedule(job)
|
13
|
+
@jobs << job
|
14
|
+
end
|
15
|
+
|
16
|
+
def shutdown
|
17
|
+
@pool.size.times do
|
18
|
+
schedule(-> { throw(:stop_thread) })
|
19
|
+
end
|
20
|
+
@pool.map(&:join)
|
21
|
+
ensure
|
22
|
+
@jobs.close
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def spawn_thread
|
28
|
+
Thread.new do
|
29
|
+
catch(:stop_thread) do
|
30
|
+
loop do
|
31
|
+
@jobs.pop.call
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|