shopify-cli 2.6.6 → 2.7.3

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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/.devcontainer.json +5 -0
  3. data/.github/CODEOWNERS +2 -2
  4. data/.github/DESIGN.md +1 -1
  5. data/.github/ISSUE_TEMPLATE.md +7 -0
  6. data/.github/workflows/shopify.yml +1 -1
  7. data/.gitignore +1 -0
  8. data/.ruby-version +1 -1
  9. data/.vscode/extensions.json +5 -0
  10. data/.vscode/settings.json +9 -0
  11. data/CHANGELOG.md +44 -4
  12. data/CONTRIBUTING.md +1 -29
  13. data/{Dockerfile → Codespace.dockerfile} +2 -2
  14. data/Gemfile.lock +5 -5
  15. data/README.md +20 -99
  16. data/Rakefile +27 -0
  17. data/Tests.dockerfile +35 -0
  18. data/assets/logo.png +0 -0
  19. data/dev.yml +1 -4
  20. data/docs/README.md +13 -0
  21. data/docs/contributors/testing.md +27 -0
  22. data/docs/users/installation.md +46 -0
  23. data/{THEMEKIT_MIGRATION.md → docs/users/migrate-from-themekit.md} +1 -1
  24. data/ext/javy/hashes/javy-arm-macos-v0.1.0.gz.sha256 +1 -0
  25. data/ext/javy/hashes/javy-x86_64-linux-v0.1.0.gz.sha256 +1 -0
  26. data/ext/javy/hashes/javy-x86_64-macos-v0.1.0.gz.sha256 +1 -0
  27. data/ext/javy/hashes/javy-x86_64-windows-v0.1.0.gz.sha256 +1 -0
  28. data/ext/javy/javy.rb +205 -0
  29. data/ext/javy/version +1 -0
  30. data/lib/project_types/extension/cli.rb +6 -3
  31. data/lib/project_types/extension/commands/build.rb +4 -8
  32. data/lib/project_types/extension/commands/create.rb +2 -5
  33. data/lib/project_types/extension/commands/extension_command.rb +1 -1
  34. data/lib/project_types/extension/features/argo_serve.rb +9 -23
  35. data/lib/project_types/extension/forms/questions/ask_template.rb +1 -5
  36. data/lib/project_types/extension/messages/messages.rb +0 -2
  37. data/lib/project_types/extension/models/development_server.rb +2 -2
  38. data/lib/project_types/extension/models/development_server_requirements.rb +2 -3
  39. data/lib/project_types/extension/models/server_config/app.rb +13 -0
  40. data/lib/project_types/extension/models/server_config/development.rb +5 -4
  41. data/lib/project_types/extension/models/server_config/development_renderer.rb +1 -1
  42. data/lib/project_types/extension/models/server_config/development_resource.rb +13 -0
  43. data/lib/project_types/extension/models/server_config/extension.rb +3 -1
  44. data/lib/project_types/extension/models/server_config/root.rb +4 -1
  45. data/lib/project_types/extension/tasks/convert_server_config.rb +65 -0
  46. data/lib/project_types/extension/tasks/ensure_resource_url.rb +39 -0
  47. data/lib/project_types/extension/tasks/merge_server_config.rb +32 -0
  48. data/lib/project_types/extension/tasks/run_extension_command.rb +11 -10
  49. data/lib/project_types/node/cli.rb +0 -16
  50. data/lib/project_types/node/forms/create.rb +5 -5
  51. data/lib/project_types/node/messages/messages.rb +2 -144
  52. data/lib/project_types/php/cli.rb +0 -11
  53. data/lib/project_types/php/forms/create.rb +5 -6
  54. data/lib/project_types/php/messages/messages.rb +2 -161
  55. data/lib/project_types/rails/cli.rb +0 -16
  56. data/lib/project_types/rails/commands/create.rb +3 -5
  57. data/lib/project_types/rails/forms/create.rb +5 -6
  58. data/lib/project_types/rails/messages/messages.rb +6 -151
  59. data/lib/project_types/script/cli.rb +8 -2
  60. data/lib/project_types/script/commands/create.rb +2 -4
  61. data/lib/project_types/script/commands/javy.rb +29 -0
  62. data/lib/project_types/script/commands/push.rb +3 -2
  63. data/lib/project_types/script/config/extension_points.yml +12 -30
  64. data/lib/project_types/script/forms/ask_app.rb +32 -0
  65. data/lib/project_types/script/forms/ask_org.rb +30 -0
  66. data/lib/project_types/script/forms/ask_script_uuid.rb +22 -0
  67. data/lib/project_types/script/forms/run_against_shopify_org.rb +14 -0
  68. data/lib/project_types/script/graphql/app_script_set.graphql +2 -2
  69. data/lib/project_types/script/layers/application/build_script.rb +0 -1
  70. data/lib/project_types/script/layers/application/connect_app.rb +73 -0
  71. data/lib/project_types/script/layers/application/create_script.rb +1 -1
  72. data/lib/project_types/script/layers/application/push_script.rb +1 -1
  73. data/lib/project_types/script/layers/domain/errors.rb +1 -4
  74. data/lib/project_types/script/layers/domain/push_package.rb +3 -3
  75. data/lib/project_types/script/layers/domain/{script_json.rb → script_config.rb} +2 -2
  76. data/lib/project_types/script/layers/domain/script_project.rb +5 -1
  77. data/lib/project_types/script/layers/infrastructure/errors.rb +36 -7
  78. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +0 -4
  79. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +0 -4
  80. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -2
  81. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +104 -27
  82. data/lib/project_types/script/layers/infrastructure/script_service.rb +11 -11
  83. data/lib/project_types/script/messages/messages.rb +21 -4
  84. data/lib/project_types/script/ui/error_handler.rb +31 -21
  85. data/lib/project_types/theme/cli.rb +1 -1
  86. data/lib/project_types/theme/commands/check.rb +1 -1
  87. data/lib/project_types/theme/commands/delete.rb +1 -1
  88. data/lib/project_types/theme/commands/init.rb +1 -1
  89. data/lib/project_types/theme/commands/language_server.rb +1 -1
  90. data/lib/project_types/theme/commands/package.rb +1 -1
  91. data/lib/project_types/theme/commands/publish.rb +1 -1
  92. data/lib/project_types/theme/commands/pull.rb +4 -1
  93. data/lib/project_types/theme/commands/push.rb +5 -1
  94. data/lib/project_types/theme/commands/serve.rb +9 -3
  95. data/lib/project_types/theme/messages/messages.rb +39 -2
  96. data/lib/project_types/theme/ui/sync_progress_bar.rb +2 -2
  97. data/lib/shopify_cli/admin_api/populate_resource_command.rb +1 -1
  98. data/lib/shopify_cli/api.rb +7 -2
  99. data/lib/shopify_cli/app_type_detector.rb +24 -20
  100. data/lib/shopify_cli/command/app_sub_command.rb +10 -0
  101. data/lib/shopify_cli/command/project_command.rb +31 -0
  102. data/lib/shopify_cli/command/sub_command.rb +19 -0
  103. data/lib/shopify_cli/command.rb +7 -2
  104. data/lib/shopify_cli/commands/app/connect.rb +22 -0
  105. data/lib/shopify_cli/commands/app/create/node.rb +36 -0
  106. data/lib/shopify_cli/commands/app/create/php.rb +36 -0
  107. data/lib/shopify_cli/commands/app/create/rails.rb +38 -0
  108. data/lib/shopify_cli/commands/app/create.rb +28 -0
  109. data/lib/shopify_cli/commands/app/deploy.rb +49 -0
  110. data/lib/shopify_cli/commands/app/open.rb +19 -0
  111. data/lib/shopify_cli/commands/app/serve.rb +49 -0
  112. data/lib/shopify_cli/commands/app/tunnel.rb +43 -0
  113. data/lib/shopify_cli/commands/app.rb +29 -0
  114. data/lib/shopify_cli/commands/config.rb +2 -2
  115. data/lib/shopify_cli/commands.rb +1 -0
  116. data/lib/shopify_cli/constants.rb +7 -0
  117. data/lib/shopify_cli/context.rb +10 -0
  118. data/lib/shopify_cli/environment.rb +4 -0
  119. data/lib/shopify_cli/exception_reporter.rb +3 -4
  120. data/lib/shopify_cli/git.rb +14 -1
  121. data/lib/shopify_cli/github/issue_url_generator.rb +19 -0
  122. data/lib/shopify_cli/github.rb +5 -0
  123. data/lib/shopify_cli/identity_auth.rb +18 -0
  124. data/lib/shopify_cli/messages/messages.rb +254 -9
  125. data/lib/shopify_cli/partners_api.rb +1 -8
  126. data/lib/shopify_cli/project.rb +5 -1
  127. data/lib/shopify_cli/project_commands.rb +1 -1
  128. data/lib/shopify_cli/services/app/connect_service.rb +25 -0
  129. data/lib/shopify_cli/services/app/create/node_service.rb +155 -0
  130. data/lib/shopify_cli/services/app/create/php_service.rb +152 -0
  131. data/lib/shopify_cli/services/app/create/rails_service.rb +215 -0
  132. data/lib/shopify_cli/services/app/deploy/heroku/node_service.rb +101 -0
  133. data/lib/shopify_cli/services/app/deploy/heroku/php_service.rb +135 -0
  134. data/lib/shopify_cli/services/app/deploy/heroku/rails_service.rb +120 -0
  135. data/lib/shopify_cli/services/app/open_service.rb +19 -0
  136. data/lib/shopify_cli/services/app/serve/node_service.rb +42 -0
  137. data/lib/shopify_cli/services/app/serve/php_service.rb +46 -0
  138. data/lib/shopify_cli/services/app/serve/rails_service.rb +48 -0
  139. data/lib/shopify_cli/services/app/tunnel/auth_service.rb +21 -0
  140. data/lib/shopify_cli/services/app/tunnel/start_service.rb +20 -0
  141. data/lib/shopify_cli/services/app/tunnel/stop_service.rb +20 -0
  142. data/lib/shopify_cli/services.rb +31 -0
  143. data/lib/shopify_cli/tasks/ensure_authenticated.rb +9 -3
  144. data/lib/shopify_cli/theme/dev_server/cdn_fonts.rb +73 -0
  145. data/lib/shopify_cli/theme/dev_server/hot-reload.js +25 -9
  146. data/lib/shopify_cli/theme/dev_server/local_assets.rb +1 -1
  147. data/lib/shopify_cli/theme/dev_server.rb +37 -18
  148. data/lib/shopify_cli/theme/syncer/error_reporter.rb +45 -0
  149. data/lib/shopify_cli/theme/syncer/operation.rb +56 -0
  150. data/lib/shopify_cli/theme/syncer/standard_reporter.rb +32 -0
  151. data/lib/shopify_cli/theme/syncer.rb +40 -39
  152. data/lib/shopify_cli/theme/theme.rb +31 -19
  153. data/lib/shopify_cli/tunnel.rb +26 -22
  154. data/lib/shopify_cli/version.rb +1 -1
  155. data/lib/shopify_cli.rb +1 -2
  156. data/shopify-cli.gemspec +1 -1
  157. data/shopify-dev +18 -0
  158. data/utilities/constants.rb +7 -0
  159. data/utilities/docker/container.rb +10 -3
  160. data/utilities/docker.rb +2 -2
  161. data/utilities/utilities.rb +1 -0
  162. data/vendor/deps/cli-kit/lib/cli/kit/system.rb +1 -1
  163. metadata +66 -50
  164. data/docs/_config.yml +0 -2
  165. data/docs/app/node/commands/index.md +0 -4
  166. data/docs/app/node/index.md +0 -4
  167. data/docs/app/rails/commands/index.md +0 -4
  168. data/docs/app/rails/index.md +0 -4
  169. data/docs/core/index.md +0 -4
  170. data/docs/getting-started/index.md +0 -4
  171. data/docs/getting-started/install/index.md +0 -4
  172. data/docs/getting-started/migrate/index.md +0 -4
  173. data/docs/getting-started/uninstall/index.md +0 -4
  174. data/docs/getting-started/upgrade/index.md +0 -4
  175. data/docs/help/start-app/index.md +0 -4
  176. data/docs/index.md +0 -4
  177. data/install.sh +0 -7
  178. data/lib/project_types/extension/tasks/converters/server_config_converter.rb +0 -30
  179. data/lib/project_types/extension/tasks/load_server_config.rb +0 -28
  180. data/lib/project_types/node/commands/connect.rb +0 -21
  181. data/lib/project_types/node/commands/create.rb +0 -125
  182. data/lib/project_types/node/commands/deploy/heroku.rb +0 -96
  183. data/lib/project_types/node/commands/deploy.rb +0 -32
  184. data/lib/project_types/node/commands/generate.rb +0 -22
  185. data/lib/project_types/node/commands/open.rb +0 -18
  186. data/lib/project_types/node/commands/serve.rb +0 -45
  187. data/lib/project_types/node/commands/tunnel.rb +0 -41
  188. data/lib/project_types/php/commands/connect.rb +0 -19
  189. data/lib/project_types/php/commands/create.rb +0 -143
  190. data/lib/project_types/php/commands/deploy/heroku.rb +0 -129
  191. data/lib/project_types/php/commands/deploy.rb +0 -32
  192. data/lib/project_types/php/commands/open.rb +0 -16
  193. data/lib/project_types/php/commands/serve.rb +0 -48
  194. data/lib/project_types/php/commands/tunnel.rb +0 -37
  195. data/lib/project_types/rails/commands/connect.rb +0 -21
  196. data/lib/project_types/rails/commands/deploy/heroku.rb +0 -115
  197. data/lib/project_types/rails/commands/deploy.rb +0 -32
  198. data/lib/project_types/rails/commands/generate/webhook.rb +0 -42
  199. data/lib/project_types/rails/commands/generate.rb +0 -60
  200. data/lib/project_types/rails/commands/open.rb +0 -18
  201. data/lib/project_types/rails/commands/serve.rb +0 -51
  202. data/lib/project_types/rails/commands/tunnel.rb +0 -41
  203. data/lib/project_types/script/tasks/ensure_env.rb +0 -106
  204. data/lib/shopify_cli/sub_command.rb +0 -17
  205. data/shopify.fish +0 -12
  206. data/shopify.sh +0 -11
@@ -3,6 +3,7 @@ require_relative "development_theme"
3
3
  require_relative "ignore_filter"
4
4
  require_relative "syncer"
5
5
 
6
+ require_relative "dev_server/cdn_fonts"
6
7
  require_relative "dev_server/hot_reload"
7
8
  require_relative "dev_server/header_hash"
8
9
  require_relative "dev_server/local_assets"
@@ -17,10 +18,14 @@ require "pathname"
17
18
  module ShopifyCLI
18
19
  module Theme
19
20
  module DevServer
21
+ # Errors
22
+ Error = Class.new(StandardError)
23
+ AddressBindingError = Class.new(Error)
24
+
20
25
  class << self
21
26
  attr_accessor :ctx
22
27
 
23
- def start(ctx, root, bind: "127.0.0.1", port: 9292, poll: false)
28
+ def start(ctx, root, host: "127.0.0.1", port: 9292, poll: false)
24
29
  @ctx = ctx
25
30
  theme = DevelopmentTheme.new(ctx, root: root)
26
31
  ignore_filter = IgnoreFilter.from_path(root)
@@ -29,9 +34,11 @@ module ShopifyCLI
29
34
 
30
35
  # Setup the middleware stack. Mimics Rack::Builder / config.ru, but in reverse order
31
36
  @app = Proxy.new(ctx, theme: theme, syncer: @syncer)
37
+ @app = CdnFonts.new(@app, theme: theme)
32
38
  @app = LocalAssets.new(ctx, @app, theme: theme)
33
39
  @app = HotReload.new(ctx, @app, theme: theme, watcher: watcher, ignore_filter: ignore_filter)
34
40
  stopped = false
41
+ address = "http://#{host}:#{port}"
35
42
 
36
43
  theme.ensure_exists!
37
44
 
@@ -40,8 +47,8 @@ module ShopifyCLI
40
47
  stop
41
48
  end
42
49
 
43
- CLI::UI::Frame.open(@ctx.message("theme.serve.serve")) do
44
- ctx.print_task("Syncing theme ##{theme.id} on #{theme.shop}")
50
+ CLI::UI::Frame.open(@ctx.message("theme.serve.viewing_theme")) do
51
+ ctx.print_task(ctx.message("theme.serve.syncing_theme", theme.id, theme.shop))
45
52
  @syncer.start_threads
46
53
  if block_given?
47
54
  yield @syncer
@@ -51,18 +58,9 @@ module ShopifyCLI
51
58
 
52
59
  return if stopped
53
60
 
54
- ctx.puts("")
55
- ctx.puts("Serving #{theme.root}")
56
- ctx.puts("")
57
- ctx.open_url!("http://127.0.0.1:#{port}")
58
- ctx.puts("")
59
- ctx.puts("Customize this theme in the Online Store Editor:")
60
- ctx.puts("{{green:#{theme.editor_url}}}")
61
- ctx.puts("")
62
- ctx.puts("Share this theme preview:")
63
- ctx.puts("{{green:#{theme.preview_url}}}")
64
- ctx.puts("")
65
- ctx.puts("(Use Ctrl-C to stop)")
61
+ ctx.puts(ctx.message("theme.serve.serving", theme.root))
62
+ ctx.open_url!(address)
63
+ ctx.puts(ctx.message("theme.serve.customize_or_preview", theme.editor_url, theme.preview_url))
66
64
  end
67
65
 
68
66
  logger = if ctx.debug?
@@ -74,7 +72,7 @@ module ShopifyCLI
74
72
  watcher.start
75
73
  WebServer.run(
76
74
  @app,
77
- BindAddress: bind,
75
+ BindAddress: host,
78
76
  Port: port,
79
77
  Logger: logger,
80
78
  AccessLog: [],
@@ -83,8 +81,11 @@ module ShopifyCLI
83
81
 
84
82
  rescue ShopifyCLI::API::APIRequestForbiddenError,
85
83
  ShopifyCLI::API::APIRequestUnauthorizedError
86
- @ctx.abort("You are not authorized to edit themes on #{theme.shop}.\n" \
87
- "Make sure you are a user of that store, and allowed to edit themes.")
84
+ raise ShopifyCLI::Abort, @ctx.message("theme.serve.ensure_user", theme.shop)
85
+ rescue Errno::EADDRINUSE
86
+ abort_address_already_in_use(address)
87
+ rescue Errno::EADDRNOTAVAIL
88
+ raise AddressBindingError, "Error binding to the address #{host}."
88
89
  end
89
90
 
90
91
  def stop
@@ -93,6 +94,24 @@ module ShopifyCLI
93
94
  @syncer.shutdown
94
95
  WebServer.shutdown
95
96
  end
97
+
98
+ private
99
+
100
+ def abort_address_already_in_use(address)
101
+ open_frame(@ctx.message("theme.serve.already_in_use_error"), color: :red) do
102
+ @ctx.puts(@ctx.message("theme.serve.address_already_in_use", address))
103
+ end
104
+
105
+ open_frame(@ctx.message("theme.serve.try_this"), color: :green) do
106
+ @ctx.puts(@ctx.message("theme.serve.try_port_option"))
107
+ end
108
+
109
+ raise ShopifyCLI::AbortSilent
110
+ end
111
+
112
+ def open_frame(title, color:, &block)
113
+ CLI::UI::Frame.open(title, color: CLI::UI.resolve_color(color), timing: false, &block)
114
+ end
96
115
  end
97
116
  end
98
117
  end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ class Syncer
6
+ ##
7
+ # ShopifyCLI::Theme::Syncer::ErrorReporter allows delaying log of errors,
8
+ # mainly to not break the progress bar.
9
+ #
10
+ class ErrorReporter
11
+ attr_reader :ctx, :delayed_errors
12
+
13
+ def initialize(ctx)
14
+ @ctx = ctx
15
+ @has_any_error = false
16
+ @delay_errors = false
17
+ @delayed_errors = []
18
+ end
19
+
20
+ def disable!
21
+ @delay_errors = true
22
+ end
23
+
24
+ def enable!
25
+ @delay_errors = false
26
+ @delayed_errors.each { |error| report(error) }
27
+ @delayed_errors.clear
28
+ end
29
+
30
+ def report(error_message)
31
+ if @delay_errors
32
+ @delayed_errors << error_message
33
+ else
34
+ @has_any_error = true
35
+ @ctx.error(error_message)
36
+ end
37
+ end
38
+
39
+ def has_any_error?
40
+ @has_any_error
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ class Syncer
6
+ class Operation
7
+ attr_accessor :method, :file
8
+
9
+ COLOR_BY_STATUS = {
10
+ error: :red,
11
+ synced: :green,
12
+ fixed: :cyan,
13
+ }
14
+
15
+ def initialize(ctx, method, file)
16
+ @ctx = ctx
17
+ @method = method
18
+ @file = file
19
+ end
20
+
21
+ def to_s
22
+ "#{method} #{file_path}"
23
+ end
24
+
25
+ def as_error_message
26
+ as_message_with(status: :error)
27
+ end
28
+
29
+ def as_synced_message
30
+ as_message_with(status: :synced)
31
+ end
32
+
33
+ def as_fix_message
34
+ as_message_with(status: :fixed)
35
+ end
36
+
37
+ def file_path
38
+ file&.relative_path.to_s
39
+ end
40
+
41
+ private
42
+
43
+ def as_message_with(status:)
44
+ status_color = COLOR_BY_STATUS[status]
45
+ status_text = @ctx.message("theme.serve.operation.status.#{status}").ljust(6)
46
+
47
+ "#{timestamp} {{#{status_color}:#{status_text}}} {{>}} {{blue:#{self}}}"
48
+ end
49
+
50
+ def timestamp
51
+ Time.now.strftime("%T")
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ShopifyCLI
4
+ module Theme
5
+ class Syncer
6
+ ##
7
+ # ShopifyCLI::Theme::Syncer::StdReporter allows disabling/enabling
8
+ # messages reported in the standard output (ShopifyCLI::Context#puts).
9
+ #
10
+ class StandardReporter
11
+ attr_reader :ctx
12
+
13
+ def initialize(ctx)
14
+ @enabled = true
15
+ @ctx = ctx
16
+ end
17
+
18
+ def disable!
19
+ @enabled = false
20
+ end
21
+
22
+ def enable!
23
+ @enabled = true
24
+ end
25
+
26
+ def report(message)
27
+ ctx.puts(message) if @enabled
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -2,24 +2,31 @@
2
2
  require "thread"
3
3
  require "json"
4
4
  require "base64"
5
+ require "forwardable"
6
+
7
+ require_relative "syncer/error_reporter"
8
+ require_relative "syncer/standard_reporter"
9
+ require_relative "syncer/operation"
5
10
 
6
11
  module ShopifyCLI
7
12
  module Theme
8
13
  class Syncer
9
- class Operation < Struct.new(:method, :file)
10
- def to_s
11
- "#{method} #{file&.relative_path}"
12
- end
13
- end
14
+ extend Forwardable
15
+
14
16
  API_VERSION = "unstable"
15
17
 
16
18
  attr_reader :checksums
17
19
  attr_accessor :ignore_filter
18
20
 
21
+ def_delegators :@error_reporter, :has_any_error?
22
+
19
23
  def initialize(ctx, theme:, ignore_filter: nil)
20
24
  @ctx = ctx
21
25
  @theme = theme
22
26
  @ignore_filter = ignore_filter
27
+ @error_reporter = ErrorReporter.new(ctx)
28
+ @standard_reporter = StandardReporter.new(ctx)
29
+ @reporters = [@error_reporter, @standard_reporter]
23
30
 
24
31
  # Queue of `Operation`s waiting to be picked up from a thread for processing.
25
32
  @queue = Queue.new
@@ -30,12 +37,19 @@ module ShopifyCLI
30
37
  # Mutex used to pause all threads when backing-off when hitting API rate limits
31
38
  @backoff_mutex = Mutex.new
32
39
 
33
- # Allows delaying log of errors, mainly to not break the progress bar.
34
- @delay_errors = false
35
- @delayed_errors = []
36
-
37
40
  # Latest theme assets checksums. Updated on each upload.
38
41
  @checksums = {}
42
+
43
+ # Checksums of assets with errors.
44
+ @error_checksums = []
45
+ end
46
+
47
+ def lock_io!
48
+ @reporters.each { |reporter| reporter.disable! }
49
+ end
50
+
51
+ def unlock_io!
52
+ @reporters.each { |reporter| reporter.enable! }
39
53
  end
40
54
 
41
55
  def enqueue_updates(files)
@@ -103,25 +117,14 @@ module ShopifyCLI
103
117
  break if operation.nil? # shutdown was called
104
118
  perform(operation)
105
119
  rescue Exception => e
106
- report_error(
107
- "{{red:ERROR}} {{blue:#{operation}}}: #{e}" +
108
- (@ctx.debug? ? "\n\t#{e.backtrace.join("\n\t")}" : "")
109
- )
120
+ error_suffix = ": #{e}"
121
+ error_suffix += + "\n\t#{e.backtrace.join("\n\t")}" if @ctx.debug?
122
+ report_error(operation, error_suffix)
110
123
  end
111
124
  end
112
125
  end
113
126
  end
114
127
 
115
- def delay_errors!
116
- @delay_errors = true
117
- end
118
-
119
- def report_errors!
120
- @delay_errors = false
121
- @delayed_errors.each { |error| report_error(error) }
122
- @delayed_errors.clear
123
- end
124
-
125
128
  def upload_theme!(delay_low_priority_files: false, delete: true, &block)
126
129
  fetch_checksums!
127
130
 
@@ -177,21 +180,27 @@ module ShopifyCLI
177
180
 
178
181
  private
179
182
 
183
+ def report_error(operation, error_suffix = "")
184
+ @error_checksums << @checksums[operation.file_path]
185
+ @error_reporter.report("#{operation.as_error_message}#{error_suffix}")
186
+ end
187
+
180
188
  def enqueue(method, file)
181
189
  raise ArgumentError, "file required" unless file
182
190
 
183
- operation = Operation.new(method, @theme[file])
191
+ operation = Operation.new(@ctx, method, @theme[file])
184
192
 
185
193
  # Already enqueued
186
194
  return if @pending.include?(operation)
187
195
 
188
- if @ignore_filter&.ignore?(operation.file.relative_path)
189
- @ctx.debug("ignore #{operation.file.relative_path}")
196
+ if @ignore_filter&.ignore?(operation.file_path)
197
+ @ctx.debug("ignore #{operation.file_path}")
190
198
  return
191
199
  end
192
200
 
193
201
  if [:update, :get].include?(method) && operation.file.exist? && !file_has_changed?(operation.file)
194
- @ctx.debug("skip #{operation}")
202
+ is_fixed = !!@error_checksums.delete(operation.file.checksum)
203
+ @standard_reporter.report(operation.as_fix_message) if is_fixed
195
204
  return
196
205
  end
197
206
 
@@ -206,16 +215,16 @@ module ShopifyCLI
206
215
 
207
216
  response = send(operation.method, operation.file)
208
217
 
218
+ @standard_reporter.report(operation.as_synced_message)
219
+
209
220
  # Check if the API told us we're near the rate limit
210
221
  if !backingoff? && (limit = response["x-shopify-shop-api-call-limit"])
211
222
  used, total = limit.split("/").map(&:to_i)
212
223
  backoff_if_near_limit!(used, total)
213
224
  end
214
225
  rescue ShopifyCLI::API::APIRequestError => e
215
- report_error(
216
- "{{red:ERROR}} {{blue:#{operation}}}:\n " +
217
- parse_api_errors(e).join("\n ")
218
- )
226
+ error_suffix = ":\n " + parse_api_errors(e).join("\n ")
227
+ report_error(operation, error_suffix)
219
228
  ensure
220
229
  @pending.delete(operation)
221
230
  end
@@ -295,14 +304,6 @@ module ShopifyCLI
295
304
  file.checksum != @checksums[file.relative_path.to_s]
296
305
  end
297
306
 
298
- def report_error(error)
299
- if @delay_errors
300
- @delayed_errors << error
301
- else
302
- @ctx.puts(error)
303
- end
304
- end
305
-
306
307
  def parse_api_errors(exception)
307
308
  parsed_body = JSON.parse(exception&.response&.body)
308
309
  message = parsed_body.dig("errors", "asset") || parsed_body["message"] || exception.message
@@ -162,26 +162,38 @@ module ShopifyCLI
162
162
  }
163
163
  end
164
164
 
165
- def self.all(ctx, root: nil)
166
- _status, body = AdminAPI.rest_request(
167
- ctx,
168
- shop: AdminAPI.get_shop_or_abort(ctx),
169
- path: "themes.json",
170
- api_version: "unstable",
171
- )
165
+ class << self
166
+ def all(ctx, root: nil)
167
+ _status, body = fetch_themes(ctx)
168
+
169
+ body["themes"]
170
+ .sort_by { |theme_attrs| Time.parse(theme_attrs["updated_at"]) }
171
+ .reverse
172
+ .map { |theme_attrs| new(ctx, root: root, **allowed_attrs(theme_attrs)) }
173
+ end
174
+
175
+ def live(ctx, root: nil)
176
+ _status, body = fetch_themes(ctx)
172
177
 
173
- body["themes"]
174
- .sort_by { |attributes| Time.parse(attributes["updated_at"]) }
175
- .reverse
176
- .map do |attributes|
177
- new(
178
- ctx,
179
- root: root,
180
- id: attributes["id"],
181
- name: attributes["name"],
182
- role: attributes["role"],
183
- )
184
- end
178
+ body["themes"]
179
+ .find { |theme_attrs| theme_attrs["role"] == "main" }
180
+ .tap { |theme_attrs| break new(ctx, root: root, **allowed_attrs(theme_attrs)) }
181
+ end
182
+
183
+ private
184
+
185
+ def allowed_attrs(attrs)
186
+ attrs.slice("id", "name", "role").transform_keys(&:to_sym)
187
+ end
188
+
189
+ def fetch_themes(ctx)
190
+ AdminAPI.rest_request(
191
+ ctx,
192
+ shop: AdminAPI.get_shop_or_abort(ctx),
193
+ path: "themes.json",
194
+ api_version: "unstable",
195
+ )
196
+ end
185
197
  end
186
198
 
187
199
  private
@@ -12,7 +12,7 @@ module ShopifyCLI
12
12
  class Tunnel
13
13
  extend SingleForwardable
14
14
 
15
- def_delegators :new, :start, :stop, :auth, :stats, :urls, :running_on?
15
+ def_delegators :new, :start, :stop, :auth, :authenticated?, :stats, :urls, :running_on?
16
16
 
17
17
  class FetchUrlError < RuntimeError; end
18
18
  class NgrokError < RuntimeError; end
@@ -21,6 +21,7 @@ module ShopifyCLI
21
21
  # mapping for supported operating systems for where to download ngrok from.
22
22
  DOWNLOAD_URLS = {
23
23
  mac: "https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-amd64.zip",
24
+ mac_m1: "https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-arm64.zip",
24
25
  linux: "https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-linux-amd64.zip",
25
26
  windows: "https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-windows-amd64.zip",
26
27
  }
@@ -65,16 +66,12 @@ module ShopifyCLI
65
66
  #
66
67
  def start(ctx, port: PORT)
67
68
  install(ctx)
68
- url, account, seconds_remaining = start_ngrok(ctx, port)
69
- if account
69
+ if authenticated?
70
+ url, account = start_ngrok(ctx, port)
70
71
  ctx.puts(ctx.message("core.tunnel.start_with_account", url, account))
71
72
  else
72
- if seconds_remaining <= 0
73
- ctx.puts(ctx.message("core.tunnel.timed_out"))
74
- url, _account, seconds_remaining = restart_ngrok(ctx, port)
75
- end
73
+ url, _ = restart_ngrok(ctx, port)
76
74
  ctx.puts(ctx.message("core.tunnel.start", url))
77
- ctx.puts(ctx.message("core.tunnel.will_timeout", seconds_to_hm(seconds_remaining)))
78
75
  ctx.puts(ctx.message("core.tunnel.signup_suggestion", ShopifyCLI::TOOL_NAME))
79
76
  end
80
77
  url
@@ -91,7 +88,16 @@ module ShopifyCLI
91
88
  #
92
89
  def auth(ctx, token)
93
90
  install(ctx)
94
- ctx.system(File.join(ShopifyCLI.cache_dir, "ngrok"), "authtoken", token)
91
+ ctx.system(ngrok_path(ctx), "authtoken", token)
92
+ end
93
+
94
+ ##
95
+ # returns a boolean: if the user has a ngrok token to authenticate
96
+ #
97
+ def authenticated?
98
+ ngrok_config_path = File.join(Dir.home, ".ngrok2/ngrok.yml")
99
+ return false unless File.exist?(ngrok_config_path)
100
+ File.read(ngrok_config_path).include?("authtoken")
95
101
  end
96
102
 
97
103
  ##
@@ -145,7 +151,7 @@ module ShopifyCLI
145
151
 
146
152
  def install(ctx)
147
153
  ngrok = "ngrok#{ctx.executable_file_extension}"
148
- return if File.exist?(File.join(ShopifyCLI.cache_dir, ngrok))
154
+ return if File.exist?(ngrok_path(ctx))
149
155
  check_prereq_command(ctx, "curl")
150
156
  check_prereq_command(ctx, ctx.linux? ? "unzip" : "tar")
151
157
  spinner = CLI::UI::SpinGroup.new
@@ -165,7 +171,7 @@ module ShopifyCLI
165
171
  spinner.wait
166
172
 
167
173
  # final check to see if ngrok is accessible
168
- unless File.exist?(File.join(ShopifyCLI.cache_dir, ngrok))
174
+ unless File.exist?(ngrok_path(ctx))
169
175
  ctx.abort(ctx.message("core.tunnel.error.ngrok", ngrok, ShopifyCLI.cache_dir))
170
176
  end
171
177
  end
@@ -177,8 +183,9 @@ module ShopifyCLI
177
183
  raise e.class, e.message
178
184
  end
179
185
 
180
- def ngrok_command(port)
181
- "\"#{File.join(ShopifyCLI.cache_dir, "ngrok")}\" http -inspect=false -log=stdout -log-level=debug #{port}"
186
+ def ngrok_path(ctx)
187
+ ngrok = "ngrok#{ctx.executable_file_extension}"
188
+ File.join(ShopifyCLI.cache_dir, ngrok)
182
189
  end
183
190
 
184
191
  def seconds_to_hm(seconds)
@@ -186,16 +193,14 @@ module ShopifyCLI
186
193
  end
187
194
 
188
195
  def start_ngrok(ctx, port)
189
- process = ShopifyCLI::ProcessSupervision.start(:ngrok, ngrok_command(port))
196
+ ngrok_command = "\"#{ngrok_path(ctx)}\" http -inspect=false -log=stdout -log-level=debug #{port}"
197
+ process = ShopifyCLI::ProcessSupervision.start(:ngrok, ngrok_command)
190
198
  log = fetch_url(ctx, process.log_path)
191
- seconds_remaining = (process.time.to_i + log.timeout) - Time.now.to_i
192
- [log.url, log.account, seconds_remaining]
199
+ [log.url, log.account]
193
200
  end
194
201
 
195
202
  def restart_ngrok(ctx, port)
196
- unless ShopifyCLI::ProcessSupervision.stop(:ngrok)
197
- ctx.abort(ctx.message("core.tunnel.error.stop"))
198
- end
203
+ ShopifyCLI::ProcessSupervision.stop(:ngrok)
199
204
  start_ngrok(ctx, port)
200
205
  end
201
206
 
@@ -208,7 +213,7 @@ module ShopifyCLI
208
213
  class LogParser # :nodoc:
209
214
  TIMEOUT = 10
210
215
 
211
- attr_reader :url, :account, :timeout
216
+ attr_reader :url, :account
212
217
 
213
218
  def initialize(log_path)
214
219
  @log_path = log_path
@@ -237,9 +242,8 @@ module ShopifyCLI
237
242
  end
238
243
 
239
244
  def parse_account
240
- account, timeout, _ = @log.match(/AccountName:(.*)\s+SessionDuration:([\d]+) PlanName/)&.captures
245
+ account, _ = @log.match(/AccountName:(.*)\s+SessionDuration/)&.captures
241
246
  @account = account&.empty? ? nil : account
242
- @timeout = timeout&.empty? ? 0 : timeout.to_i
243
247
  end
244
248
 
245
249
  def error
@@ -1,3 +1,3 @@
1
1
  module ShopifyCLI
2
- VERSION = "2.6.6"
2
+ VERSION = "2.7.3"
3
3
  end
data/lib/shopify_cli.rb CHANGED
@@ -111,6 +111,7 @@ module ShopifyCLI
111
111
  autoload :Feature, "shopify_cli/feature"
112
112
  autoload :Form, "shopify_cli/form"
113
113
  autoload :Git, "shopify_cli/git"
114
+ autoload :GitHub, "shopify_cli/github"
114
115
  autoload :Helpers, "shopify_cli/helpers"
115
116
  autoload :Heroku, "shopify_cli/heroku"
116
117
  autoload :IdentityAuth, "shopify_cli/identity_auth"
@@ -123,7 +124,6 @@ module ShopifyCLI
123
124
  autoload :PartnersAPI, "shopify_cli/partners_api"
124
125
  autoload :ProcessSupervision, "shopify_cli/process_supervision"
125
126
  autoload :Project, "shopify_cli/project"
126
- autoload :ProjectCommands, "shopify_cli/project_commands"
127
127
  autoload :ProjectType, "shopify_cli/project_type"
128
128
  autoload :ReportingConfigurationController, "shopify_cli/reporting_configuration_controller"
129
129
  autoload :ResolveConstant, "shopify_cli/resolve_constant"
@@ -131,7 +131,6 @@ module ShopifyCLI
131
131
  autoload :Result, "shopify_cli/result"
132
132
  autoload :Services, "shopify_cli/services"
133
133
  autoload :Shopifolk, "shopify_cli/shopifolk"
134
- autoload :SubCommand, "shopify_cli/sub_command"
135
134
  autoload :Task, "shopify_cli/task"
136
135
  autoload :Tasks, "shopify_cli/tasks"
137
136
  autoload :TransformDataStructure, "shopify_cli/transform_data_structure"
data/shopify-cli.gemspec CHANGED
@@ -41,5 +41,5 @@ Gem::Specification.new do |spec|
41
41
 
42
42
  spec.add_dependency("bugsnag", "~> 6.22")
43
43
  spec.add_dependency("listen", "~> 3.7.0")
44
- spec.add_dependency("theme-check", "~> 1.7.2")
44
+ spec.add_dependency("theme-check", "~> 1.9.0")
45
45
  end
data/shopify-dev ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative "./bin/load_shopify"
4
+
5
+ exit(proc do
6
+ begin
7
+ ShopifyCLI::ErrorHandler.call do
8
+ ShopifyCLI::Core::EntryPoint.call(ARGV.dup)
9
+ end
10
+ rescue StandardError => error
11
+ ShopifyCLI::ErrorHandler.exception = error
12
+ if ShopifyCLI::Environment.print_stacktrace?
13
+ raise error
14
+ else
15
+ 1
16
+ end
17
+ end
18
+ end.call)
@@ -0,0 +1,7 @@
1
+ module Utilities
2
+ module Constants
3
+ module Paths
4
+ ROOT = File.expand_path("..", __dir__)
5
+ end
6
+ end
7
+ end
@@ -79,12 +79,19 @@ module Utilities
79
79
  if ARGV.include?("--verbose")
80
80
  stat = Open3.popen3(*command) do |stdin, stdout, stderr, wait_thread|
81
81
  Thread.new do
82
- stdout.each { |l| STDOUT.puts("#{docker_prefix.colorize(:cyan).bold} #{l}") }
83
- stderr.each { |l| STDERR.puts("#{docker_prefix.colorize(:red).bold} #{l}") }
82
+ stdout.each { |l| STDOUT.puts("#{docker_prefix.colorize(:cyan).bold} #{l}") } unless stdout&.nil?
83
+ end
84
+ Thread.new do
85
+ stderr.each { |l| STDERR.puts("#{docker_prefix.colorize(:red).bold} #{l}") } unless stderr&.nil?
84
86
  end
85
87
  stdin.close
86
88
 
87
- wait_thread.value
89
+ status = wait_thread.value
90
+
91
+ stdout.close
92
+ stderr.close
93
+
94
+ status
88
95
  end
89
96
  raise StandardError, "The command #{args.first} failed" unless stat.success?
90
97
  else