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.
- checksums.yaml +4 -4
- data/.github/CONTRIBUTING.md +1 -1
- data/.github/workflows/stale.yml +46 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +1 -0
- data/Gemfile.lock +39 -7
- data/Rakefile +40 -0
- data/ext/shopify-extensions/version +1 -1
- data/lib/project_types/extension/forms/questions/ask_template.rb +5 -8
- data/lib/project_types/extension/messages/messages.rb +11 -1
- data/lib/project_types/extension/models/development_server_requirements.rb +13 -7
- data/lib/project_types/extension/models/server_config/root.rb +2 -0
- data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +13 -0
- data/lib/project_types/script/config/extension_points.yml +18 -0
- data/lib/project_types/script/layers/infrastructure/errors.rb +17 -0
- data/lib/project_types/script/layers/infrastructure/script_service.rb +2 -0
- data/lib/project_types/script/messages/messages.rb +3 -0
- data/lib/project_types/script/ui/error_handler.rb +11 -0
- data/lib/project_types/theme/commands/pull.rb +2 -2
- data/lib/project_types/theme/commands/push.rb +2 -2
- data/lib/project_types/theme/commands/serve.rb +1 -0
- data/lib/project_types/theme/conversions/base_glob.rb +20 -5
- data/lib/project_types/theme/messages/messages.rb +47 -8
- data/lib/shopify_cli/changelog.rb +76 -0
- data/lib/shopify_cli/command.rb +8 -7
- data/lib/shopify_cli/environment.rb +19 -11
- data/lib/shopify_cli/git.rb +36 -0
- data/lib/shopify_cli/messages/messages.rb +16 -9
- data/lib/shopify_cli/release.rb +194 -0
- data/lib/shopify_cli/sed.rb +19 -0
- data/lib/shopify_cli/services/app/create/node_service.rb +2 -14
- data/lib/shopify_cli/services/app/create/php_service.rb +1 -6
- data/lib/shopify_cli/services/app/create/rails_service.rb +4 -12
- data/lib/shopify_cli/theme/dev_server/hot-reload.js +40 -13
- data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/hot_reload/sections_index.rb +51 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload.rb +6 -1
- data/lib/shopify_cli/theme/dev_server/local_assets.rb +1 -1
- data/lib/shopify_cli/theme/dev_server/remote_watcher/json_files_update_job.rb +34 -0
- data/lib/shopify_cli/theme/dev_server/remote_watcher.rb +44 -0
- data/lib/shopify_cli/theme/dev_server/watcher.rb +1 -1
- data/lib/shopify_cli/theme/dev_server.rb +15 -3
- data/lib/shopify_cli/theme/file.rb +15 -4
- data/lib/shopify_cli/theme/syncer/checksums.rb +60 -0
- data/lib/shopify_cli/theme/syncer/forms/apply_to_all.rb +39 -0
- data/lib/shopify_cli/theme/syncer/forms/apply_to_all_form.rb +35 -0
- data/lib/shopify_cli/theme/syncer/forms/base_strategy_form.rb +62 -0
- data/lib/shopify_cli/theme/syncer/forms/select_delete_strategy.rb +27 -0
- data/lib/shopify_cli/theme/syncer/forms/select_update_strategy.rb +28 -0
- data/lib/shopify_cli/theme/syncer/ignore_helper.rb +33 -0
- data/lib/shopify_cli/theme/syncer/json_delete_handler.rb +51 -0
- data/lib/shopify_cli/theme/syncer/json_update_handler.rb +82 -0
- data/lib/shopify_cli/theme/syncer/merger.rb +53 -0
- data/lib/shopify_cli/theme/syncer/operation.rb +1 -1
- data/lib/shopify_cli/theme/syncer.rb +79 -63
- data/lib/shopify_cli/theme/theme.rb +12 -4
- data/lib/shopify_cli/theme/theme_admin_api.rb +24 -23
- data/lib/shopify_cli/thread_pool/job.rb +10 -2
- data/lib/shopify_cli/thread_pool.rb +15 -3
- data/lib/shopify_cli/tunnel.rb +9 -0
- data/lib/shopify_cli/version.rb +1 -1
- data/shopify-cli.gemspec +3 -1
- metadata +21 -4
- data/lib/project_types/rails/ruby.rb +0 -17
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
module DevServer
|
6
|
+
class HotReload
|
7
|
+
class SectionsIndex
|
8
|
+
def initialize(theme)
|
9
|
+
@theme = theme
|
10
|
+
end
|
11
|
+
|
12
|
+
def section_names_by_type
|
13
|
+
index = {}
|
14
|
+
|
15
|
+
files.each do |file|
|
16
|
+
section_hash(file).each do |key, value|
|
17
|
+
name = key
|
18
|
+
type = value&.dig("type")
|
19
|
+
|
20
|
+
next if !name || !type
|
21
|
+
|
22
|
+
index[type] = [] unless index[type]
|
23
|
+
index[type] << name
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
index
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def section_hash(file)
|
33
|
+
content = JSON.parse(file.read)
|
34
|
+
return [] unless content.is_a?(Hash)
|
35
|
+
|
36
|
+
sections = content["sections"]
|
37
|
+
return [] if sections.nil?
|
38
|
+
|
39
|
+
sections
|
40
|
+
rescue JSON::JSONError
|
41
|
+
[]
|
42
|
+
end
|
43
|
+
|
44
|
+
def files
|
45
|
+
@theme.json_files
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "hot_reload/remote_file_reloader"
|
4
|
+
require_relative "hot_reload/sections_index"
|
4
5
|
|
5
6
|
module ShopifyCLI
|
6
7
|
module Theme
|
@@ -13,6 +14,7 @@ module ShopifyCLI
|
|
13
14
|
@mode = mode
|
14
15
|
@streams = SSE::Streams.new
|
15
16
|
@remote_file_reloader = RemoteFileReloader.new(ctx, theme: @theme, streams: @streams)
|
17
|
+
@sections_index = SectionsIndex.new(@theme)
|
16
18
|
@watcher = watcher
|
17
19
|
@watcher.add_observer(self, :notify_streams_of_file_change)
|
18
20
|
@ignore_filter = ignore_filter
|
@@ -78,7 +80,10 @@ module ShopifyCLI
|
|
78
80
|
end
|
79
81
|
|
80
82
|
def params_js
|
81
|
-
env = {
|
83
|
+
env = {
|
84
|
+
mode: @mode,
|
85
|
+
section_names_by_type: @sections_index.section_names_by_type,
|
86
|
+
}
|
82
87
|
<<~JS
|
83
88
|
(() => {
|
84
89
|
window.__SHOPIFY_CLI_ENV__ = #{env.to_json};
|
@@ -0,0 +1,34 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shopify_cli/thread_pool/job"
|
4
|
+
|
5
|
+
module ShopifyCLI
|
6
|
+
module Theme
|
7
|
+
module DevServer
|
8
|
+
class RemoteWatcher
|
9
|
+
class JsonFilesUpdateJob < ShopifyCLI::ThreadPool::Job
|
10
|
+
def initialize(theme, syncer, interval)
|
11
|
+
super(interval)
|
12
|
+
|
13
|
+
@theme = theme
|
14
|
+
@syncer = syncer
|
15
|
+
end
|
16
|
+
|
17
|
+
def perform!
|
18
|
+
@syncer.fetch_checksums!
|
19
|
+
@syncer.enqueue_get(json_files)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def json_files
|
25
|
+
@theme
|
26
|
+
.json_files
|
27
|
+
.reject { |file| @syncer.pending_updates.include?(file) }
|
28
|
+
.reject { |file| @syncer.broken_file?(file) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "shopify_cli/thread_pool"
|
4
|
+
|
5
|
+
require_relative "remote_watcher/json_files_update_job"
|
6
|
+
|
7
|
+
module ShopifyCLI
|
8
|
+
module Theme
|
9
|
+
module DevServer
|
10
|
+
class RemoteWatcher
|
11
|
+
SYNC_INTERVAL = 3 # seconds
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def to(theme:, syncer:)
|
15
|
+
new(theme, syncer)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def start
|
20
|
+
thread_pool.schedule(recurring_job)
|
21
|
+
end
|
22
|
+
|
23
|
+
def stop
|
24
|
+
thread_pool.shutdown
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def initialize(theme, syncer)
|
30
|
+
@theme = theme
|
31
|
+
@syncer = syncer
|
32
|
+
end
|
33
|
+
|
34
|
+
def thread_pool
|
35
|
+
@thread_pool ||= ShopifyCLI::ThreadPool.new(pool_size: 1)
|
36
|
+
end
|
37
|
+
|
38
|
+
def recurring_job
|
39
|
+
JsonFilesUpdateJob.new(@theme, @syncer, SYNC_INTERVAL)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -11,6 +11,7 @@ require_relative "dev_server/local_assets"
|
|
11
11
|
require_relative "dev_server/proxy"
|
12
12
|
require_relative "dev_server/sse"
|
13
13
|
require_relative "dev_server/watcher"
|
14
|
+
require_relative "dev_server/remote_watcher"
|
14
15
|
require_relative "dev_server/web_server"
|
15
16
|
require_relative "dev_server/certificate_manager"
|
16
17
|
|
@@ -26,12 +27,13 @@ module ShopifyCLI
|
|
26
27
|
class << self
|
27
28
|
attr_accessor :ctx
|
28
29
|
|
29
|
-
def start(ctx, root, host: "127.0.0.1", port: 9292, poll: false, mode: ReloadMode.default)
|
30
|
+
def start(ctx, root, host: "127.0.0.1", port: 9292, poll: false, editor_sync: false, mode: ReloadMode.default)
|
30
31
|
@ctx = ctx
|
31
32
|
theme = DevelopmentTheme.find_or_create!(ctx, root: root)
|
32
33
|
ignore_filter = IgnoreFilter.from_path(root)
|
33
|
-
@syncer = Syncer.new(ctx, theme: theme, ignore_filter: ignore_filter)
|
34
|
+
@syncer = Syncer.new(ctx, theme: theme, ignore_filter: ignore_filter, overwrite_json: !editor_sync)
|
34
35
|
watcher = Watcher.new(ctx, theme: theme, syncer: @syncer, ignore_filter: ignore_filter, poll: poll)
|
36
|
+
remote_watcher = RemoteWatcher.to(theme: theme, syncer: @syncer)
|
35
37
|
|
36
38
|
# Setup the middleware stack. Mimics Rack::Builder / config.ru, but in reverse order
|
37
39
|
@app = Proxy.new(ctx, theme: theme, syncer: @syncer)
|
@@ -57,9 +59,17 @@ module ShopifyCLI
|
|
57
59
|
|
58
60
|
return if stopped
|
59
61
|
|
62
|
+
preview_suffix = editor_sync ? "" : ctx.message("theme.serve.download_changes")
|
63
|
+
preview_message = ctx.message(
|
64
|
+
"theme.serve.customize_or_preview",
|
65
|
+
preview_suffix,
|
66
|
+
theme.editor_url,
|
67
|
+
theme.preview_url
|
68
|
+
)
|
69
|
+
|
60
70
|
ctx.puts(ctx.message("theme.serve.serving", theme.root))
|
61
71
|
ctx.open_url!(address)
|
62
|
-
ctx.puts(
|
72
|
+
ctx.puts(preview_message)
|
63
73
|
end
|
64
74
|
|
65
75
|
logger = if ctx.debug?
|
@@ -69,6 +79,7 @@ module ShopifyCLI
|
|
69
79
|
end
|
70
80
|
|
71
81
|
watcher.start
|
82
|
+
remote_watcher.start if editor_sync
|
72
83
|
WebServer.run(
|
73
84
|
@app,
|
74
85
|
BindAddress: host,
|
@@ -76,6 +87,7 @@ module ShopifyCLI
|
|
76
87
|
Logger: logger,
|
77
88
|
AccessLog: [],
|
78
89
|
)
|
90
|
+
remote_watcher.stop if editor_sync
|
79
91
|
watcher.stop
|
80
92
|
|
81
93
|
rescue ShopifyCLI::API::APIRequestForbiddenError,
|
@@ -4,7 +4,6 @@ require_relative "mime_type"
|
|
4
4
|
module ShopifyCLI
|
5
5
|
module Theme
|
6
6
|
class File < Struct.new(:path)
|
7
|
-
attr_reader :relative_path
|
8
7
|
attr_accessor :remote_checksum
|
9
8
|
|
10
9
|
def initialize(path, root)
|
@@ -42,7 +41,7 @@ module ShopifyCLI
|
|
42
41
|
end
|
43
42
|
|
44
43
|
def mime_type
|
45
|
-
@mime_type ||= MimeType.by_filename(relative_path)
|
44
|
+
@mime_type ||= MimeType.by_filename(@relative_path)
|
46
45
|
end
|
47
46
|
|
48
47
|
def text?
|
@@ -54,7 +53,7 @@ module ShopifyCLI
|
|
54
53
|
end
|
55
54
|
|
56
55
|
def liquid_css?
|
57
|
-
relative_path.
|
56
|
+
relative_path.end_with?(".css.liquid")
|
58
57
|
end
|
59
58
|
|
60
59
|
def json?
|
@@ -62,7 +61,7 @@ module ShopifyCLI
|
|
62
61
|
end
|
63
62
|
|
64
63
|
def template?
|
65
|
-
relative_path.
|
64
|
+
relative_path.start_with?("templates/")
|
66
65
|
end
|
67
66
|
|
68
67
|
def checksum
|
@@ -84,6 +83,18 @@ module ShopifyCLI
|
|
84
83
|
relative_path == other.relative_path
|
85
84
|
end
|
86
85
|
|
86
|
+
def name(*args)
|
87
|
+
::File.basename(path, *args)
|
88
|
+
end
|
89
|
+
|
90
|
+
def absolute_path
|
91
|
+
path.realpath.to_s
|
92
|
+
end
|
93
|
+
|
94
|
+
def relative_path
|
95
|
+
@relative_path.to_s
|
96
|
+
end
|
97
|
+
|
87
98
|
private
|
88
99
|
|
89
100
|
def normalize_json(content)
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
class Syncer
|
6
|
+
class Checksums
|
7
|
+
def initialize(theme)
|
8
|
+
@theme = theme
|
9
|
+
@checksum_by_key = {}
|
10
|
+
|
11
|
+
# Mutex used to coordinate changes in the checksums (shared accross `Syncer` threads)
|
12
|
+
@checksums_mutex = Mutex.new
|
13
|
+
end
|
14
|
+
|
15
|
+
def has?(file)
|
16
|
+
checksum_by_key.key?(to_key(file))
|
17
|
+
end
|
18
|
+
|
19
|
+
def file_has_changed?(file)
|
20
|
+
file.checksum != checksum_by_key[file.relative_path]
|
21
|
+
end
|
22
|
+
|
23
|
+
def keys
|
24
|
+
checksum_by_key.keys
|
25
|
+
end
|
26
|
+
|
27
|
+
def [](key)
|
28
|
+
checksum_by_key[key]
|
29
|
+
end
|
30
|
+
|
31
|
+
def []=(key, value)
|
32
|
+
checksums_mutex.synchronize do
|
33
|
+
checksum_by_key[key] = value
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Generate .liquid asset files are reported twice in checksum:
|
38
|
+
# once of generated, once for .liquid. We only keep the .liquid, that's the one we have
|
39
|
+
# on disk.
|
40
|
+
def reject_duplicated_checksums!
|
41
|
+
checksums_mutex.synchronize do
|
42
|
+
checksum_by_key.reject! { |key, _| checksum_by_key.key?("#{key}.liquid") }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def to_key(file)
|
49
|
+
theme[file].relative_path
|
50
|
+
end
|
51
|
+
|
52
|
+
# Private getters only used in unit tests
|
53
|
+
|
54
|
+
attr_reader :checksum_by_key
|
55
|
+
attr_reader :theme
|
56
|
+
attr_reader :checksums_mutex
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "apply_to_all_form"
|
4
|
+
|
5
|
+
module ShopifyCLI
|
6
|
+
module Theme
|
7
|
+
class Syncer
|
8
|
+
module Forms
|
9
|
+
class ApplyToAll
|
10
|
+
attr_reader :value
|
11
|
+
|
12
|
+
def initialize(ctx, number_of_files)
|
13
|
+
@ctx = ctx
|
14
|
+
@number_of_files = number_of_files
|
15
|
+
@value = nil
|
16
|
+
@apply = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
def apply?(value)
|
20
|
+
return unless @number_of_files > 1
|
21
|
+
|
22
|
+
if @apply.nil?
|
23
|
+
@apply = ask.apply?
|
24
|
+
@value = value if @apply
|
25
|
+
end
|
26
|
+
|
27
|
+
@apply
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def ask
|
33
|
+
ApplyToAllForm.ask(@ctx, [], number_of_files: @number_of_files)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
class Syncer
|
6
|
+
module Forms
|
7
|
+
class ApplyToAllForm < ShopifyCLI::Form
|
8
|
+
attr_accessor :apply
|
9
|
+
flag_arguments :number_of_files
|
10
|
+
|
11
|
+
def ask
|
12
|
+
title = message("title", number_of_files - 1)
|
13
|
+
|
14
|
+
self.apply = CLI::UI::Prompt.ask(title, allow_empty: false) do |handler|
|
15
|
+
handler.option(message("yes")) { true }
|
16
|
+
handler.option(message("no")) { false }
|
17
|
+
end
|
18
|
+
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def apply?
|
23
|
+
apply
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def message(key, *params)
|
29
|
+
ctx.message("theme.serve.syncer.forms.apply_to_all.#{key}", *params)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
class Syncer
|
6
|
+
module Forms
|
7
|
+
class BaseStrategyForm < ShopifyCLI::Form
|
8
|
+
attr_accessor :strategy
|
9
|
+
|
10
|
+
def ask
|
11
|
+
ctx.puts(title_context(file))
|
12
|
+
|
13
|
+
self.strategy = CLI::UI::Prompt.ask(title_question, allow_empty: false) do |handler|
|
14
|
+
strategies.each do |strategy|
|
15
|
+
handler.option(as_text(strategy)) { strategy }
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
exit_cli if self.strategy == :exit
|
20
|
+
|
21
|
+
self
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
##
|
27
|
+
# List of strategies that populate the form options
|
28
|
+
#
|
29
|
+
def strategies
|
30
|
+
raise "`#{self.class.name}#strategies' must be defined"
|
31
|
+
end
|
32
|
+
|
33
|
+
##
|
34
|
+
# Message prefix for the form title and options (strategies).
|
35
|
+
# See the methods `title` and `as_text`
|
36
|
+
#
|
37
|
+
def prefix
|
38
|
+
raise "`#{self.class.name}#prefix' must be defined"
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def exit_cli
|
44
|
+
exit(0)
|
45
|
+
end
|
46
|
+
|
47
|
+
def title_context(file)
|
48
|
+
ctx.message("#{prefix}.title_context", file.relative_path)
|
49
|
+
end
|
50
|
+
|
51
|
+
def title_question
|
52
|
+
ctx.message("#{prefix}.title_question")
|
53
|
+
end
|
54
|
+
|
55
|
+
def as_text(strategy)
|
56
|
+
ctx.message("#{prefix}.#{strategy}")
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_strategy_form"
|
4
|
+
|
5
|
+
module ShopifyCLI
|
6
|
+
module Theme
|
7
|
+
class Syncer
|
8
|
+
module Forms
|
9
|
+
class SelectDeleteStrategy < BaseStrategyForm
|
10
|
+
flag_arguments :file
|
11
|
+
|
12
|
+
def strategies
|
13
|
+
%i[
|
14
|
+
delete
|
15
|
+
restore
|
16
|
+
exit
|
17
|
+
]
|
18
|
+
end
|
19
|
+
|
20
|
+
def prefix
|
21
|
+
"theme.serve.syncer.forms.delete_strategy"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_strategy_form"
|
4
|
+
|
5
|
+
module ShopifyCLI
|
6
|
+
module Theme
|
7
|
+
class Syncer
|
8
|
+
module Forms
|
9
|
+
class SelectUpdateStrategy < BaseStrategyForm
|
10
|
+
flag_arguments :file
|
11
|
+
|
12
|
+
def strategies
|
13
|
+
%i[
|
14
|
+
keep_remote
|
15
|
+
keep_local
|
16
|
+
union_merge
|
17
|
+
exit
|
18
|
+
]
|
19
|
+
end
|
20
|
+
|
21
|
+
def prefix
|
22
|
+
"theme.serve.syncer.forms.update_strategy"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
class Syncer
|
6
|
+
module IgnoreHelper
|
7
|
+
def ignore_operation?(operation)
|
8
|
+
path = operation.file_path
|
9
|
+
ignore_path?(path)
|
10
|
+
end
|
11
|
+
|
12
|
+
def ignore_file?(file)
|
13
|
+
path = file.path
|
14
|
+
ignore_path?(path)
|
15
|
+
end
|
16
|
+
|
17
|
+
def ignore_path?(path)
|
18
|
+
ignored_by_ignore_filter?(path) || ignored_by_include_filter?(path)
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def ignored_by_ignore_filter?(path)
|
24
|
+
ignore_filter&.ignore?(path)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ignored_by_include_filter?(path)
|
28
|
+
!!include_filter && !include_filter.match?(path)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "forms/apply_to_all"
|
4
|
+
require_relative "forms/select_delete_strategy"
|
5
|
+
|
6
|
+
module ShopifyCLI
|
7
|
+
module Theme
|
8
|
+
class Syncer
|
9
|
+
module JsonDeleteHandler
|
10
|
+
def enqueue_json_deletes(files)
|
11
|
+
files = files.select { |file| !ignore_file?(file) }
|
12
|
+
|
13
|
+
if overwrite_json?
|
14
|
+
enqueue_deletes(files)
|
15
|
+
else
|
16
|
+
# Handle conflicts when JSON files cannot be overwritten
|
17
|
+
handle_delete_conflicts(files)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def handle_delete_conflicts(files)
|
24
|
+
to_delete = []
|
25
|
+
to_get = []
|
26
|
+
|
27
|
+
apply_to_all = Forms::ApplyToAll.new(@ctx, files.size)
|
28
|
+
|
29
|
+
files.each do |file|
|
30
|
+
delete_strategy = apply_to_all.value || ask_delete_strategy(file)
|
31
|
+
apply_to_all.apply?(delete_strategy)
|
32
|
+
|
33
|
+
case delete_strategy
|
34
|
+
when :delete
|
35
|
+
to_delete << file
|
36
|
+
when :restore
|
37
|
+
to_get << file
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
enqueue_deletes(to_delete)
|
42
|
+
enqueue_get(to_get)
|
43
|
+
end
|
44
|
+
|
45
|
+
def ask_delete_strategy(file)
|
46
|
+
Forms::SelectDeleteStrategy.ask(@ctx, [], file: file).strategy
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|