@shopify/cli-kit 3.48.6 → 3.49.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.
- package/README.md +3 -1
- package/assets/cli-ruby/lib/project_types/theme/commands/console.rb +9 -3
- package/assets/cli-ruby/lib/shopify_cli/theme/dev_server/proxy_param_builder.rb +2 -0
- package/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb +1 -1
- package/assets/cli-ruby/lib/shopify_cli/theme/repl/api.rb +7 -7
- package/assets/cli-ruby/lib/shopify_cli/theme/repl/auth_dev_server.rb +18 -3
- package/assets/cli-ruby/lib/shopify_cli/theme/repl/auth_middleware.rb +27 -6
- package/assets/cli-ruby/lib/shopify_cli/theme/repl/remote_evaluator.rb +158 -0
- package/assets/cli-ruby/lib/shopify_cli/theme/repl/resources/success.html +0 -1
- package/assets/cli-ruby/lib/shopify_cli/theme/repl/snippet.rb +81 -0
- package/assets/cli-ruby/lib/shopify_cli/theme/repl.rb +31 -89
- package/dist/private/node/analytics.js +2 -2
- package/dist/private/node/analytics.js.map +1 -1
- package/dist/private/node/api/graphql.d.ts +2 -2
- package/dist/private/node/api/graphql.js +12 -6
- package/dist/private/node/api/graphql.js.map +1 -1
- package/dist/private/node/api.d.ts +1 -1
- package/dist/private/node/api.js +1 -1
- package/dist/private/node/api.js.map +1 -1
- package/dist/private/node/context/utilities.d.ts +2 -0
- package/dist/private/node/context/utilities.js +3 -2
- package/dist/private/node/context/utilities.js.map +1 -1
- package/dist/private/node/demo-recorder.d.ts +0 -3
- package/dist/private/node/demo-recorder.js +3 -5
- package/dist/private/node/demo-recorder.js.map +1 -1
- package/dist/private/node/session/exchange.js +1 -0
- package/dist/private/node/session/exchange.js.map +1 -1
- package/dist/private/node/testing/ui.d.ts +6 -1
- package/dist/private/node/testing/ui.js +25 -1
- package/dist/private/node/testing/ui.js.map +1 -1
- package/dist/private/node/ui/components/AutocompletePrompt.test.js +2 -0
- package/dist/private/node/ui/components/AutocompletePrompt.test.js.map +1 -1
- package/dist/private/node/ui/components/Command.js +1 -1
- package/dist/private/node/ui/components/Command.js.map +1 -1
- package/dist/private/node/ui/components/Command.test.js +1 -1
- package/dist/private/node/ui/components/Command.test.js.map +1 -1
- package/dist/private/node/ui/components/ConcurrentOutput.d.ts +2 -23
- package/dist/private/node/ui/components/ConcurrentOutput.js +39 -103
- package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
- package/dist/private/node/ui/components/ConcurrentOutput.test.js +22 -208
- package/dist/private/node/ui/components/ConcurrentOutput.test.js.map +1 -1
- package/dist/private/node/ui/components/SelectPrompt.test.js +2 -0
- package/dist/private/node/ui/components/SelectPrompt.test.js.map +1 -1
- package/dist/private/node/ui/components/Tasks.test.js +2 -0
- package/dist/private/node/ui/components/Tasks.test.js.map +1 -1
- package/dist/private/node/ui/components/TextPrompt.test.js +2 -0
- package/dist/private/node/ui/components/TextPrompt.test.js.map +1 -1
- package/dist/private/node/ui/hooks/use-abort-signal.d.ts +1 -1
- package/dist/private/node/ui/hooks/use-abort-signal.js +8 -3
- package/dist/private/node/ui/hooks/use-abort-signal.js.map +1 -1
- package/dist/private/node/ui/utilities.d.ts +1 -1
- package/dist/private/node/ui.js +1 -1
- package/dist/private/node/ui.js.map +1 -1
- package/dist/public/common/version.d.ts +1 -1
- package/dist/public/common/version.js +1 -1
- package/dist/public/common/version.js.map +1 -1
- package/dist/public/node/base-command.d.ts +2 -4
- package/dist/public/node/base-command.js +18 -4
- package/dist/public/node/base-command.js.map +1 -1
- package/dist/public/node/dot-env.d.ts +0 -7
- package/dist/public/node/dot-env.js +1 -9
- package/dist/public/node/dot-env.js.map +1 -1
- package/dist/public/node/node-package-manager.d.ts +16 -5
- package/dist/public/node/node-package-manager.js +28 -13
- package/dist/public/node/node-package-manager.js.map +1 -1
- package/dist/public/node/output.d.ts +1 -1
- package/dist/public/node/output.js +4 -0
- package/dist/public/node/output.js.map +1 -1
- package/dist/public/node/system.js +3 -6
- package/dist/public/node/system.js.map +1 -1
- package/dist/public/node/testing/ui.d.ts +1 -0
- package/dist/public/node/testing/ui.js +2 -0
- package/dist/public/node/testing/ui.js.map +1 -0
- package/dist/public/node/tree-kill.d.ts +7 -2
- package/dist/public/node/tree-kill.js +165 -4
- package/dist/public/node/tree-kill.js.map +1 -1
- package/dist/public/node/ui/components.d.ts +1 -0
- package/dist/public/node/ui/components.js +2 -0
- package/dist/public/node/ui/components.js.map +1 -0
- package/dist/public/node/ui/hooks.d.ts +1 -0
- package/dist/public/node/ui/hooks.js +2 -0
- package/dist/public/node/ui/hooks.js.map +1 -0
- package/dist/public/node/ui.d.ts +3 -10
- package/dist/public/node/ui.js +5 -14
- package/dist/public/node/ui.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +11 -15
package/README.md
CHANGED
|
@@ -9,6 +9,8 @@ With the Shopify command line interface (Shopify CLI 3.0), you can:
|
|
|
9
9
|
- initialize, build, dev, and deploy Shopify apps, extensions, functions and themes
|
|
10
10
|
- build custom storefronts and manage their hosting
|
|
11
11
|
|
|
12
|
+
Learn more in the [commands docs](./packages/cli/README.md#commands).
|
|
13
|
+
|
|
12
14
|
<p> </p>
|
|
13
15
|
|
|
14
16
|
### Before you begin ###
|
|
@@ -32,7 +34,7 @@ Learn more in the docs: [Create an app](https://shopify.dev/apps/getting-started
|
|
|
32
34
|
|
|
33
35
|
## Developing themes with Shopify CLI
|
|
34
36
|
|
|
35
|
-
To work with themes, the CLI needs to be installed globally with:
|
|
37
|
+
To work with themes, the CLI needs to be installed globally with:
|
|
36
38
|
|
|
37
39
|
- `npm install -g @shopify/cli @shopify/theme`
|
|
38
40
|
|
|
@@ -5,10 +5,16 @@ require "shopify_cli/theme/repl"
|
|
|
5
5
|
module Theme
|
|
6
6
|
class Command
|
|
7
7
|
class Console < ShopifyCLI::Command::SubCommand
|
|
8
|
-
|
|
8
|
+
options do |parser, flags|
|
|
9
|
+
parser.on("--url=URL") { |url| flags[:url] = url }
|
|
10
|
+
parser.on("--port=PORT") { |port| flags[:port] = port }
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(_args, _name)
|
|
14
|
+
url = options.flags[:url]
|
|
15
|
+
port = options.flags[:port]
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
ShopifyCLI::Theme::Repl.new(@ctx).run
|
|
17
|
+
ShopifyCLI::Theme::Repl.new(@ctx, url, port).run
|
|
12
18
|
end
|
|
13
19
|
end
|
|
14
20
|
end
|
|
@@ -156,7 +156,7 @@ module ShopifyCLI
|
|
|
156
156
|
|
|
157
157
|
def preview_message
|
|
158
158
|
if Shopifolk.acting_as_shopify_organization?
|
|
159
|
-
parsed_uri = URI.parse(extension.
|
|
159
|
+
parsed_uri = URI.parse(extension.location)
|
|
160
160
|
shopify_org_url = "#{parsed_uri.scheme}://#{parsed_uri.host}/9082/impersonate"
|
|
161
161
|
if ShopifyCLI::Environment.unified_deployment?
|
|
162
162
|
ctx.message("serve.preview_message_1p_unified", shopify_org_url, theme.editor_url, address)
|
|
@@ -4,10 +4,11 @@ module ShopifyCLI
|
|
|
4
4
|
module Theme
|
|
5
5
|
class Repl
|
|
6
6
|
class Api
|
|
7
|
-
attr_reader :ctx, :repl
|
|
7
|
+
attr_reader :ctx, :url, :repl
|
|
8
8
|
|
|
9
|
-
def initialize(ctx, repl)
|
|
9
|
+
def initialize(ctx, url, repl)
|
|
10
10
|
@ctx = ctx
|
|
11
|
+
@url = url.start_with?("/") ? url : url.prepend("/")
|
|
11
12
|
@repl = repl
|
|
12
13
|
end
|
|
13
14
|
|
|
@@ -47,10 +48,9 @@ module ShopifyCLI
|
|
|
47
48
|
end
|
|
48
49
|
|
|
49
50
|
def form_data(liquid_snippet)
|
|
50
|
-
template = ["", "", liquid_snippet, "", liquid_template].join("\n")
|
|
51
|
-
|
|
52
51
|
{
|
|
53
|
-
"replace_templates[
|
|
52
|
+
"replace_templates[snippets/eval.liquid]" => "\n#{liquid_snippet}\n",
|
|
53
|
+
"replace_templates[sections/announcement-bar.liquid]" => "\n{% render 'eval' %}#{liquid_template}",
|
|
54
54
|
:_method => "GET",
|
|
55
55
|
}
|
|
56
56
|
end
|
|
@@ -92,9 +92,9 @@ module ShopifyCLI
|
|
|
92
92
|
return @api_uri if @api_uri
|
|
93
93
|
|
|
94
94
|
uri_address = if Environment.theme_access_password?
|
|
95
|
-
"https://#{ThemeAccessAPI::BASE_URL}/cli/sfr"
|
|
95
|
+
"https://#{ThemeAccessAPI::BASE_URL}/cli/sfr#{url}"
|
|
96
96
|
else
|
|
97
|
-
"https://#{shop}"
|
|
97
|
+
"https://#{shop}#{url}"
|
|
98
98
|
end
|
|
99
99
|
|
|
100
100
|
@api_uri = URI(uri_address)
|
|
@@ -38,11 +38,21 @@ module ShopifyCLI
|
|
|
38
38
|
method: "PUT",
|
|
39
39
|
body: JSON.generate({
|
|
40
40
|
assets: [
|
|
41
|
-
{ key: "sections/announcement-bar.liquid", value: "" },
|
|
42
|
-
{ key: "config/settings_schema.json", value: "[]" },
|
|
43
41
|
{ key: "config/settings_data.json", value: "{}" },
|
|
44
|
-
{ key: "
|
|
42
|
+
{ key: "config/settings_schema.json", value: "[]" },
|
|
43
|
+
{ key: "snippets/eval.liquid", value: "" },
|
|
45
44
|
{ key: "layout/password.liquid", value: "{{ content_for_header }}{{ content_for_layout }}" },
|
|
45
|
+
{ key: "layout/theme.liquid", value: "{{ content_for_header }}{{ content_for_layout }}" },
|
|
46
|
+
{ key: "sections/announcement-bar.liquid", value: "" },
|
|
47
|
+
{
|
|
48
|
+
key: "templates/index.json",
|
|
49
|
+
value: {
|
|
50
|
+
sections: {
|
|
51
|
+
a: { type: "announcement-bar", settings: {} },
|
|
52
|
+
},
|
|
53
|
+
order: ["a"],
|
|
54
|
+
}.to_json,
|
|
55
|
+
},
|
|
46
56
|
],
|
|
47
57
|
}),
|
|
48
58
|
)
|
|
@@ -69,6 +79,11 @@ module ShopifyCLI
|
|
|
69
79
|
@proxy ||= Proxy.new(ctx, theme, param_builder)
|
|
70
80
|
end
|
|
71
81
|
|
|
82
|
+
def start_server
|
|
83
|
+
ctx.open_browser_url!(address)
|
|
84
|
+
super
|
|
85
|
+
end
|
|
86
|
+
|
|
72
87
|
def frame_title; end
|
|
73
88
|
def preview_message; end
|
|
74
89
|
def setup_server; end
|
|
@@ -4,6 +4,8 @@ module ShopifyCLI
|
|
|
4
4
|
module Theme
|
|
5
5
|
class Repl
|
|
6
6
|
class AuthMiddleware
|
|
7
|
+
PASSWORD_PAGE_PATH = "/password"
|
|
8
|
+
|
|
7
9
|
def initialize(app, proxy, repl, &stop_dev_server)
|
|
8
10
|
@app = app
|
|
9
11
|
@proxy = proxy
|
|
@@ -13,11 +15,16 @@ module ShopifyCLI
|
|
|
13
15
|
|
|
14
16
|
def call(env)
|
|
15
17
|
@env = env
|
|
18
|
+
@env["PATH_INFO"] = PASSWORD_PAGE_PATH if redirect_to_password?(@env)
|
|
16
19
|
|
|
17
|
-
return @app.call(env)
|
|
20
|
+
return @app.call(@env) if password_page?(@env)
|
|
18
21
|
|
|
19
22
|
authenticate!
|
|
20
|
-
|
|
23
|
+
|
|
24
|
+
# The authentication server only shuts down when the root page is
|
|
25
|
+
# loaded, preventing favicons or other assets on the /password page
|
|
26
|
+
# from shutting down the server.
|
|
27
|
+
shutdown if index_page?(@env)
|
|
21
28
|
|
|
22
29
|
[
|
|
23
30
|
200,
|
|
@@ -35,6 +42,14 @@ module ShopifyCLI
|
|
|
35
42
|
|
|
36
43
|
private
|
|
37
44
|
|
|
45
|
+
def redirect_to_password?(env)
|
|
46
|
+
return false if defined?(@redirect_to_password)
|
|
47
|
+
|
|
48
|
+
code, _body, _resp = @proxy.call({ **env, "PATH_INFO" => "/" })
|
|
49
|
+
|
|
50
|
+
@redirect_to_password = code == "302"
|
|
51
|
+
end
|
|
52
|
+
|
|
38
53
|
def storefront_session
|
|
39
54
|
cookie["storefront_digest"]&.first
|
|
40
55
|
end
|
|
@@ -43,14 +58,18 @@ module ShopifyCLI
|
|
|
43
58
|
@proxy.secure_session_id
|
|
44
59
|
end
|
|
45
60
|
|
|
46
|
-
def authenticated?
|
|
47
|
-
storefront_session && secure_session
|
|
48
|
-
end
|
|
49
|
-
|
|
50
61
|
def authenticate!
|
|
51
62
|
@repl.authenticate(storefront_session, secure_session)
|
|
52
63
|
end
|
|
53
64
|
|
|
65
|
+
def password_page?(env)
|
|
66
|
+
env["PATH_INFO"]&.start_with?(PASSWORD_PAGE_PATH)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def index_page?(env)
|
|
70
|
+
env["PATH_INFO"] == "/"
|
|
71
|
+
end
|
|
72
|
+
|
|
54
73
|
def shutdown
|
|
55
74
|
Thread.new do
|
|
56
75
|
# Web server answers the request and shutdown itself
|
|
@@ -66,6 +85,8 @@ module ShopifyCLI
|
|
|
66
85
|
|
|
67
86
|
def cookie
|
|
68
87
|
CGI::Cookie.parse(@env["HTTP_COOKIE"])
|
|
88
|
+
rescue StandardError
|
|
89
|
+
[]
|
|
69
90
|
end
|
|
70
91
|
end
|
|
71
92
|
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShopifyCLI
|
|
4
|
+
module Theme
|
|
5
|
+
class Repl
|
|
6
|
+
class RemoteEvaluator
|
|
7
|
+
extend Forwardable
|
|
8
|
+
|
|
9
|
+
attr_reader :snippet
|
|
10
|
+
|
|
11
|
+
def_delegators :snippet, :ctx, :api, :session, :input
|
|
12
|
+
|
|
13
|
+
def initialize(snippet)
|
|
14
|
+
@snippet = snippet
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def evaluate
|
|
18
|
+
catch(:result) do
|
|
19
|
+
eval_result || eval_context || eval_assignment_context || eval_syntax_error
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def eval_result
|
|
26
|
+
ctx.debug("Evaluating result")
|
|
27
|
+
|
|
28
|
+
json = <<~JSON
|
|
29
|
+
{ "type": "display", "value": {{ #{input} | json }} }
|
|
30
|
+
JSON
|
|
31
|
+
|
|
32
|
+
response = json_request(json)
|
|
33
|
+
|
|
34
|
+
return nil unless success?(response)
|
|
35
|
+
|
|
36
|
+
json = json_from_response(response)
|
|
37
|
+
json.last["value"] if json
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def eval_context
|
|
41
|
+
ctx.debug("Evaluating context")
|
|
42
|
+
|
|
43
|
+
json = <<~JSON
|
|
44
|
+
{ "type": "context", "value": "{% #{input.gsub(/"/, "\\\"")} %}" }
|
|
45
|
+
JSON
|
|
46
|
+
|
|
47
|
+
response = json_request(json)
|
|
48
|
+
session << JSON.parse(json) if success?(response)
|
|
49
|
+
|
|
50
|
+
nil
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def eval_assignment_context
|
|
54
|
+
ctx.debug("Evaluating assignment context")
|
|
55
|
+
|
|
56
|
+
return unless smart_assignment?(input)
|
|
57
|
+
|
|
58
|
+
input.prepend("assign ")
|
|
59
|
+
ctx.puts("{{gray:> #{input}}}")
|
|
60
|
+
|
|
61
|
+
eval_context
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def eval_syntax_error
|
|
65
|
+
ctx.debug("Evaluating syntax error")
|
|
66
|
+
|
|
67
|
+
body = ""
|
|
68
|
+
body = extract_body(request("{{ #{input} }}")) unless standard_assignment?(input)
|
|
69
|
+
body = extract_body(request("{% #{input} %}")) unless has_liquid_error?(body)
|
|
70
|
+
|
|
71
|
+
return unless has_liquid_error?(body)
|
|
72
|
+
|
|
73
|
+
error = body.gsub(/ \(snippets\/eval line \d+\)/, "")
|
|
74
|
+
|
|
75
|
+
ctx.puts("{{red:#{error}}}")
|
|
76
|
+
|
|
77
|
+
nil
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def json_from_response(response)
|
|
81
|
+
JSON.parse(
|
|
82
|
+
extract_body(response),
|
|
83
|
+
)
|
|
84
|
+
rescue StandardError
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def json_request(json)
|
|
89
|
+
request_body = <<~LIQUID
|
|
90
|
+
[
|
|
91
|
+
#{session.map(&:to_json).push(json).join(",").gsub('\"', '"')}
|
|
92
|
+
]
|
|
93
|
+
LIQUID
|
|
94
|
+
|
|
95
|
+
request(request_body)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def request(request_body)
|
|
99
|
+
response = api.request(request_body)
|
|
100
|
+
|
|
101
|
+
expired_session_error if expired_session?(response)
|
|
102
|
+
too_many_requests_error if too_many_requests?(response)
|
|
103
|
+
not_found_error if not_found?(response)
|
|
104
|
+
|
|
105
|
+
response
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def smart_assignment?(input)
|
|
109
|
+
/^\s*((?-mix:\(?[\w\-\.\[\]]\)?)+)\s*=\s*(.*)\s*/m.match?(input)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def standard_assignment?(input)
|
|
113
|
+
/^\s*assign\s*((?-mix:\(?[\w\-\.\[\]]\)?)+)\s*=\s*(.*)\s*/m.match?(input)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def has_liquid_error?(body)
|
|
117
|
+
body.match?(/\ALiquid (syntax )?error/)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def success?(response)
|
|
121
|
+
response.code == "200" && !has_liquid_error?(extract_body(response))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def expired_session?(response)
|
|
125
|
+
response.code == "401" || response.code == "403"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def too_many_requests?(response)
|
|
129
|
+
response.code == "430" || response.code == "429"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def extract_body(response)
|
|
133
|
+
response.body.lines[1..-2].join.strip
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def not_found?(response)
|
|
137
|
+
# Section Rendering API returns 200 even on unknown paths.
|
|
138
|
+
response.header["server-timing"]&.include?("pageType;desc=\"404\"")
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def expired_session_error
|
|
142
|
+
ctx.puts("{{red:Session expired. Please initiate a new one.}}")
|
|
143
|
+
raise ShopifyCLI::AbortSilent
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def too_many_requests_error
|
|
147
|
+
ctx.puts("{{red:Evaluations limit reached. Try again later.}}")
|
|
148
|
+
raise ShopifyCLI::AbortSilent
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def not_found_error
|
|
152
|
+
ctx.puts("{{red:Page not found. Please provide a valid --url value.}}")
|
|
153
|
+
raise ShopifyCLI::AbortSilent
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ShopifyCLI
|
|
4
|
+
module Theme
|
|
5
|
+
class Repl
|
|
6
|
+
class Snippet
|
|
7
|
+
attr_reader :ctx, :api, :session, :input
|
|
8
|
+
|
|
9
|
+
def initialize(ctx, api, session, input)
|
|
10
|
+
@ctx = ctx
|
|
11
|
+
@api = api
|
|
12
|
+
@session = session
|
|
13
|
+
@input = input
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def render
|
|
17
|
+
return delimiter_warning if has_delimiter?(input)
|
|
18
|
+
|
|
19
|
+
output = evaluator.evaluate
|
|
20
|
+
output = present(output)
|
|
21
|
+
|
|
22
|
+
ctx.puts(output)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
def present(output)
|
|
28
|
+
return json_error if json_error?(output)
|
|
29
|
+
return empty if output.nil?
|
|
30
|
+
|
|
31
|
+
output = JSON.pretty_generate(output)
|
|
32
|
+
|
|
33
|
+
safe_cyan(output)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def evaluator
|
|
37
|
+
@evaluator ||= RemoteEvaluator.new(self)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def safe_cyan(str)
|
|
41
|
+
"\e[36m#{str}\e[0m"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def empty
|
|
45
|
+
safe_cyan("nil")
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def has_delimiter?(input)
|
|
49
|
+
input.match?(/\{\{|\}\}|\{\%|\%\}/)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def delimiter_warning
|
|
53
|
+
ctx.puts("{{yellow:\n#{delimiter_warning_text}}}")
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def delimiter_warning_text
|
|
57
|
+
<<~WARN
|
|
58
|
+
Liquid Console doesn't support Liquid delimiters such as '{{ ... }}' or '{% ... %}'.
|
|
59
|
+
|
|
60
|
+
Please use 'collections.first' instead of '{{ collections.first }}'.
|
|
61
|
+
WARN
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def json_error?(output)
|
|
65
|
+
case output
|
|
66
|
+
when Hash
|
|
67
|
+
output["error"]&.include?("json not allowed for this object")
|
|
68
|
+
when Array
|
|
69
|
+
json_error?(output.first)
|
|
70
|
+
else
|
|
71
|
+
false
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def json_error
|
|
76
|
+
"{{yellow:Object cannot be printed, but you can access its fields. Read more at https://shopify.dev/docs/api/liquid.}}"
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "readline"
|
|
4
|
-
require "json"
|
|
5
3
|
require "cgi"
|
|
4
|
+
require "forwardable"
|
|
5
|
+
require "json"
|
|
6
|
+
require "readline"
|
|
6
7
|
|
|
7
8
|
require "shopify_cli/theme/dev_server"
|
|
8
9
|
|
|
@@ -10,18 +11,18 @@ require_relative "theme_admin_api"
|
|
|
10
11
|
require_relative "repl/api"
|
|
11
12
|
require_relative "repl/auth_dev_server"
|
|
12
13
|
require_relative "repl/auth_middleware"
|
|
14
|
+
require_relative "repl/remote_evaluator"
|
|
15
|
+
require_relative "repl/snippet"
|
|
13
16
|
|
|
14
17
|
module ShopifyCLI
|
|
15
18
|
module Theme
|
|
16
19
|
class Repl
|
|
17
|
-
attr_reader :ctx, :
|
|
20
|
+
attr_reader :ctx, :url, :port, :session, :storefront_digest, :secure_session_id
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
PORT = 9293
|
|
21
|
-
|
|
22
|
-
def initialize(ctx)
|
|
22
|
+
def initialize(ctx, url, port)
|
|
23
23
|
@ctx = ctx
|
|
24
|
-
@
|
|
24
|
+
@url = url
|
|
25
|
+
@port = port
|
|
25
26
|
@session = []
|
|
26
27
|
end
|
|
27
28
|
|
|
@@ -37,15 +38,7 @@ module ShopifyCLI
|
|
|
37
38
|
trap("INT") { raise ShopifyCLI::AbortSilent }
|
|
38
39
|
trap("TERM") { raise ShopifyCLI::AbortSilent }
|
|
39
40
|
|
|
40
|
-
loop
|
|
41
|
-
input = Readline.readline("> ", true)
|
|
42
|
-
output = liquid_eval(input)
|
|
43
|
-
|
|
44
|
-
render(output)
|
|
45
|
-
rescue StandardError => error
|
|
46
|
-
ctx.puts("{{red:Shopify Liquid console error: #{error.message}}}")
|
|
47
|
-
ctx.debug(error.backtrace)
|
|
48
|
-
end
|
|
41
|
+
loop { repl }
|
|
49
42
|
end
|
|
50
43
|
|
|
51
44
|
def authenticate(storefront_digest, secure_session_id)
|
|
@@ -55,90 +48,39 @@ module ShopifyCLI
|
|
|
55
48
|
|
|
56
49
|
private
|
|
57
50
|
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def url
|
|
65
|
-
"http://#{HOST}:#{PORT}"
|
|
66
|
-
end
|
|
67
|
-
|
|
68
|
-
def liquid_eval(liquid_snippet)
|
|
69
|
-
render_result(liquid_snippet) || render_context(liquid_snippet)
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def render_result(liquid_snippet)
|
|
73
|
-
entry_str = as_rendered_json(liquid_snippet)
|
|
74
|
-
response = request(entry_str)
|
|
75
|
-
|
|
76
|
-
return nil unless success?(response)
|
|
77
|
-
|
|
78
|
-
json = json_from_response(response)
|
|
79
|
-
json.last["value"]
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
def render_context(liquid_snippet)
|
|
83
|
-
entry_str = as_context_json(liquid_snippet)
|
|
84
|
-
response = request(entry_str)
|
|
85
|
-
|
|
86
|
-
@session << JSON.parse(entry_str) if success?(response)
|
|
87
|
-
|
|
88
|
-
nil
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
def request(entry_str)
|
|
92
|
-
request_body = @session
|
|
93
|
-
.map(&:to_json)
|
|
94
|
-
.push(entry_str)
|
|
95
|
-
.join(",")
|
|
96
|
-
|
|
97
|
-
response = api.request("[#{request_body}]")
|
|
98
|
-
|
|
99
|
-
if unauthorized?(response) || forbidden?(response)
|
|
100
|
-
ctx.puts("{{red:Session expired.}}")
|
|
101
|
-
raise ShopifyCLI::AbortSilent
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
response
|
|
51
|
+
def repl
|
|
52
|
+
snippet.render
|
|
53
|
+
rescue StandardError => error
|
|
54
|
+
shutdown_session(error)
|
|
105
55
|
end
|
|
106
56
|
|
|
107
|
-
def
|
|
108
|
-
|
|
57
|
+
def snippet
|
|
58
|
+
Snippet.new(ctx, api, session, input)
|
|
109
59
|
end
|
|
110
60
|
|
|
111
|
-
def
|
|
112
|
-
|
|
61
|
+
def input
|
|
62
|
+
Readline.readline("> ", true)
|
|
113
63
|
end
|
|
114
64
|
|
|
115
|
-
def
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def json_from_response(response)
|
|
120
|
-
json_str = response.body.lines[1..-2].join.strip
|
|
121
|
-
JSON.parse(json_str)
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def as_rendered_json(liquid_snippet)
|
|
125
|
-
as_json_str("render", "{{ #{liquid_snippet} | json }}")
|
|
126
|
-
end
|
|
65
|
+
def shutdown_session(error)
|
|
66
|
+
message = error.message
|
|
67
|
+
backtrace = error.backtrace
|
|
68
|
+
error_message = "{{red:Shopify Liquid console error: #{message}}}"
|
|
127
69
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
end
|
|
70
|
+
ctx.puts(error_message)
|
|
71
|
+
ctx.debug(backtrace)
|
|
131
72
|
|
|
132
|
-
|
|
133
|
-
<<~JSON
|
|
134
|
-
{ "type": "#{type}", "value": #{value} }
|
|
135
|
-
JSON
|
|
73
|
+
raise ShopifyCLI::AbortSilent
|
|
136
74
|
end
|
|
137
75
|
|
|
138
76
|
def authenticate!
|
|
139
77
|
# Currently, Shopify CLI can't bypass the store password, so the
|
|
140
|
-
# `AuthDevServer` gets the session to perform
|
|
141
|
-
ShopifyCLI::Theme::Repl::AuthDevServer.start(ctx, self,
|
|
78
|
+
# `AuthDevServer` gets the session to perform requests at the SFR.
|
|
79
|
+
ShopifyCLI::Theme::Repl::AuthDevServer.start(ctx, self, port)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def api
|
|
83
|
+
@api ||= Api.new(ctx, url, self)
|
|
142
84
|
end
|
|
143
85
|
end
|
|
144
86
|
end
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { hashString } from '../../public/node/crypto.js';
|
|
2
|
-
import { getPackageManager,
|
|
2
|
+
import { getPackageManager, packageManagerFromUserAgent } from '../../public/node/node-package-manager.js';
|
|
3
3
|
import * as metadata from '../../public/node/metadata.js';
|
|
4
4
|
import { platformAndArch } from '../../public/node/os.js';
|
|
5
5
|
import { ciPlatform, cloudEnvironment, macAddress } from '@shopify/cli-kit/node/context/local';
|
|
@@ -17,7 +17,7 @@ export async function startAnalytics({ commandContent, args, currentTime = new D
|
|
|
17
17
|
},
|
|
18
18
|
}));
|
|
19
19
|
await metadata.addPublicMetadata(() => ({
|
|
20
|
-
cmd_all_launcher:
|
|
20
|
+
cmd_all_launcher: packageManagerFromUserAgent(),
|
|
21
21
|
cmd_all_alias_used: commandContent.alias,
|
|
22
22
|
cmd_all_topic: commandContent.topic,
|
|
23
23
|
cmd_all_plugin: commandClass?.plugin?.name,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/private/node/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,UAAU,EAAC,MAAM,6BAA6B,CAAA;AACtD,OAAO,EAAC,iBAAiB,EAAE,
|
|
1
|
+
{"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/private/node/analytics.ts"],"names":[],"mappings":"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","sourcesContent":["import {hashString} from '../../public/node/crypto.js'\nimport {getPackageManager, packageManagerFromUserAgent} from '../../public/node/node-package-manager.js'\nimport BaseCommand from '../../public/node/base-command.js'\nimport {CommandContent} from '../../public/node/hooks/prerun.js'\nimport * as metadata from '../../public/node/metadata.js'\nimport {platformAndArch} from '../../public/node/os.js'\nimport {Command, Interfaces} from '@oclif/core'\nimport {ciPlatform, cloudEnvironment, macAddress} from '@shopify/cli-kit/node/context/local'\nimport {cwd} from '@shopify/cli-kit/node/path'\n\ninterface StartOptions {\n commandContent: CommandContent\n args: string[]\n currentTime?: number\n commandClass?: Command.Class | typeof BaseCommand\n}\n\nexport async function startAnalytics({\n commandContent,\n args,\n currentTime = new Date().getTime(),\n commandClass,\n}: StartOptions): Promise<void> {\n let startCommand: string = commandContent.command\n if (commandClass && Object.prototype.hasOwnProperty.call(commandClass, 'analyticsNameOverride')) {\n startCommand = (commandClass as typeof BaseCommand).analyticsNameOverride() ?? commandContent.command\n }\n\n await metadata.addSensitiveMetadata(() => ({\n commandStartOptions: {\n startTime: currentTime,\n startCommand,\n startArgs: args,\n },\n }))\n\n await metadata.addPublicMetadata(() => ({\n cmd_all_launcher: packageManagerFromUserAgent(),\n cmd_all_alias_used: commandContent.alias,\n cmd_all_topic: commandContent.topic,\n cmd_all_plugin: commandClass?.plugin?.name,\n }))\n}\n\ninterface EnvironmentData {\n uname: string\n env_ci: boolean\n env_ci_platform?: string\n env_plugin_installed_any_custom: boolean\n env_plugin_installed_shopify: string\n env_shell: string\n env_web_ide: string | undefined\n env_device_id: string\n env_cloud: string\n env_package_manager: string\n}\n\nexport async function getEnvironmentData(config: Interfaces.Config): Promise<EnvironmentData> {\n const ciplatform = ciPlatform()\n\n const pluginNames = getPluginNames(config)\n const shopifyPlugins = pluginNames.filter((plugin) => plugin.startsWith('@shopify/'))\n\n const {platform, arch} = platformAndArch()\n\n return {\n uname: `${platform} ${arch}`,\n env_ci: ciplatform.isCI,\n env_ci_platform: ciplatform.name,\n env_plugin_installed_any_custom: pluginNames.length !== shopifyPlugins.length,\n env_plugin_installed_shopify: JSON.stringify(shopifyPlugins),\n env_shell: config.shell,\n env_web_ide: cloudEnvironment().editor ? cloudEnvironment().platform : undefined,\n env_device_id: hashString(await macAddress()),\n env_cloud: cloudEnvironment().platform,\n env_package_manager: await getPackageManager(cwd()),\n }\n}\n\nexport async function getSensitiveEnvironmentData(config: Interfaces.Config) {\n return {\n env_plugin_installed_all: JSON.stringify(getPluginNames(config)),\n }\n}\n\nfunction getPluginNames(config: Interfaces.Config) {\n return config.plugins\n .map((plugin) => plugin.name)\n .sort()\n .filter((plugin) => !plugin.startsWith('@oclif/'))\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { RequestDocument, Variables } from 'graphql-request';
|
|
2
|
-
export declare function debugLogRequestInfo
|
|
2
|
+
export declare function debugLogRequestInfo(api: string, query: RequestDocument, variables?: Variables, headers?: {
|
|
3
3
|
[key: string]: string;
|
|
4
4
|
}): void;
|
|
5
|
-
export declare function errorHandler<T>(api: string): (error: unknown) => Error | unknown;
|
|
5
|
+
export declare function errorHandler<T>(api: string): (error: unknown, requestId?: string) => Error | unknown;
|