shopify-cli 2.6.0 → 2.6.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +15 -4
  3. data/.github/workflows/shopify.yml +3 -6
  4. data/CHANGELOG.md +77 -94
  5. data/Dockerfile +12 -2
  6. data/Gemfile +1 -0
  7. data/Gemfile.lock +5 -3
  8. data/README.md +1 -1
  9. data/RELEASING.md +17 -30
  10. data/Rakefile +0 -5
  11. data/bin/shopify +1 -0
  12. data/ext/shopify-cli/extconf.rb +0 -1
  13. data/lib/project_types/extension/features/argo.rb +8 -2
  14. data/lib/project_types/extension/models/specification_handlers/checkout_post_purchase.rb +1 -1
  15. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +1 -1
  16. data/lib/project_types/node/commands/serve.rb +7 -16
  17. data/lib/project_types/node/messages/messages.rb +0 -5
  18. data/lib/project_types/php/commands/serve.rb +6 -9
  19. data/lib/project_types/php/messages/messages.rb +1 -4
  20. data/lib/project_types/rails/commands/serve.rb +7 -8
  21. data/lib/project_types/rails/messages/messages.rb +1 -4
  22. data/lib/project_types/script/commands/create.rb +3 -1
  23. data/lib/project_types/script/config/extension_points.yml +7 -0
  24. data/lib/project_types/script/graphql/app_script_set.graphql +2 -0
  25. data/lib/project_types/script/layers/application/build_script.rb +2 -1
  26. data/lib/project_types/script/layers/application/push_script.rb +15 -1
  27. data/lib/project_types/script/layers/domain/push_package.rb +5 -2
  28. data/lib/project_types/script/layers/infrastructure/errors.rb +17 -0
  29. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +10 -13
  30. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +4 -13
  31. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +4 -2
  32. data/lib/project_types/script/layers/infrastructure/script_service.rb +6 -1
  33. data/lib/project_types/script/messages/messages.rb +6 -0
  34. data/lib/project_types/script/ui/error_handler.rb +16 -0
  35. data/lib/project_types/theme/commands/serve.rb +1 -0
  36. data/lib/project_types/theme/messages/messages.rb +5 -0
  37. data/lib/shopify_cli/api.rb +4 -0
  38. data/lib/shopify_cli/app_type_detector.rb +32 -0
  39. data/lib/shopify_cli/command.rb +6 -1
  40. data/lib/shopify_cli/command_options/command_serve_options.rb +43 -0
  41. data/lib/shopify_cli/command_options.rb +7 -0
  42. data/lib/shopify_cli/commands/login.rb +3 -3
  43. data/lib/shopify_cli/commands/reporting.rb +38 -0
  44. data/lib/shopify_cli/commands/switch.rb +1 -1
  45. data/lib/shopify_cli/commands.rb +1 -0
  46. data/lib/shopify_cli/constants.rb +7 -3
  47. data/lib/shopify_cli/core/monorail.rb +9 -20
  48. data/lib/shopify_cli/environment.rb +15 -1
  49. data/lib/shopify_cli/exception_reporter.rb +25 -14
  50. data/lib/shopify_cli/messages/messages.rb +48 -19
  51. data/lib/shopify_cli/migrator/migration.rb +1 -1
  52. data/lib/shopify_cli/migrator/migrations/1631709766_noop.rb +1 -1
  53. data/lib/shopify_cli/migrator/migrations/1633691650_merge_reporting_configuration.rb +41 -0
  54. data/lib/shopify_cli/reporting_configuration_controller.rb +64 -0
  55. data/lib/shopify_cli/services/base_service.rb +13 -0
  56. data/lib/shopify_cli/services/reporting_service.rb +16 -0
  57. data/lib/shopify_cli/services.rb +6 -0
  58. data/lib/shopify_cli/theme/dev_server/watcher.rb +2 -2
  59. data/lib/shopify_cli/theme/dev_server.rb +2 -2
  60. data/lib/shopify_cli/version.rb +1 -1
  61. data/lib/shopify_cli.rb +4 -0
  62. data/shopify-cli.gemspec +1 -8
  63. data/utilities/docker/container.rb +76 -0
  64. data/utilities/docker.rb +44 -3
  65. metadata +14 -5
  66. data/lib/shopify_cli/exception_reporter/permission_controller.rb +0 -54
@@ -2,20 +2,17 @@
2
2
  module PHP
3
3
  class Command
4
4
  class Serve < ShopifyCLI::SubCommand
5
- PORT = 3000
5
+ include ShopifyCLI::CommandOptions::CommandServeOptions
6
6
 
7
7
  prerequisite_task :ensure_env, :ensure_dev_store
8
8
 
9
- options do |parser, flags|
10
- parser.on("--host=HOST") do |h|
11
- flags[:host] = h.gsub('"', "")
12
- end
13
- end
9
+ parse_host_option
10
+ parse_port_option
14
11
 
15
12
  def call(*)
16
13
  project = ShopifyCLI::Project.current
17
- url = options.flags[:host] || ShopifyCLI::Tunnel.start(@ctx, port: PORT)
18
- @ctx.abort(@ctx.message("php.serve.error.host_must_be_https")) if url.match(/^https/i).nil?
14
+ tunnel_port = port.to_s
15
+ url = host || ShopifyCLI::Tunnel.start(@ctx, port: tunnel_port)
19
16
  project.env.update(@ctx, :host, url)
20
17
  ShopifyCLI::Tasks::UpdateDashboardURLS.call(
21
18
  @ctx,
@@ -35,7 +32,7 @@ module PHP
35
32
  ShopifyCLI::ProcessSupervision.start(:npm_watch, "npm run watch", force_spawn: true)
36
33
 
37
34
  env = project.env.to_h
38
- @ctx.system("php", "artisan", "serve", "--port", PORT.to_s, env: env)
35
+ @ctx.system("php", "artisan", "serve", "--port", tunnel_port, env: env)
39
36
  end
40
37
  end
41
38
 
@@ -133,12 +133,9 @@ module PHP
133
133
  extended_help: <<~HELP,
134
134
  {{bold:Options:}}
135
135
  {{cyan:--host=HOST}}: Bypass running tunnel and use custom host. HOST must be HTTPS url.
136
+ {{cyan:--port=PORT}}: Use custom port.
136
137
  HELP
137
138
 
138
- error: {
139
- host_must_be_https: "HOST must be a HTTPS url.",
140
- },
141
-
142
139
  open_info: <<~MESSAGE,
143
140
  {{*}} To install and start using your app, open this URL in your browser:
144
141
  {{green:%s}}
@@ -2,19 +2,18 @@
2
2
  module Rails
3
3
  class Command
4
4
  class Serve < ShopifyCLI::SubCommand
5
+ include ShopifyCLI::CommandOptions::CommandServeOptions
6
+
5
7
  prerequisite_task ensure_project_type: :rails
6
8
  prerequisite_task :ensure_env, :ensure_dev_store
7
9
 
8
- options do |parser, flags|
9
- parser.on("--host=HOST") do |h|
10
- flags[:host] = h.gsub('"', "")
11
- end
12
- end
10
+ parse_host_option
11
+ parse_port_option
13
12
 
14
13
  def call(*)
15
14
  project = ShopifyCLI::Project.current
16
- url = options.flags[:host] || ShopifyCLI::Tunnel.start(@ctx)
17
- @ctx.abort(@ctx.message("rails.serve.error.host_must_be_https")) if url.match(/^https/i).nil?
15
+ tunnel_port = port.to_s
16
+ url = host || ShopifyCLI::Tunnel.start(@ctx, port: tunnel_port)
18
17
  project.env.update(@ctx, :host, url)
19
18
  ShopifyCLI::Tasks::UpdateDashboardURLS.call(
20
19
  @ctx,
@@ -30,7 +29,7 @@ module Rails
30
29
  CLI::UI::Frame.open(@ctx.message("rails.serve.running_server")) do
31
30
  env = ShopifyCLI::Project.current.env.to_h
32
31
  env.delete("HOST")
33
- env["PORT"] = ShopifyCLI::Tunnel::PORT.to_s
32
+ env["PORT"] = tunnel_port
34
33
  env["GEM_PATH"] = Gem.gem_path(@ctx)
35
34
  if @ctx.windows?
36
35
  @ctx.system("ruby bin\\rails server", env: env)
@@ -170,12 +170,9 @@ module Rails
170
170
  extended_help: <<~HELP,
171
171
  {{bold:Options:}}
172
172
  {{cyan:--host=HOST}}: Bypass running tunnel and use custom host. HOST must be HTTPS url.
173
+ {{cyan:--port=PORT}}: Use custom port.
173
174
  HELP
174
175
 
175
- error: {
176
- host_must_be_https: "{{red:HOST must be a HTTPS url.}}",
177
- },
178
-
179
176
  open_info: <<~MESSAGE,
180
177
  {{*}} To install and start using your app, open this URL in your browser:
181
178
  {{green:%s}}
@@ -3,7 +3,9 @@
3
3
  module Script
4
4
  class Command
5
5
  class Create < ShopifyCLI::SubCommand
6
- prerequisite_task :ensure_authenticated
6
+ unless ShopifyCLI::Environment.acceptance_test?
7
+ prerequisite_task :ensure_authenticated
8
+ end
7
9
 
8
10
  options do |parser, flags|
9
11
  parser.on("--name=NAME") { |name| flags[:name] = name }
@@ -42,3 +42,10 @@ shipping_methods:
42
42
  typescript:
43
43
  beta: true
44
44
  repo: "https://github.com/Shopify/scripts-apis-examples"
45
+ discount_types:
46
+ beta: true
47
+ domain: 'discounts'
48
+ libraries:
49
+ typescript:
50
+ beta: true
51
+ repo: "https://github.com/Shopify/scripts-apis-examples"
@@ -10,6 +10,7 @@ mutation AppScriptSet(
10
10
  $configurationUi: Boolean!,
11
11
  $configurationDefinition: String!,
12
12
  $moduleUploadUrl: String!,
13
+ $library: LibraryInput,
13
14
  ) {
14
15
  appScriptSet(
15
16
  uuid: $uuid
@@ -23,6 +24,7 @@ mutation AppScriptSet(
23
24
  configurationUi: $configurationUi,
24
25
  configurationDefinition: $configurationDefinition,
25
26
  moduleUploadUrl: $moduleUploadUrl,
27
+ library: $library,
26
28
  ) {
27
29
  userErrors {
28
30
  field
@@ -5,7 +5,7 @@ module Script
5
5
  module Application
6
6
  class BuildScript
7
7
  class << self
8
- def call(ctx:, task_runner:, script_project:)
8
+ def call(ctx:, task_runner:, script_project:, library:)
9
9
  CLI::UI::Frame.open(ctx.message("script.application.building")) do
10
10
  begin
11
11
  UI::StrictSpinner.spin(ctx.message("script.application.building_script")) do |spinner|
@@ -14,6 +14,7 @@ module Script
14
14
  script_content: task_runner.build,
15
15
  compiled_type: task_runner.compiled_type,
16
16
  metadata: task_runner.metadata,
17
+ library: library,
17
18
  )
18
19
  spinner.update_title(ctx.message("script.application.built"))
19
20
  end
@@ -11,14 +11,27 @@ module Script
11
11
  task_runner = Infrastructure::Languages::TaskRunner
12
12
  .for(ctx, script_project.language, script_project.script_name)
13
13
 
14
+ extension_point = ExtensionPoints.get(type: script_project.extension_point_type)
15
+ library_name = extension_point.libraries.for(script_project.language)&.package
16
+ raise Infrastructure::Errors::LanguageLibraryForAPINotFoundError.new(
17
+ language: script_project.language,
18
+ api: script_project.extension_point_type
19
+ ) unless library_name
20
+
21
+ library = {
22
+ language: script_project.language,
23
+ version: task_runner.library_version(library_name),
24
+ }
25
+
14
26
  ProjectDependencies.install(ctx: ctx, task_runner: task_runner)
15
- BuildScript.call(ctx: ctx, task_runner: task_runner, script_project: script_project)
27
+ BuildScript.call(ctx: ctx, task_runner: task_runner, script_project: script_project, library: library)
16
28
 
17
29
  UI::PrintingSpinner.spin(ctx, ctx.message("script.application.pushing")) do |p_ctx, spinner|
18
30
  package = Infrastructure::PushPackageRepository.new(ctx: p_ctx).get_push_package(
19
31
  script_project: script_project,
20
32
  compiled_type: task_runner.compiled_type,
21
33
  metadata: task_runner.metadata,
34
+ library: library,
22
35
  )
23
36
  script_service = Infrastructure::ServiceLocator.script_service(
24
37
  ctx: p_ctx,
@@ -32,6 +45,7 @@ module Script
32
45
  metadata: package.metadata,
33
46
  script_json: package.script_json,
34
47
  module_upload_url: module_upload_url,
48
+ library: package.library,
35
49
  )
36
50
  script_project_repo.update_env(uuid: uuid)
37
51
  spinner.update_title(p_ctx.message("script.application.pushed"))
@@ -10,7 +10,8 @@ module Script
10
10
  :script_json,
11
11
  :script_content,
12
12
  :compiled_type,
13
- :metadata
13
+ :metadata,
14
+ :library
14
15
 
15
16
  def initialize(
16
17
  id:,
@@ -19,7 +20,8 @@ module Script
19
20
  script_content:,
20
21
  compiled_type: nil,
21
22
  metadata:,
22
- script_json:
23
+ script_json:,
24
+ library:
23
25
  )
24
26
  @id = id
25
27
  @uuid = uuid
@@ -28,6 +30,7 @@ module Script
28
30
  @compiled_type = compiled_type
29
31
  @metadata = metadata
30
32
  @script_json = script_json
33
+ @library = library
31
34
  end
32
35
  end
33
36
  end
@@ -40,6 +40,23 @@ module Script
40
40
  end
41
41
  end
42
42
 
43
+ class APILibraryNotFoundError < ScriptProjectError
44
+ attr_reader :library_name
45
+ def initialize(library_name)
46
+ super()
47
+ @library_name = library_name
48
+ end
49
+ end
50
+
51
+ class LanguageLibraryForAPINotFoundError < ScriptProjectError
52
+ attr_reader :language, :api
53
+ def initialize(language:, api:)
54
+ super()
55
+ @language = language
56
+ @api = api
57
+ end
58
+ end
59
+
43
60
  class DependencyInstallError < ScriptProjectError; end
44
61
  class DeprecatedEPError < ScriptProjectError; end
45
62
  class EmptyResponseError < ScriptProjectError; end
@@ -5,7 +5,7 @@ module Script
5
5
  module Infrastructure
6
6
  module Languages
7
7
  class AssemblyScriptTaskRunner
8
- BYTECODE_FILE = "build/%{name}.wasm"
8
+ BYTECODE_FILE = "build/script.wasm"
9
9
  METADATA_FILE = "build/metadata.json"
10
10
  SCRIPT_SDK_BUILD = "npm run build"
11
11
 
@@ -47,6 +47,12 @@ module Script
47
47
  Domain::Metadata.create_from_json(@ctx, raw_contents)
48
48
  end
49
49
 
50
+ def library_version(library_name)
51
+ output = JSON.parse(CommandRunner.new(ctx: ctx).call("npm list --json"))
52
+ raise Errors::APILibraryNotFoundError.new(library_name), output unless output["dependencies"][library_name]
53
+ output["dependencies"][library_name]["version"]
54
+ end
55
+
50
56
  private
51
57
 
52
58
  def check_node_version!
@@ -80,19 +86,10 @@ module Script
80
86
  end
81
87
 
82
88
  def bytecode
83
- legacy_filename = format(BYTECODE_FILE, name: script_name)
84
- filename = format(BYTECODE_FILE, name: "script")
85
-
86
- bytecode_file = if ctx.file_exist?(filename)
87
- filename
88
- elsif ctx.file_exist?(legacy_filename)
89
- legacy_filename
90
- else
91
- raise Errors::WebAssemblyBinaryNotFoundError
92
- end
89
+ raise Errors::WebAssemblyBinaryNotFoundError unless ctx.file_exist?(BYTECODE_FILE)
93
90
 
94
- contents = ctx.binread(bytecode_file)
95
- ctx.rm(bytecode_file)
91
+ contents = ctx.binread(BYTECODE_FILE)
92
+ ctx.rm(BYTECODE_FILE)
96
93
 
97
94
  contents
98
95
  end
@@ -5,7 +5,7 @@ module Script
5
5
  module Infrastructure
6
6
  module Languages
7
7
  class TypeScriptTaskRunner
8
- BYTECODE_FILE = "build/%{name}.wasm"
8
+ BYTECODE_FILE = "build/index.wasm"
9
9
  METADATA_FILE = "build/metadata.json"
10
10
  SCRIPT_SDK_BUILD = "npm run build"
11
11
  GEN_METADATA = "npm run gen-metadata"
@@ -82,19 +82,10 @@ module Script
82
82
  end
83
83
 
84
84
  def bytecode
85
- legacy_filename = format(BYTECODE_FILE, name: script_name)
86
- filename = format(BYTECODE_FILE, name: "index")
87
-
88
- bytecode_file = if ctx.file_exist?(filename)
89
- filename
90
- elsif ctx.file_exist?(legacy_filename)
91
- legacy_filename
92
- else
93
- raise Errors::WebAssemblyBinaryNotFoundError
94
- end
85
+ raise Errors::WebAssemblyBinaryNotFoundError unless ctx.file_exist?(BYTECODE_FILE)
95
86
 
96
- contents = ctx.binread(bytecode_file)
97
- ctx.rm(bytecode_file)
87
+ contents = ctx.binread(BYTECODE_FILE)
88
+ ctx.rm(BYTECODE_FILE)
98
89
 
99
90
  contents
100
91
  end
@@ -7,7 +7,7 @@ module Script
7
7
  include SmartProperties
8
8
  property! :ctx, accepts: ShopifyCLI::Context
9
9
 
10
- def create_push_package(script_project:, script_content:, compiled_type:, metadata:)
10
+ def create_push_package(script_project:, script_content:, compiled_type:, metadata:, library:)
11
11
  build_file_path = file_path(script_project.id, compiled_type)
12
12
  write_to_path(build_file_path, script_content)
13
13
 
@@ -19,10 +19,11 @@ module Script
19
19
  compiled_type: compiled_type,
20
20
  metadata: metadata,
21
21
  script_json: script_project.script_json,
22
+ library: library
22
23
  )
23
24
  end
24
25
 
25
- def get_push_package(script_project:, compiled_type:, metadata:)
26
+ def get_push_package(script_project:, compiled_type:, metadata:, library:)
26
27
  build_file_path = file_path(script_project.id, compiled_type)
27
28
  raise Domain::PushPackageNotFoundError unless ctx.file_exist?(build_file_path)
28
29
 
@@ -34,6 +35,7 @@ module Script
34
35
  script_content: script_content,
35
36
  metadata: metadata,
36
37
  script_json: script_project.script_json,
38
+ library: library
37
39
  )
38
40
  end
39
41
 
@@ -18,7 +18,8 @@ module Script
18
18
  force: false,
19
19
  metadata:,
20
20
  script_json:,
21
- module_upload_url:
21
+ module_upload_url:,
22
+ library:
22
23
  )
23
24
  query_name = "app_script_set"
24
25
  variables = {
@@ -33,6 +34,10 @@ module Script
33
34
  configurationUi: script_json.configuration_ui,
34
35
  configurationDefinition: script_json.configuration&.to_json,
35
36
  moduleUploadUrl: module_upload_url,
37
+ library: {
38
+ language: library[:language],
39
+ version: library[:version],
40
+ },
36
41
  }
37
42
  resp_hash = make_request(query_name: query_name, variables: variables)
38
43
  user_errors = resp_hash["data"]["appScriptSet"]["userErrors"]
@@ -148,6 +148,12 @@ module Script
148
148
 
149
149
  script_upload_cause: "Fail to upload script.",
150
150
  script_upload_help: "Try again.",
151
+
152
+ api_library_not_found_cause: "Script can't be created because API library %{library_name} is missing from the dependencies",
153
+ api_library_not_found_help: "This error can occur because the API library was removed from your system or there is a problem with dependencies in the repository.",
154
+
155
+ language_library_for_api_not_found_cause: "Script can’t be pushed because the %{language} library for API %{api} is missing.",
156
+ language_library_for_api_not_found_help: "Make sure extension_point.yml contains the correct API library.",
151
157
  },
152
158
 
153
159
  create: {
@@ -250,6 +250,22 @@ module Script
250
250
  cause_of_error: ShopifyCLI::Context.message("script.error.script_upload_cause"),
251
251
  help_suggestion: ShopifyCLI::Context.message("script.error.script_upload_help"),
252
252
  }
253
+ when Layers::Infrastructure::Errors::APILibraryNotFoundError
254
+ {
255
+ cause_of_error: ShopifyCLI::Context
256
+ .message("script.error.api_library_not_found_cause", library_name: e.library_name),
257
+ help_suggestion: ShopifyCLI::Context.message("script.error.api_library_not_found_help"),
258
+ }
259
+ when Layers::Infrastructure::Errors::LanguageLibraryForAPINotFoundError
260
+ {
261
+ cause_of_error: ShopifyCLI::Context
262
+ .message(
263
+ "script.error.language_library_for_api_not_found_cause",
264
+ language: e.language,
265
+ api: e.api
266
+ ),
267
+ help_suggestion: ShopifyCLI::Context.message("script.error.language_library_for_api_not_found_help"),
268
+ }
253
269
  end
254
270
  end
255
271
  end
@@ -6,6 +6,7 @@ module Theme
6
6
  class Serve < ShopifyCLI::SubCommand
7
7
  options do |parser, flags|
8
8
  parser.on("--port=PORT") { |port| flags[:port] = port.to_i }
9
+ parser.on("--poll") { flags[:poll] = true }
9
10
  end
10
11
 
11
12
  def call(*)
@@ -88,7 +88,12 @@ module Theme
88
88
  serve: {
89
89
  help: <<~HELP,
90
90
  Uploads the current theme as a development theme to the connected store, then prints theme editor and preview URLs to your terminal. While running, changes will push to the store in real time.
91
+
91
92
  Usage: {{command:%s theme serve}}
93
+
94
+ Options:
95
+ {{command:--port=PORT}} Local port to serve theme preview from
96
+ {{command:--poll}} Force polling to detect file changes
92
97
  HELP
93
98
  serve: "Viewing theme…",
94
99
  open_fail: "Couldn't open the theme",
@@ -1,4 +1,5 @@
1
1
  require "shopify_cli"
2
+ require "securerandom"
2
3
 
3
4
  module ShopifyCLI
4
5
  class API
@@ -54,6 +55,7 @@ module ShopifyCLI
54
55
  # we delay this require so as to avoid a performance hit on starting the CLI
55
56
  require "shopify_cli/http_request"
56
57
  headers = default_headers.merge(headers)
58
+ ctx.debug("#{method} #{uri} with X-Request-Id: #{headers["X-Request-Id"]}")
57
59
  response = if method == "POST"
58
60
  HttpRequest.post(uri, body, headers)
59
61
  elsif method == "PUT"
@@ -82,6 +84,7 @@ module ShopifyCLI
82
84
  raise APIRequestUnexpectedError.new("#{response.code}\n#{response.body}", response: response)
83
85
  end
84
86
  rescue Errno::ETIMEDOUT, Timeout::Error
87
+ ctx.debug("timeout in #{method} #{uri} with X-Request-Id: #{headers["X-Request-Id"]}")
85
88
  raise APIRequestTimeoutError.new("Timeout")
86
89
  end.retry_after(APIRequestRetriableError, retries: 3) do |e|
87
90
  sleep(1) if e.is_a?(APIRequestThrottledError)
@@ -109,6 +112,7 @@ module ShopifyCLI
109
112
  "User-Agent" => "Shopify CLI; v=#{ShopifyCLI::VERSION}",
110
113
  "Sec-CH-UA" => "Shopify CLI; v=#{ShopifyCLI::VERSION} sha=#{ShopifyCLI.sha}",
111
114
  "Sec-CH-UA-PLATFORM" => ctx.os.to_s,
115
+ "X-Request-Id" => SecureRandom.uuid,
112
116
  }.tap do |headers|
113
117
  headers["X-Shopify-Cli-Employee"] = "1" if Shopifolk.acting_as_shopify_organization?
114
118
  end.merge(auth_headers(token))
@@ -0,0 +1,32 @@
1
+ require "json"
2
+
3
+ module ShopifyCLI
4
+ class AppTypeDetector
5
+ Error = Class.new(StandardError)
6
+ TypeNotFoundError = Class.new(Error)
7
+
8
+ def self.detect(project_directory:)
9
+ return :node if node?(project_directory: project_directory)
10
+ return :rails if rails?(project_directory: project_directory)
11
+ return :php if php?(project_directory: project_directory)
12
+ raise TypeNotFoundError, "Couldn't detect the project type in directory: #{project_directory}"
13
+ end
14
+
15
+ def self.node?(project_directory:)
16
+ package_json_path = File.join(project_directory, "package.json")
17
+ return false unless File.exist?(package_json_path)
18
+ package_json = JSON.parse(File.read(package_json_path))
19
+ !package_json.dig("scripts", "dev").nil?
20
+ end
21
+
22
+ def self.rails?(project_directory:)
23
+ rails_binstub_path = File.join(project_directory, "bin/rails")
24
+ File.exist?(rails_binstub_path)
25
+ end
26
+
27
+ def self.php?(project_directory:)
28
+ bootstrap_app_path = File.join(project_directory, "bootstrap/app.php")
29
+ File.exist?(bootstrap_app_path)
30
+ end
31
+ end
32
+ end
@@ -27,7 +27,12 @@ module ShopifyCLI
27
27
  end
28
28
 
29
29
  def options(&block)
30
- @_options = block
30
+ existing_options = @_options
31
+ # We prevent new options calls to override existing blocks by nesting them.
32
+ @_options = ->(parser, flags) {
33
+ existing_options&.call(parser, flags)
34
+ block.call(parser, flags)
35
+ }
31
36
  end
32
37
 
33
38
  def subcommand(const, cmd, path = nil)
@@ -0,0 +1,43 @@
1
+ require "shopify_cli"
2
+
3
+ module ShopifyCLI
4
+ module CommandOptions
5
+ module CommandServeOptions
6
+ def self.included(base)
7
+ base.extend(ClassMethods)
8
+ base.class_eval do
9
+ def port
10
+ return ShopifyCLI::Tunnel::PORT.to_s unless options.flags.key?(:port)
11
+ port = options.flags[:port].to_i
12
+ @ctx.abort(@ctx.message("core.app.serve.error.invalid_port", options.flags[:port])) unless port > 0
13
+ port
14
+ end
15
+
16
+ def host
17
+ host = options.flags[:host]
18
+ unless host.nil?
19
+ @ctx.abort(@ctx.message("core.app.serve.error.host_must_be_https")) if host.match(/^https/i).nil?
20
+ end
21
+ host
22
+ end
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+ def parse_host_option
28
+ options do |parser, flags|
29
+ parser.on("--host=HOST") do |h|
30
+ flags[:host] = h.gsub('"', "")
31
+ end
32
+ end
33
+ end
34
+
35
+ def parse_port_option
36
+ options do |parser, flags|
37
+ parser.on("--port=PORT") { |port| flags[:port] = port }
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ require "shopify_cli"
2
+
3
+ module ShopifyCLI
4
+ module CommandOptions
5
+ autoload :CommandServeOptions, "shopify_cli/command_options/command_serve_options"
6
+ end
7
+ end
@@ -15,7 +15,7 @@ module ShopifyCLI
15
15
 
16
16
  def call(*)
17
17
  shop = (options.flags[:shop] || @ctx.getenv("SHOPIFY_SHOP" || nil))
18
- ShopifyCLI::DB.set(shop: self.class.validate_shop(shop)) unless shop.nil?
18
+ ShopifyCLI::DB.set(shop: self.class.validate_shop(shop, context: @ctx)) unless shop.nil?
19
19
 
20
20
  if shop.nil? && Shopifolk.check
21
21
  Shopifolk.reset
@@ -41,9 +41,9 @@ module ShopifyCLI
41
41
  ShopifyCLI::Context.message("core.login.help", ShopifyCLI::TOOL_NAME)
42
42
  end
43
43
 
44
- def self.validate_shop(shop)
44
+ def self.validate_shop(shop, context:)
45
45
  permanent_domain = shop_to_permanent_domain(shop)
46
- @ctx.abort(@ctx.message("core.login.invalid_shop", shop)) unless permanent_domain
46
+ context.abort(context.message("core.login.invalid_shop", shop)) unless permanent_domain
47
47
  permanent_domain
48
48
  end
49
49
 
@@ -0,0 +1,38 @@
1
+ require "shopify_cli"
2
+
3
+ module ShopifyCLI
4
+ module Commands
5
+ class Reporting < ShopifyCLI::Command
6
+ def call(args, _name)
7
+ enable_reporting = reporting_enabled?(args)
8
+ Services::ReportingService.call(enable: enable_reporting)
9
+
10
+ message = if enable_reporting
11
+ @ctx.message("core.reporting.turned_on_message")
12
+ else
13
+ @ctx.message("core.reporting.turned_off_message", ShopifyCLI::TOOL_NAME)
14
+ end
15
+ @ctx.puts(message)
16
+ end
17
+
18
+ def reporting_enabled?(args)
19
+ case args.first
20
+ when nil
21
+ @ctx.abort(@ctx.message("core.reporting.missing_argument", ShopifyCLI::TOOL_NAME))
22
+ when "on"
23
+ true
24
+ when "off"
25
+ false
26
+ else
27
+ @ctx.abort(
28
+ @ctx.message("core.reporting.invalid_argument", ShopifyCLI::TOOL_NAME, args.first)
29
+ )
30
+ end
31
+ end
32
+
33
+ def self.help
34
+ ShopifyCLI::Context.message("core.reporting.help", ShopifyCLI::TOOL_NAME)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -16,7 +16,7 @@ module ShopifyCLI
16
16
  end
17
17
 
18
18
  shop = if options.flags[:shop]
19
- Login.validate_shop(options.flags[:shop])
19
+ Login.validate_shop(options.flags[:shop], context: @ctx)
20
20
  elsif (org_id = DB.get(:organization_id))
21
21
  res = ShopifyCLI::Tasks::SelectOrgAndShop.call(@ctx, organization_id: org_id)
22
22
  res[:shop_domain]
@@ -23,6 +23,7 @@ module ShopifyCLI
23
23
  register :Login, "login", "shopify_cli/commands/login", true
24
24
  register :Logout, "logout", "shopify_cli/commands/logout", true
25
25
  register :Populate, "populate", "shopify_cli/commands/populate", true
26
+ register :Reporting, "reporting", "shopify_cli/commands/reporting", true
26
27
  register :Store, "store", "shopify_cli/commands/store", true
27
28
  register :Switch, "switch", "shopify_cli/commands/switch", true
28
29
  register :System, "system", "shopify_cli/commands/system", true