shopify-cli 2.7.3 → 2.10.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/.gitignore +1 -0
- data/CHANGELOG.md +44 -0
- data/Gemfile.lock +1 -1
- data/RELEASING.md +4 -3
- data/dev.yml +2 -2
- data/ext/javy/javy.rb +8 -9
- 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 -10
- data/lib/project_types/extension/commands/create.rb +2 -3
- 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 -0
- data/lib/project_types/extension/models/app.rb +1 -1
- data/lib/project_types/extension/models/development_server.rb +2 -4
- 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/gem.rb +1 -2
- data/lib/project_types/script/cli.rb +7 -0
- data/lib/project_types/script/commands/connect.rb +19 -0
- data/lib/project_types/script/commands/create.rb +8 -2
- data/lib/project_types/script/commands/push.rb +35 -12
- data/lib/project_types/script/layers/application/connect_app.rb +15 -3
- data/lib/project_types/script/layers/application/create_script.rb +16 -16
- data/lib/project_types/script/layers/application/extension_points.rb +50 -26
- data/lib/project_types/script/layers/application/push_script.rb +5 -2
- data/lib/project_types/script/layers/domain/errors.rb +3 -2
- data/lib/project_types/script/layers/domain/extension_point.rb +14 -0
- data/lib/project_types/script/layers/domain/script_config.rb +6 -4
- data/lib/project_types/script/layers/infrastructure/errors.rb +38 -23
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +49 -28
- data/lib/project_types/script/layers/infrastructure/script_service.rb +22 -5
- data/lib/project_types/script/loaders/project.rb +44 -0
- data/lib/project_types/script/loaders/specification_handler.rb +22 -0
- data/lib/project_types/script/messages/messages.rb +39 -16
- data/lib/project_types/script/ui/error_handler.rb +46 -29
- data/lib/project_types/theme/commands/pull.rb +45 -17
- data/lib/project_types/theme/commands/push.rb +65 -28
- data/lib/project_types/theme/commands/serve.rb +5 -0
- data/lib/project_types/theme/messages/messages.rb +34 -18
- data/lib/shopify_cli/command.rb +6 -0
- data/lib/shopify_cli/commands/login.rb +1 -1
- data/lib/shopify_cli/commands/switch.rb +1 -1
- data/lib/shopify_cli/constants.rb +11 -2
- data/lib/shopify_cli/context.rb +66 -12
- data/lib/shopify_cli/core/executor.rb +4 -4
- data/lib/shopify_cli/environment.rb +50 -20
- data/lib/shopify_cli/form.rb +2 -0
- data/lib/shopify_cli/identity_auth.rb +4 -3
- data/lib/shopify_cli/messages/messages.rb +9 -1
- data/lib/shopify_cli/method_object.rb +21 -9
- 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 +1 -0
- data/lib/shopify_cli/project.rb +8 -7
- data/lib/shopify_cli/resources/env_file.rb +18 -6
- data/lib/shopify_cli/result.rb +61 -59
- data/lib/shopify_cli/task.rb +5 -3
- data/lib/shopify_cli/theme/dev_server/cdn/cdn_helper.rb +49 -0
- data/lib/shopify_cli/theme/dev_server/cdn_assets.rb +69 -0
- data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +8 -28
- data/lib/shopify_cli/theme/dev_server/hot-reload.js +34 -3
- data/lib/shopify_cli/theme/dev_server/hot_reload.rb +18 -2
- data/lib/shopify_cli/theme/dev_server/local_assets.rb +4 -0
- data/lib/shopify_cli/theme/dev_server/proxy/template_param_builder.rb +84 -0
- data/lib/shopify_cli/theme/dev_server/proxy.rb +10 -15
- data/lib/shopify_cli/theme/dev_server/reload_mode.rb +34 -0
- data/lib/shopify_cli/theme/dev_server.rb +8 -21
- data/lib/shopify_cli/theme/file.rb +2 -2
- data/lib/shopify_cli/theme/filter/path_matcher.rb +38 -0
- data/lib/shopify_cli/theme/ignore_filter.rb +14 -18
- data/lib/shopify_cli/theme/include_filter.rb +43 -0
- data/lib/shopify_cli/theme/syncer.rb +17 -2
- data/lib/shopify_cli/theme/theme.rb +26 -4
- data/lib/shopify_cli/thread_pool/job.rb +27 -0
- data/lib/shopify_cli/thread_pool.rb +37 -0
- data/lib/shopify_cli/version.rb +1 -1
- data/lib/shopify_cli.rb +6 -1
- data/vendor/deps/cli-kit/lib/cli/kit/error_handler.rb +3 -1
- data/vendor/deps/ruby2_keywords/LICENSE +22 -0
- data/vendor/deps/ruby2_keywords/README.md +67 -0
- data/vendor/deps/ruby2_keywords/Rakefile +54 -0
- data/vendor/deps/ruby2_keywords/lib/ruby2_keywords.rb +57 -0
- data/vendor/deps/ruby2_keywords/ruby2_keywords.gemspec +18 -0
- data/vendor/deps/ruby2_keywords/test/test_keyword.rb +41 -0
- metadata +28 -4
- data/lib/graphql/all_orgs_with_extensions.graphql +0 -37
- data/lib/project_types/extension/tasks/run_extension_command.rb +0 -82
|
@@ -66,15 +66,27 @@ module ShopifyCLI
|
|
|
66
66
|
# initializer or to `call`. If the keyword argument matches the name of
|
|
67
67
|
# property, it is forwarded to the initializer, otherwise to call.
|
|
68
68
|
#
|
|
69
|
-
def call(*args,
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
69
|
+
ruby2_keywords def call(*args, &block)
|
|
70
|
+
# This is an extremely complicated case of delegation. The method wants
|
|
71
|
+
# to delegate arguments, but to have control over which keyword
|
|
72
|
+
# arguments are delegated. I'm not sure the forward and backward
|
|
73
|
+
# compatibility of this unusual form of delegation has really been
|
|
74
|
+
# explored or there's any good way to support it. So I have done
|
|
75
|
+
# done something hacky here and I'm looking at the last argument and
|
|
76
|
+
# modifying the package of arguments to be delegated in-place.
|
|
77
|
+
if args.last.is_a?(Hash)
|
|
78
|
+
kwargs = args.last
|
|
79
|
+
|
|
80
|
+
initializer_kwargs = kwargs.slice(*properties.keys)
|
|
81
|
+
instance = new(**initializer_kwargs)
|
|
82
|
+
|
|
83
|
+
kwargs.reject! { |key| initializer_kwargs.key?(key) }
|
|
84
|
+
args.pop if kwargs.empty?
|
|
85
|
+
instance.call(*args, &block)
|
|
86
|
+
else
|
|
87
|
+
# Since the former is so complicated - let's have a fast path that
|
|
88
|
+
# is much simpler.
|
|
89
|
+
new.call(*args, &block)
|
|
78
90
|
end
|
|
79
91
|
end
|
|
80
92
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shopify_cli/thread_pool/job"
|
|
4
|
+
|
|
5
|
+
module ShopifyCLI
|
|
6
|
+
class PartnersAPI
|
|
7
|
+
class AppExtensions
|
|
8
|
+
class Job < ShopifyCLI::ThreadPool::Job
|
|
9
|
+
attr_reader :result
|
|
10
|
+
|
|
11
|
+
def initialize(ctx, app, type)
|
|
12
|
+
super()
|
|
13
|
+
@ctx = ctx
|
|
14
|
+
@app = app
|
|
15
|
+
@api_key = @app["apiKey"]
|
|
16
|
+
@type = type
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def perform!
|
|
20
|
+
resp = PartnersAPI.query(@ctx, "get_extension_registrations", **params)
|
|
21
|
+
@result = resp&.dig("data", "app") || {}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def patch_app_with_extensions!
|
|
25
|
+
@app.merge!(result)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def params
|
|
31
|
+
{ api_key: @api_key, type: @type }
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "shopify_cli/thread_pool"
|
|
4
|
+
|
|
5
|
+
require_relative "app_extensions/job"
|
|
6
|
+
|
|
7
|
+
module ShopifyCLI
|
|
8
|
+
class PartnersAPI
|
|
9
|
+
class AppExtensions
|
|
10
|
+
class << self
|
|
11
|
+
def fetch_apps_extensions(ctx, orgs, type)
|
|
12
|
+
jobs = apps(orgs).map { |app| AppExtensions::Job.new(ctx, app, type) }
|
|
13
|
+
|
|
14
|
+
consume_jobs!(jobs)
|
|
15
|
+
patch_apps_with_extensions!(jobs)
|
|
16
|
+
|
|
17
|
+
orgs
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def apps(orgs)
|
|
23
|
+
orgs.flat_map { |org| org["apps"] }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def consume_jobs!(jobs)
|
|
27
|
+
thread_pool = ShopifyCLI::ThreadPool.new
|
|
28
|
+
jobs.each do |job|
|
|
29
|
+
thread_pool.schedule(job)
|
|
30
|
+
end
|
|
31
|
+
thread_pool.shutdown
|
|
32
|
+
|
|
33
|
+
raise_if_any_error(jobs)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def patch_apps_with_extensions!(jobs)
|
|
37
|
+
jobs.each(&:patch_app_with_extensions!)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def raise_if_any_error(jobs)
|
|
41
|
+
jobs.find(&:error?).tap { |job| raise job.error if job }
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -28,11 +28,8 @@ module ShopifyCLI
|
|
|
28
28
|
end
|
|
29
29
|
|
|
30
30
|
def fetch_with_extensions(ctx, type)
|
|
31
|
-
|
|
32
|
-
(
|
|
33
|
-
org["apps"] = (org.dig("apps", "nodes") || [])
|
|
34
|
-
org
|
|
35
|
-
end
|
|
31
|
+
orgs = fetch_with_app(ctx)
|
|
32
|
+
AppExtensions.fetch_apps_extensions(ctx, orgs, type)
|
|
36
33
|
end
|
|
37
34
|
end
|
|
38
35
|
end
|
data/lib/shopify_cli/project.rb
CHANGED
|
@@ -107,13 +107,6 @@ module ShopifyCLI
|
|
|
107
107
|
@dir = nil
|
|
108
108
|
end
|
|
109
109
|
|
|
110
|
-
private
|
|
111
|
-
|
|
112
|
-
def directory(dir)
|
|
113
|
-
@dir ||= Hash.new { |h, k| h[k] = __directory(k) }
|
|
114
|
-
@dir[dir]
|
|
115
|
-
end
|
|
116
|
-
|
|
117
110
|
def at(dir)
|
|
118
111
|
proj_dir = directory(dir)
|
|
119
112
|
unless proj_dir
|
|
@@ -123,6 +116,13 @@ module ShopifyCLI
|
|
|
123
116
|
@at[proj_dir]
|
|
124
117
|
end
|
|
125
118
|
|
|
119
|
+
private
|
|
120
|
+
|
|
121
|
+
def directory(dir)
|
|
122
|
+
@dir ||= Hash.new { |h, k| h[k] = __directory(k) }
|
|
123
|
+
@dir[dir]
|
|
124
|
+
end
|
|
125
|
+
|
|
126
126
|
def __directory(curr)
|
|
127
127
|
loop do
|
|
128
128
|
return nil if curr == "/" || /^[A-Z]:\/$/.match?(curr)
|
|
@@ -134,6 +134,7 @@ module ShopifyCLI
|
|
|
134
134
|
end
|
|
135
135
|
|
|
136
136
|
property :directory # :nodoc:
|
|
137
|
+
property :env # :nodoc:
|
|
137
138
|
|
|
138
139
|
##
|
|
139
140
|
# will read, parse and return the envfile for the project
|
|
@@ -14,13 +14,21 @@ module ShopifyCLI
|
|
|
14
14
|
}
|
|
15
15
|
|
|
16
16
|
class << self
|
|
17
|
-
def
|
|
18
|
-
|
|
17
|
+
def path(directory)
|
|
18
|
+
File.join(directory, FILENAME)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def read(_directory = Dir.pwd, overrides: {})
|
|
22
|
+
input = parse_external_env(overrides: overrides)
|
|
19
23
|
new(input)
|
|
20
24
|
end
|
|
21
25
|
|
|
26
|
+
def from_hash(hash)
|
|
27
|
+
new(env_input(hash))
|
|
28
|
+
end
|
|
29
|
+
|
|
22
30
|
def parse(directory)
|
|
23
|
-
File.read(
|
|
31
|
+
File.read(path(directory))
|
|
24
32
|
.gsub("\r\n", "\n").split("\n").each_with_object({}) do |line, output|
|
|
25
33
|
match = /\A([A-Za-z_0-9]+)\s*=\s*(.*)\z/.match(line)
|
|
26
34
|
if match
|
|
@@ -37,10 +45,14 @@ module ShopifyCLI
|
|
|
37
45
|
end
|
|
38
46
|
end
|
|
39
47
|
|
|
40
|
-
def parse_external_env(directory = Dir.pwd)
|
|
48
|
+
def parse_external_env(directory = Dir.pwd, overrides: {})
|
|
49
|
+
env_input(parse(directory), overrides: overrides)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def env_input(parsed_source, overrides: {})
|
|
41
53
|
env_details = {}
|
|
42
54
|
extra = {}
|
|
43
|
-
|
|
55
|
+
parsed_source.merge(overrides).each do |key, value|
|
|
44
56
|
if KEY_MAP[key]
|
|
45
57
|
env_details[KEY_MAP[key]] = value
|
|
46
58
|
else
|
|
@@ -53,7 +65,7 @@ module ShopifyCLI
|
|
|
53
65
|
end
|
|
54
66
|
|
|
55
67
|
property :api_key, required: true
|
|
56
|
-
property :secret
|
|
68
|
+
property :secret
|
|
57
69
|
property :shop
|
|
58
70
|
property :scopes
|
|
59
71
|
property :host
|
data/lib/shopify_cli/result.rb
CHANGED
|
@@ -363,70 +363,72 @@ module ShopifyCLI
|
|
|
363
363
|
Result::Failure.new(error)
|
|
364
364
|
end
|
|
365
365
|
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
366
|
+
class << self
|
|
367
|
+
##
|
|
368
|
+
# takes either a value or a block and chooses the appropriate result
|
|
369
|
+
# container based on the type of the value or the type of the block's return
|
|
370
|
+
# value. If the type is an exception, it is wrapped in a
|
|
371
|
+
# `ShopifyCli::Result::Failure` and otherwise in a
|
|
372
|
+
# `ShopifyCli::Result::Success`. If a block was provided instead of value, a
|
|
373
|
+
# `Proc` is returned and the result wrapping doesn't occur until the block
|
|
374
|
+
# is invoked.
|
|
375
|
+
#
|
|
376
|
+
# #### Parameters
|
|
377
|
+
#
|
|
378
|
+
# * `*args` should be an `Array` with zero or one element
|
|
379
|
+
# * `&block` should be a `Proc` that takes zero or one argument
|
|
380
|
+
#
|
|
381
|
+
# #### Returns
|
|
382
|
+
#
|
|
383
|
+
# Returns either a `Result::Success`, `Result::Failure` or a `Proc` that
|
|
384
|
+
# produces one of the former when invoked.
|
|
385
|
+
#
|
|
386
|
+
# #### Examples
|
|
387
|
+
#
|
|
388
|
+
# Result.wrap(1) # => ShopifyCli::Result::Success
|
|
389
|
+
# Result.wrap(RuntimeError.new) # => ShopifyCli::Result::Failure
|
|
390
|
+
#
|
|
391
|
+
# Result.wrap { 1 } # => Proc
|
|
392
|
+
# Result.wrap { 1 }.call # => ShopifyCli::Result::Success
|
|
393
|
+
# Result.wrap { raise }.call # => ShopifyCli::Result::Failure
|
|
394
|
+
#
|
|
395
|
+
# Result.wrap { |s| s.upcase }.call("hello").tap do |result|
|
|
396
|
+
# result # => Result::Success
|
|
397
|
+
# result.value # => "HELLO"
|
|
398
|
+
# end
|
|
399
|
+
#
|
|
400
|
+
ruby2_keywords def wrap(*values, &block)
|
|
401
|
+
raise ArgumentError, "expected either a value or a block" unless (values.length == 1) ^ block
|
|
401
402
|
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
end
|
|
413
|
-
else
|
|
414
|
-
->(*args) do
|
|
415
|
-
begin
|
|
416
|
-
wrap(block.call(*args))
|
|
417
|
-
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
418
|
-
wrap(error)
|
|
403
|
+
if values.length == 1
|
|
404
|
+
values.pop.yield_self do |value|
|
|
405
|
+
case value
|
|
406
|
+
when Result::Success, Result::Failure
|
|
407
|
+
value
|
|
408
|
+
when NilClass, Exception
|
|
409
|
+
Result.failure(value)
|
|
410
|
+
else
|
|
411
|
+
Result.success(value)
|
|
412
|
+
end
|
|
419
413
|
end
|
|
414
|
+
else
|
|
415
|
+
->(*args) do
|
|
416
|
+
begin
|
|
417
|
+
wrap(block.call(*args))
|
|
418
|
+
rescue Exception => error # rubocop:disable Lint/RescueException
|
|
419
|
+
wrap(error)
|
|
420
|
+
end
|
|
421
|
+
end.ruby2_keywords
|
|
420
422
|
end
|
|
421
423
|
end
|
|
422
|
-
end
|
|
423
424
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
425
|
+
##
|
|
426
|
+
# Wraps the given block and invokes it with the passed arguments.
|
|
427
|
+
#
|
|
428
|
+
ruby2_keywords def call(*args, &block)
|
|
429
|
+
raise ArgumentError, "expected a block" unless block
|
|
430
|
+
wrap(&block).call(*args)
|
|
431
|
+
end
|
|
430
432
|
end
|
|
431
433
|
end
|
|
432
434
|
end
|
data/lib/shopify_cli/task.rb
CHANGED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShopifyCLI
|
|
4
|
+
module Theme
|
|
5
|
+
module DevServer
|
|
6
|
+
module Cdn
|
|
7
|
+
module CdnHelper
|
|
8
|
+
def proxy_request(env, uri, theme)
|
|
9
|
+
response = Net::HTTP.start(uri.host, 443, use_ssl: true) do |http|
|
|
10
|
+
req_class = Net::HTTP.const_get(method(env))
|
|
11
|
+
|
|
12
|
+
req = req_class.new(uri)
|
|
13
|
+
req.initialize_http_header(req_headers(theme))
|
|
14
|
+
req.body_stream = req_body(env)
|
|
15
|
+
|
|
16
|
+
http.request(req)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
[
|
|
20
|
+
response.code.to_s,
|
|
21
|
+
{
|
|
22
|
+
"Content-Type" => response.content_type,
|
|
23
|
+
"Content-Length" => response.content_length.to_s,
|
|
24
|
+
},
|
|
25
|
+
[response.body],
|
|
26
|
+
]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def method(env)
|
|
32
|
+
env["REQUEST_METHOD"].capitalize
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def req_body(env)
|
|
36
|
+
env["rack.input"]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def req_headers(theme)
|
|
40
|
+
{
|
|
41
|
+
"Referer" => "https://#{theme.shop}",
|
|
42
|
+
"Transfer-Encoding" => "chunked",
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "cdn/cdn_helper"
|
|
4
|
+
require_relative "local_assets"
|
|
5
|
+
|
|
6
|
+
module ShopifyCLI
|
|
7
|
+
module Theme
|
|
8
|
+
module DevServer
|
|
9
|
+
class CdnAssets
|
|
10
|
+
include Cdn::CdnHelper
|
|
11
|
+
|
|
12
|
+
ASSETS_PROXY_PATH = "/cdn_asset"
|
|
13
|
+
ASSETS_CDN = "//cdn.shopify.com"
|
|
14
|
+
ASSETS_CDN_REGEX = %r{(https?:)?#{ASSETS_CDN}}
|
|
15
|
+
ASSETS_SOURCE_MAP_REGEX = /\/[\/|\*]# sourceMappingURL\=(\/.*)/
|
|
16
|
+
|
|
17
|
+
def initialize(app, theme:)
|
|
18
|
+
@app = app
|
|
19
|
+
@theme = theme
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(env)
|
|
23
|
+
path = env["PATH_INFO"]
|
|
24
|
+
|
|
25
|
+
# Serve assets from CDN
|
|
26
|
+
return serve_asset(env, path) if path.start_with?(ASSETS_PROXY_PATH)
|
|
27
|
+
|
|
28
|
+
# Proxy the request, and replace the URLs in the response
|
|
29
|
+
status, headers, body = @app.call(env)
|
|
30
|
+
body = replace_asset_urls(body)
|
|
31
|
+
[status, headers, body]
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def serve_asset(env, path)
|
|
37
|
+
path = path.gsub(%r{^#{ASSETS_PROXY_PATH}}, "")
|
|
38
|
+
query = env["QUERY_STRING"]
|
|
39
|
+
uri = asset_cdn_uri(path, query)
|
|
40
|
+
|
|
41
|
+
status, headers, body = proxy_request(env, uri, @theme)
|
|
42
|
+
|
|
43
|
+
[status, headers, replace_source_map_url(body)]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def asset_cdn_uri(path, query)
|
|
47
|
+
uri = URI.join("https:#{ASSETS_CDN}", path)
|
|
48
|
+
uri.query = query.split("&").last
|
|
49
|
+
uri
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def replace_asset_urls(body)
|
|
53
|
+
[body.join.gsub(ASSETS_CDN_REGEX, ASSETS_PROXY_PATH)]
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def replace_source_map_url(body)
|
|
57
|
+
body_content = body.join
|
|
58
|
+
map_regex_match = body_content.match(ASSETS_SOURCE_MAP_REGEX)
|
|
59
|
+
return body if map_regex_match.nil?
|
|
60
|
+
|
|
61
|
+
map_url = map_regex_match[1]
|
|
62
|
+
return body if map_url.nil?
|
|
63
|
+
|
|
64
|
+
[body_content.gsub(map_url, "#{ASSETS_PROXY_PATH}#{map_url}")]
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "cdn/cdn_helper"
|
|
4
|
+
|
|
3
5
|
module ShopifyCLI
|
|
4
6
|
module Theme
|
|
5
7
|
module DevServer
|
|
6
8
|
class CdnFonts
|
|
9
|
+
include Cdn::CdnHelper
|
|
10
|
+
|
|
7
11
|
FONTS_PATH = "/fonts"
|
|
8
12
|
FONTS_CDN = "https://fonts.shopifycdn.com"
|
|
9
13
|
FONTS_REGEX = %r{#{FONTS_CDN}}
|
|
@@ -17,7 +21,7 @@ module ShopifyCLI
|
|
|
17
21
|
path = env["PATH_INFO"]
|
|
18
22
|
|
|
19
23
|
# Serve from fonts CDN
|
|
20
|
-
return serve_font(env) if path.start_with?(FONTS_PATH)
|
|
24
|
+
return serve_font(env, path) if path.start_with?(FONTS_PATH)
|
|
21
25
|
|
|
22
26
|
# Proxy the request, and replace the URLs in the response
|
|
23
27
|
status, headers, body = @app.call(env)
|
|
@@ -27,35 +31,11 @@ module ShopifyCLI
|
|
|
27
31
|
|
|
28
32
|
private
|
|
29
33
|
|
|
30
|
-
def serve_font(env)
|
|
31
|
-
|
|
32
|
-
path, query, method, body_stream = *env.slice(*parameters).values
|
|
33
|
-
|
|
34
|
+
def serve_font(env, path)
|
|
35
|
+
query = env["QUERY_STRING"]
|
|
34
36
|
uri = fonts_cdn_uri(path, query)
|
|
35
37
|
|
|
36
|
-
|
|
37
|
-
req_class = Net::HTTP.const_get(method.capitalize)
|
|
38
|
-
req = req_class.new(uri)
|
|
39
|
-
req.initialize_http_header(fonts_cdn_headers)
|
|
40
|
-
req.body_stream = body_stream
|
|
41
|
-
http.request(req)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
[
|
|
45
|
-
response.code.to_s,
|
|
46
|
-
{
|
|
47
|
-
"Content-Type" => response.content_type,
|
|
48
|
-
"Content-Length" => response.content_length.to_s,
|
|
49
|
-
},
|
|
50
|
-
[response.body],
|
|
51
|
-
]
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def fonts_cdn_headers
|
|
55
|
-
{
|
|
56
|
-
"Referer" => "https://#{@theme.shop}",
|
|
57
|
-
"Transfer-Encoding" => "chunked",
|
|
58
|
-
}
|
|
38
|
+
proxy_request(env, uri, @theme)
|
|
59
39
|
end
|
|
60
40
|
|
|
61
41
|
def fonts_cdn_uri(path, query)
|
|
@@ -15,9 +15,23 @@
|
|
|
15
15
|
eventSource.onerror = () => eventSource.close();
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
function reloadMode() {
|
|
19
|
+
var namespace = window.__SHOPIFY_CLI_ENV__;
|
|
20
|
+
return namespace.mode;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isFullPageReloadMode(){
|
|
24
|
+
return reloadMode() === "full-page";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isReloadModeActive(){
|
|
28
|
+
return reloadMode() !== "off";
|
|
29
|
+
}
|
|
19
30
|
|
|
20
31
|
function isRefreshRequired(files) {
|
|
32
|
+
if (isFullPageReloadMode()) {
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
21
35
|
return files.some((file) => !isCssFile(file) && !isSectionFile(file));
|
|
22
36
|
}
|
|
23
37
|
|
|
@@ -33,7 +47,20 @@
|
|
|
33
47
|
}
|
|
34
48
|
}
|
|
35
49
|
|
|
36
|
-
function
|
|
50
|
+
function setHotReloadCookie(files) {
|
|
51
|
+
var date = new Date();
|
|
52
|
+
|
|
53
|
+
// Hot reload cookie expires in 3 seconds
|
|
54
|
+
date.setSeconds(date.getSeconds() + 3);
|
|
55
|
+
|
|
56
|
+
var sections = files.join(',');
|
|
57
|
+
var expires = date.toUTCString();
|
|
58
|
+
|
|
59
|
+
document.cookie = `hot_reload_sections=${sections}; expires=${expires}; path=/`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function refreshPage(files) {
|
|
63
|
+
setHotReloadCookie(files);
|
|
37
64
|
console.log('[HotReload] Refreshing entire page');
|
|
38
65
|
window.location.reload();
|
|
39
66
|
}
|
|
@@ -43,7 +70,7 @@
|
|
|
43
70
|
var modifiedFiles = data.modified;
|
|
44
71
|
|
|
45
72
|
if (isRefreshRequired(modifiedFiles)) {
|
|
46
|
-
refreshPage();
|
|
73
|
+
refreshPage(modifiedFiles);
|
|
47
74
|
} else {
|
|
48
75
|
modifiedFiles.forEach(refreshFile);
|
|
49
76
|
}
|
|
@@ -106,4 +133,8 @@
|
|
|
106
133
|
}
|
|
107
134
|
}
|
|
108
135
|
}
|
|
136
|
+
|
|
137
|
+
if (isReloadModeActive()) {
|
|
138
|
+
connect();
|
|
139
|
+
}
|
|
109
140
|
})();
|
|
@@ -4,10 +4,11 @@ module ShopifyCLI
|
|
|
4
4
|
module Theme
|
|
5
5
|
module DevServer
|
|
6
6
|
class HotReload
|
|
7
|
-
def initialize(ctx, app, theme:, watcher:, ignore_filter: nil)
|
|
7
|
+
def initialize(ctx, app, theme:, watcher:, mode:, ignore_filter: nil)
|
|
8
8
|
@ctx = ctx
|
|
9
9
|
@app = app
|
|
10
10
|
@theme = theme
|
|
11
|
+
@mode = mode
|
|
11
12
|
@streams = SSE::Streams.new
|
|
12
13
|
@watcher = watcher
|
|
13
14
|
@watcher.add_observer(self, :notify_streams_of_file_change)
|
|
@@ -48,12 +49,27 @@ module ShopifyCLI
|
|
|
48
49
|
|
|
49
50
|
def inject_hot_reload_javascript(body)
|
|
50
51
|
hot_reload_js = ::File.read("#{__dir__}/hot-reload.js")
|
|
51
|
-
hot_reload_script =
|
|
52
|
+
hot_reload_script = [
|
|
53
|
+
"<script>",
|
|
54
|
+
params_js,
|
|
55
|
+
hot_reload_js,
|
|
56
|
+
"</script>",
|
|
57
|
+
].join("\n")
|
|
58
|
+
|
|
52
59
|
body = body.join.gsub("</body>", "#{hot_reload_script}\n</body>")
|
|
53
60
|
|
|
54
61
|
[body]
|
|
55
62
|
end
|
|
56
63
|
|
|
64
|
+
def params_js
|
|
65
|
+
env = { mode: @mode }
|
|
66
|
+
<<~JS
|
|
67
|
+
(() => {
|
|
68
|
+
window.__SHOPIFY_CLI_ENV__ = #{env.to_json};
|
|
69
|
+
})();
|
|
70
|
+
JS
|
|
71
|
+
end
|
|
72
|
+
|
|
57
73
|
def create_stream
|
|
58
74
|
stream = @streams.new
|
|
59
75
|
|