@shopify/cli-kit 3.48.6 → 3.49.1

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.
Files changed (87) hide show
  1. package/README.md +3 -1
  2. package/assets/cli-ruby/lib/project_types/theme/commands/console.rb +9 -3
  3. package/assets/cli-ruby/lib/shopify_cli/theme/dev_server/proxy_param_builder.rb +2 -0
  4. package/assets/cli-ruby/lib/shopify_cli/theme/extension/dev_server.rb +1 -1
  5. package/assets/cli-ruby/lib/shopify_cli/theme/repl/api.rb +7 -7
  6. package/assets/cli-ruby/lib/shopify_cli/theme/repl/auth_dev_server.rb +18 -3
  7. package/assets/cli-ruby/lib/shopify_cli/theme/repl/auth_middleware.rb +27 -6
  8. package/assets/cli-ruby/lib/shopify_cli/theme/repl/remote_evaluator.rb +158 -0
  9. package/assets/cli-ruby/lib/shopify_cli/theme/repl/resources/success.html +0 -1
  10. package/assets/cli-ruby/lib/shopify_cli/theme/repl/snippet.rb +81 -0
  11. package/assets/cli-ruby/lib/shopify_cli/theme/repl.rb +31 -89
  12. package/dist/private/node/analytics.js +2 -2
  13. package/dist/private/node/analytics.js.map +1 -1
  14. package/dist/private/node/api/graphql.d.ts +2 -2
  15. package/dist/private/node/api/graphql.js +12 -6
  16. package/dist/private/node/api/graphql.js.map +1 -1
  17. package/dist/private/node/api.d.ts +1 -1
  18. package/dist/private/node/api.js +1 -1
  19. package/dist/private/node/api.js.map +1 -1
  20. package/dist/private/node/context/utilities.d.ts +2 -0
  21. package/dist/private/node/context/utilities.js +3 -2
  22. package/dist/private/node/context/utilities.js.map +1 -1
  23. package/dist/private/node/demo-recorder.d.ts +0 -3
  24. package/dist/private/node/demo-recorder.js +3 -5
  25. package/dist/private/node/demo-recorder.js.map +1 -1
  26. package/dist/private/node/session/exchange.js +1 -0
  27. package/dist/private/node/session/exchange.js.map +1 -1
  28. package/dist/private/node/testing/ui.d.ts +6 -1
  29. package/dist/private/node/testing/ui.js +25 -1
  30. package/dist/private/node/testing/ui.js.map +1 -1
  31. package/dist/private/node/ui/components/AutocompletePrompt.test.js +2 -0
  32. package/dist/private/node/ui/components/AutocompletePrompt.test.js.map +1 -1
  33. package/dist/private/node/ui/components/Command.js +1 -1
  34. package/dist/private/node/ui/components/Command.js.map +1 -1
  35. package/dist/private/node/ui/components/Command.test.js +1 -1
  36. package/dist/private/node/ui/components/Command.test.js.map +1 -1
  37. package/dist/private/node/ui/components/ConcurrentOutput.d.ts +2 -23
  38. package/dist/private/node/ui/components/ConcurrentOutput.js +39 -103
  39. package/dist/private/node/ui/components/ConcurrentOutput.js.map +1 -1
  40. package/dist/private/node/ui/components/ConcurrentOutput.test.js +22 -208
  41. package/dist/private/node/ui/components/ConcurrentOutput.test.js.map +1 -1
  42. package/dist/private/node/ui/components/SelectPrompt.test.js +2 -0
  43. package/dist/private/node/ui/components/SelectPrompt.test.js.map +1 -1
  44. package/dist/private/node/ui/components/Tasks.test.js +2 -0
  45. package/dist/private/node/ui/components/Tasks.test.js.map +1 -1
  46. package/dist/private/node/ui/components/TextPrompt.test.js +2 -0
  47. package/dist/private/node/ui/components/TextPrompt.test.js.map +1 -1
  48. package/dist/private/node/ui/hooks/use-abort-signal.d.ts +1 -1
  49. package/dist/private/node/ui/hooks/use-abort-signal.js +8 -3
  50. package/dist/private/node/ui/hooks/use-abort-signal.js.map +1 -1
  51. package/dist/private/node/ui/utilities.d.ts +1 -1
  52. package/dist/private/node/ui.js +1 -1
  53. package/dist/private/node/ui.js.map +1 -1
  54. package/dist/public/common/version.d.ts +1 -1
  55. package/dist/public/common/version.js +1 -1
  56. package/dist/public/common/version.js.map +1 -1
  57. package/dist/public/node/base-command.d.ts +2 -4
  58. package/dist/public/node/base-command.js +18 -4
  59. package/dist/public/node/base-command.js.map +1 -1
  60. package/dist/public/node/dot-env.d.ts +0 -7
  61. package/dist/public/node/dot-env.js +1 -9
  62. package/dist/public/node/dot-env.js.map +1 -1
  63. package/dist/public/node/node-package-manager.d.ts +16 -5
  64. package/dist/public/node/node-package-manager.js +28 -13
  65. package/dist/public/node/node-package-manager.js.map +1 -1
  66. package/dist/public/node/output.d.ts +1 -1
  67. package/dist/public/node/output.js +4 -0
  68. package/dist/public/node/output.js.map +1 -1
  69. package/dist/public/node/system.js +3 -6
  70. package/dist/public/node/system.js.map +1 -1
  71. package/dist/public/node/testing/ui.d.ts +1 -0
  72. package/dist/public/node/testing/ui.js +2 -0
  73. package/dist/public/node/testing/ui.js.map +1 -0
  74. package/dist/public/node/tree-kill.d.ts +7 -2
  75. package/dist/public/node/tree-kill.js +165 -4
  76. package/dist/public/node/tree-kill.js.map +1 -1
  77. package/dist/public/node/ui/components.d.ts +1 -0
  78. package/dist/public/node/ui/components.js +2 -0
  79. package/dist/public/node/ui/components.js.map +1 -0
  80. package/dist/public/node/ui/hooks.d.ts +1 -0
  81. package/dist/public/node/ui/hooks.js +2 -0
  82. package/dist/public/node/ui/hooks.js.map +1 -0
  83. package/dist/public/node/ui.d.ts +3 -10
  84. package/dist/public/node/ui.js +5 -14
  85. package/dist/public/node/ui.js.map +1 -1
  86. package/dist/tsconfig.tsbuildinfo +1 -1
  87. 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>&nbsp;</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
- recommend_default_ruby_range
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
- def call(_args, *)
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
@@ -55,6 +55,8 @@ module ShopifyCLI
55
55
 
56
56
  def cookie_sections
57
57
  CGI::Cookie.parse(cookie)["hot_reload_files"].join.split(",") || []
58
+ rescue StandardError
59
+ []
58
60
  end
59
61
 
60
62
  def core?(path)
@@ -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.preview_message)
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[sections/announcement-bar.liquid]" => template,
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: "layout/theme.liquid", value: "{{ content_for_header }}{{ content_for_layout }}" },
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) unless authenticated?
20
+ return @app.call(@env) if password_page?(@env)
18
21
 
19
22
  authenticate!
20
- shutdown
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
@@ -65,7 +65,6 @@
65
65
  padding-left: 7.5em;
66
66
  }
67
67
  </style>
68
- <link rel="icon" href="favicon.svg" sizes="any" type="image/svg+xml">
69
68
  </head>
70
69
 
71
70
  <body class="body-success">
@@ -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, :api, :storefront_digest, :secure_session_id
20
+ attr_reader :ctx, :url, :port, :session, :storefront_digest, :secure_session_id
18
21
 
19
- HOST = "localhost"
20
- PORT = 9293
21
-
22
- def initialize(ctx)
22
+ def initialize(ctx, url, port)
23
23
  @ctx = ctx
24
- @api = Api.new(ctx, self)
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 do
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 render(output)
59
- output = output ? JSON.pretty_generate(output) : "nil"
60
-
61
- ctx.puts("{{cyan:#{output}}}")
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 success?(response)
108
- response.code == "200"
57
+ def snippet
58
+ Snippet.new(ctx, api, session, input)
109
59
  end
110
60
 
111
- def unauthorized?(response)
112
- response.code == "401"
61
+ def input
62
+ Readline.readline("> ", true)
113
63
  end
114
64
 
115
- def forbidden?(response)
116
- response.code == "403"
117
- end
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
- def as_context_json(liquid_snippet)
129
- as_json_str("context", "\"{% #{liquid_snippet} %}\"")
130
- end
70
+ ctx.puts(error_message)
71
+ ctx.debug(backtrace)
131
72
 
132
- def as_json_str(type, value)
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 requests at the SFR.
141
- ShopifyCLI::Theme::Repl::AuthDevServer.start(ctx, self, PORT)
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, packageManagerUsedForCreating } from '../../public/node/node-package-manager.js';
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: packageManagerUsedForCreating(),
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":"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","sourcesContent":["import {hashString} from '../../public/node/crypto.js'\nimport {getPackageManager, packageManagerUsedForCreating} 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: packageManagerUsedForCreating(),\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
+ {"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<T>(api: string, query: RequestDocument, variables?: Variables, headers?: {
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;