shopify-cli 2.6.2 → 2.6.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (83) 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 +89 -99
  5. data/CONTRIBUTING.md +9 -1
  6. data/Dockerfile +22 -4
  7. data/Gemfile +2 -0
  8. data/Gemfile.lock +7 -3
  9. data/RELEASING.md +17 -30
  10. data/Rakefile +0 -5
  11. data/lib/project_types/extension/cli.rb +1 -0
  12. data/lib/project_types/extension/commands/create.rb +1 -0
  13. data/lib/project_types/extension/features/argo.rb +9 -10
  14. data/lib/project_types/extension/features/argo_serve.rb +1 -1
  15. data/lib/project_types/extension/forms/create.rb +1 -1
  16. data/lib/project_types/extension/forms/questions/ask_template.rb +2 -1
  17. data/lib/project_types/extension/messages/messages.rb +1 -0
  18. data/lib/project_types/extension/models/server_config/extension.rb +2 -0
  19. data/lib/project_types/extension/models/specification_handlers/checkout_post_purchase.rb +1 -1
  20. data/lib/project_types/extension/models/specification_handlers/checkout_ui_extension.rb +1 -1
  21. data/lib/project_types/extension/tasks/converters/server_config_converter.rb +4 -5
  22. data/lib/project_types/extension/tasks/find_package_from_json.rb +37 -0
  23. data/lib/project_types/extension/tasks/load_server_config.rb +6 -1
  24. data/lib/project_types/node/commands/serve.rb +7 -16
  25. data/lib/project_types/node/messages/messages.rb +0 -5
  26. data/lib/project_types/php/commands/serve.rb +6 -9
  27. data/lib/project_types/php/messages/messages.rb +1 -4
  28. data/lib/project_types/rails/commands/create.rb +45 -16
  29. data/lib/project_types/rails/commands/serve.rb +7 -8
  30. data/lib/project_types/rails/forms/create.rb +0 -1
  31. data/lib/project_types/rails/messages/messages.rb +1 -4
  32. data/lib/project_types/script/commands/create.rb +4 -5
  33. data/lib/project_types/script/config/extension_points.yml +10 -0
  34. data/lib/project_types/script/errors.rb +0 -18
  35. data/lib/project_types/script/graphql/app_script_set.graphql +2 -0
  36. data/lib/project_types/script/layers/application/build_script.rb +2 -1
  37. data/lib/project_types/script/layers/application/create_script.rb +2 -2
  38. data/lib/project_types/script/layers/application/push_script.rb +15 -1
  39. data/lib/project_types/script/layers/domain/push_package.rb +5 -2
  40. data/lib/project_types/script/layers/domain/script_json.rb +1 -1
  41. data/lib/project_types/script/layers/infrastructure/api_clients/partners_proxy_api_client.rb +0 -4
  42. data/lib/project_types/script/layers/infrastructure/errors.rb +17 -2
  43. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +29 -13
  44. data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +29 -13
  45. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +4 -2
  46. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +3 -4
  47. data/lib/project_types/script/layers/infrastructure/script_service.rb +7 -2
  48. data/lib/project_types/script/messages/messages.rb +9 -22
  49. data/lib/project_types/script/ui/error_handler.rb +16 -26
  50. data/lib/project_types/theme/commands/serve.rb +2 -0
  51. data/lib/project_types/theme/messages/messages.rb +6 -0
  52. data/lib/shopify_cli/app_type_detector.rb +32 -0
  53. data/lib/shopify_cli/command.rb +6 -1
  54. data/lib/shopify_cli/command_options/command_serve_options.rb +43 -0
  55. data/lib/shopify_cli/command_options.rb +7 -0
  56. data/lib/shopify_cli/commands/login.rb +3 -3
  57. data/lib/shopify_cli/commands/reporting.rb +38 -0
  58. data/lib/shopify_cli/commands/switch.rb +1 -1
  59. data/lib/shopify_cli/commands.rb +1 -0
  60. data/lib/shopify_cli/constants.rb +7 -3
  61. data/lib/shopify_cli/core/monorail.rb +9 -20
  62. data/lib/shopify_cli/environment.rb +15 -1
  63. data/lib/shopify_cli/exception_reporter.rb +29 -15
  64. data/lib/shopify_cli/messages/messages.rb +48 -19
  65. data/lib/shopify_cli/migrator/migration.rb +1 -1
  66. data/lib/shopify_cli/migrator/migrations/1631709766_noop.rb +1 -1
  67. data/lib/shopify_cli/migrator/migrations/1633691650_merge_reporting_configuration.rb +41 -0
  68. data/lib/shopify_cli/migrator.rb +9 -11
  69. data/lib/shopify_cli/reporting_configuration_controller.rb +64 -0
  70. data/lib/shopify_cli/services/base_service.rb +13 -0
  71. data/lib/shopify_cli/services/reporting_service.rb +16 -0
  72. data/lib/shopify_cli/services.rb +6 -0
  73. data/lib/shopify_cli/theme/dev_server/watcher.rb +2 -2
  74. data/lib/shopify_cli/theme/dev_server.rb +3 -2
  75. data/lib/shopify_cli/version.rb +1 -1
  76. data/lib/shopify_cli.rb +4 -0
  77. data/shopify-cli.gemspec +2 -13
  78. data/utilities/docker/container.rb +97 -0
  79. data/utilities/docker.rb +45 -3
  80. metadata +18 -10
  81. data/ext/shopify-cli/extconf.rb +0 -60
  82. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +0 -0
  83. data/lib/shopify_cli/exception_reporter/permission_controller.rb +0 -54
@@ -4,7 +4,6 @@ module Script
4
4
  module Layers
5
5
  module Infrastructure
6
6
  module Errors
7
- class AppNotInstalledError < ScriptProjectError; end
8
7
  class BuildError < ScriptProjectError; end
9
8
  class ScriptJsonSyntaxError < ScriptProjectError; end
10
9
 
@@ -40,6 +39,23 @@ module Script
40
39
  end
41
40
  end
42
41
 
42
+ class APILibraryNotFoundError < ScriptProjectError
43
+ attr_reader :library_name
44
+ def initialize(library_name)
45
+ super()
46
+ @library_name = library_name
47
+ end
48
+ end
49
+
50
+ class LanguageLibraryForAPINotFoundError < ScriptProjectError
51
+ attr_reader :language, :api
52
+ def initialize(language:, api:)
53
+ super()
54
+ @language = language
55
+ @api = api
56
+ end
57
+ end
58
+
43
59
  class DependencyInstallError < ScriptProjectError; end
44
60
  class DeprecatedEPError < ScriptProjectError; end
45
61
  class EmptyResponseError < ScriptProjectError; end
@@ -84,7 +100,6 @@ module Script
84
100
  end
85
101
 
86
102
  class ScriptProjectAlreadyExistsError < ScriptProjectError; end
87
- class ShopAuthenticationError < ScriptProjectError; end
88
103
  class TaskRunnerNotFoundError < ScriptProjectError; end
89
104
  class BuildScriptNotFoundError < ScriptProjectError; end
90
105
  class InvalidBuildScriptError < 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,8 +47,33 @@ 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 -s list --json"))
52
+ library_version_from_npm_list(output, library_name)
53
+ rescue Errors::SystemCallFailureError => error
54
+ library_version_from_npm_list_error_output(error, library_name)
55
+ end
56
+
50
57
  private
51
58
 
59
+ def library_version_from_npm_list_error_output(error, library_name)
60
+ # npm list can return a failure status code, even when returning the correct data.
61
+ # This causes the CommandRunner to throw a SystemCallFailure error that contains the data.
62
+ # In here, we check that the output contains `npm list`'s structure and extract the version.
63
+ output = JSON.parse(error.out)
64
+ raise error unless output.key?("dependencies")
65
+
66
+ library_version_from_npm_list(output, library_name)
67
+ rescue JSON::ParserError
68
+ raise error
69
+ end
70
+
71
+ def library_version_from_npm_list(output, library_name)
72
+ output.dig("dependencies", library_name, "version").tap do |version|
73
+ raise Errors::APILibraryNotFoundError, library_name unless version
74
+ end
75
+ end
76
+
52
77
  def check_node_version!
53
78
  output, status = @ctx.capture2e("node", "--version")
54
79
  raise Errors::DependencyInstallError, output unless status.success?
@@ -80,19 +105,10 @@ module Script
80
105
  end
81
106
 
82
107
  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
108
+ raise Errors::WebAssemblyBinaryNotFoundError unless ctx.file_exist?(BYTECODE_FILE)
93
109
 
94
- contents = ctx.binread(bytecode_file)
95
- ctx.rm(bytecode_file)
110
+ contents = ctx.binread(BYTECODE_FILE)
111
+ ctx.rm(BYTECODE_FILE)
96
112
 
97
113
  contents
98
114
  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"
@@ -48,8 +48,33 @@ module Script
48
48
  Domain::Metadata.create_from_json(@ctx, raw_contents)
49
49
  end
50
50
 
51
+ def library_version(library_name)
52
+ output = JSON.parse(CommandRunner.new(ctx: ctx).call("npm -s list --json"))
53
+ library_version_from_npm_list(output, library_name)
54
+ rescue Errors::SystemCallFailureError => error
55
+ library_version_from_npm_list_error_output(error, library_name)
56
+ end
57
+
51
58
  private
52
59
 
60
+ def library_version_from_npm_list_error_output(error, library_name)
61
+ # npm list can return a failure status code, even when returning the correct data.
62
+ # This causes the CommandRunner to throw a SystemCallFailure error that contains the data.
63
+ # In here, we check that the output contains `npm list`'s structure and extract the version.
64
+ output = JSON.parse(error.out)
65
+ raise error unless output.key?("dependencies")
66
+
67
+ library_version_from_npm_list(output, library_name)
68
+ rescue JSON::ParserError
69
+ raise error
70
+ end
71
+
72
+ def library_version_from_npm_list(output, library_name)
73
+ output.dig("dependencies", library_name, "version").tap do |version|
74
+ raise Errors::APILibraryNotFoundError, library_name unless version
75
+ end
76
+ end
77
+
53
78
  def check_node_version!
54
79
  output, status = @ctx.capture2e("node", "--version")
55
80
  raise Errors::DependencyInstallError, output unless status.success?
@@ -82,19 +107,10 @@ module Script
82
107
  end
83
108
 
84
109
  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
110
+ raise Errors::WebAssemblyBinaryNotFoundError unless ctx.file_exist?(BYTECODE_FILE)
95
111
 
96
- contents = ctx.binread(bytecode_file)
97
- ctx.rm(bytecode_file)
112
+ contents = ctx.binread(BYTECODE_FILE)
113
+ ctx.rm(BYTECODE_FILE)
98
114
 
99
115
  contents
100
116
  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
 
@@ -81,10 +81,10 @@ module Script
81
81
  )
82
82
  end
83
83
 
84
- def update_or_create_script_json(title:, configuration_ui: false)
84
+ def update_or_create_script_json(title:)
85
85
  script_json = ScriptJsonRepository
86
86
  .new(ctx: ctx)
87
- .update_or_create(title: title, configuration_ui: configuration_ui)
87
+ .update_or_create(title: title)
88
88
 
89
89
  Domain::ScriptProject.new(
90
90
  id: ctx.root,
@@ -148,11 +148,10 @@ module Script
148
148
  current_script_json || raise(Domain::Errors::NoScriptJsonFile)
149
149
  end
150
150
 
151
- def update_or_create(title:, configuration_ui:)
151
+ def update_or_create(title:)
152
152
  json = current_script_json&.content || {}
153
153
  json["version"] ||= "1"
154
154
  json["title"] = title
155
- json["configurationUi"] = !!configuration_ui
156
155
 
157
156
  ctx.write(SCRIPT_JSON_FILENAME, JSON.pretty_generate(json))
158
157
 
@@ -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"]
@@ -41,7 +46,7 @@ module Script
41
46
 
42
47
  if user_errors.any? { |e| e["tag"] == "already_exists_error" }
43
48
  raise Errors::ScriptRepushError, uuid
44
- elsif (e = user_errors.any? { |err| err["tag"] == "configuration_syntax_error" })
49
+ elsif (e = user_errors.any? { |err| err["tag"] == "configuration_definition_syntax_error" })
45
50
  raise Errors::ScriptJsonSyntaxError
46
51
  elsif (e = user_errors.find { |err| err["tag"] == "configuration_definition_missing_keys_error" })
47
52
  raise Errors::ScriptJsonMissingKeysError, e["message"]
@@ -27,10 +27,6 @@ module Script
27
27
  "extension_point_type or script_name.",
28
28
  invalid_context_help: "Add these values and try again.",
29
29
 
30
- invalid_config_props_cause: "{{command:--config-props}} is formatted incorrectly.",
31
- invalid_config_props_help: "Try again using this format: "\
32
- "{{cyan:--config-props='name1:value1, name2:value2'}}",
33
-
34
30
  invalid_script_name_cause: "Invalid script name.",
35
31
  invalid_script_name_help: "Replace or remove unsupported characters. Valid characters "\
36
32
  "are numbers, letters, hyphens, or underscores.",
@@ -42,9 +38,6 @@ module Script
42
38
  no_existing_orgs_cause: "You don't have any partner organizations.",
43
39
  no_existing_orgs_help: "Visit https://partners.shopify.com/ to create a partners account.",
44
40
 
45
- no_existing_stores_cause: "You don't have any stores in your Partner Dashboard.",
46
- no_existing_stores_help: "Visit https://partners.shopify.com/%{organization_id}/stores/ to create one.",
47
-
48
41
  project_exists_cause: "A directory with this same name already exists.",
49
42
  project_exists_help: "Try again and enter a different name for the script.",
50
43
 
@@ -54,9 +47,6 @@ module Script
54
47
  invalid_language_cause: "Invalid language %s.",
55
48
  invalid_language_help: "Allowed values: %s.",
56
49
 
57
- invalid_config: "Can't change the configuration values because %1$s is missing or "\
58
- "it isn't formatted properly.",
59
-
60
50
  missing_script_json_field_cause: "The script.json file is missing the required %s field.",
61
51
  missing_script_json_field_help: "Add the field and try again.",
62
52
 
@@ -91,8 +81,8 @@ module Script
91
81
  system_call_failure_cause: "An error was returned while running {{command:%{cmd}}}.",
92
82
  system_call_failure_help: "Review the following error and try again.\n{{red:%{out}}}",
93
83
 
94
- metadata_validation_cause: "Invalid Script API metadata.",
95
- metadata_validation_help: "Ensure the 'shopify/scripts-toolchain-as' package is up to date.",
84
+ metadata_validation_cause: "The Script API metadata is incorrect.",
85
+ metadata_validation_help: "The 'schemaVersions.major' field contains an unsupported version.",
96
86
 
97
87
  metadata_schema_versions_missing: "Invalid Script metadata:" \
98
88
  " 'schemaVersions' field is missing",
@@ -107,7 +97,6 @@ module Script
107
97
  metadata_not_found_help: "Ensure the 'shopify/scripts-toolchain-as' package is up to date and " \
108
98
  "'package.json' contains a 'scripts/build' entry with a " \
109
99
  "'--metadata build/metadata.json' argument",
110
- app_not_installed_cause: "App not installed on store.",
111
100
 
112
101
  build_error_cause: "Something went wrong while building the script.",
113
102
  build_error_help: "Correct the errors and try again.",
@@ -126,9 +115,6 @@ module Script
126
115
  script_repush_cause: "A version of this script already exists on the app.",
127
116
  script_repush_help: "Use {{cyan:--force}} to replace the existing script.",
128
117
 
129
- shop_auth_cause: "Unable to authenticate with the store.",
130
- shop_auth_help: "Try again.",
131
-
132
118
  invalid_build_script: "The root package.json contains an invalid build command that " \
133
119
  "is needed to compile your script to WebAssembly.",
134
120
  build_script_not_found: "The root package.json is missing the build command that " \
@@ -148,6 +134,12 @@ module Script
148
134
 
149
135
  script_upload_cause: "Fail to upload script.",
150
136
  script_upload_help: "Try again.",
137
+
138
+ api_library_not_found_cause: "Script can't be created because API library %{library_name} is missing from the dependencies",
139
+ 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.",
140
+
141
+ language_library_for_api_not_found_cause: "Script can’t be pushed because the %{language} library for API %{api} is missing.",
142
+ language_library_for_api_not_found_help: "Make sure extension_point.yml contains the correct API library.",
151
143
  },
152
144
 
153
145
  create: {
@@ -156,8 +148,7 @@ module Script
156
148
  Usage: {{command:%1$s script create}}
157
149
  Options:
158
150
  {{command:--name=NAME}} Script project name. Use any string.
159
- {{command:--extension-point=TYPE}} Script API name. Allowed values: %2$s.
160
- {{command:--no-config-ui}} Specify this option when you don’t want your script to render an interface in Shopify admin.
151
+ {{command:--api=TYPE}} Script API name. Allowed values: %2$s.
161
152
  HELP
162
153
 
163
154
  error: {
@@ -206,10 +197,6 @@ module Script
206
197
  built: "Built",
207
198
  pushing: "Pushing",
208
199
  pushed: "Pushed",
209
- disabling: "Disabling",
210
- disabled: "Disabled",
211
- enabling: "Enabling",
212
- enabled: "Enabled",
213
200
  ensure_env: {
214
201
  organization: "Partner organization {{green:%s (%s)}}.",
215
202
  organization_select: "Which partner organization do you want to use?",
@@ -44,15 +44,6 @@ module Script
44
44
  cause_of_error: ShopifyCLI::Context.message("script.error.invalid_context_cause"),
45
45
  help_suggestion: ShopifyCLI::Context.message("script.error.invalid_context_help"),
46
46
  }
47
- when Errors::InvalidConfigProps
48
- {
49
- cause_of_error: ShopifyCLI::Context.message("script.error.invalid_config_props_cause"),
50
- help_suggestion: ShopifyCLI::Context.message("script.error.invalid_config_props_help"),
51
- }
52
- when Errors::InvalidConfigYAMLError
53
- {
54
- cause_of_error: ShopifyCLI::Context.message("script.error.invalid_config", e.config_file),
55
- }
56
47
  when Layers::Infrastructure::Errors::InvalidLanguageError
57
48
  {
58
49
  cause_of_error: ShopifyCLI::Context.message("script.error.invalid_language_cause", e.language),
@@ -76,14 +67,6 @@ module Script
76
67
  cause_of_error: ShopifyCLI::Context.message("script.error.no_existing_orgs_cause"),
77
68
  help_suggestion: ShopifyCLI::Context.message("script.error.no_existing_orgs_help"),
78
69
  }
79
- when Errors::NoExistingStoresError
80
- {
81
- cause_of_error: ShopifyCLI::Context.message("script.error.no_existing_stores_cause"),
82
- help_suggestion: ShopifyCLI::Context.message(
83
- "script.error.no_existing_stores_help",
84
- organization_id: e.organization_id
85
- ),
86
- }
87
70
  when Layers::Infrastructure::Errors::ScriptProjectAlreadyExistsError
88
71
  {
89
72
  cause_of_error: ShopifyCLI::Context.message("script.error.project_exists_cause"),
@@ -135,10 +118,6 @@ module Script
135
118
  cause_of_error: ShopifyCLI::Context.message("script.error.no_script_json_file_cause"),
136
119
  help_suggestion: ShopifyCLI::Context.message("script.error.no_script_json_file_help"),
137
120
  }
138
- when Layers::Infrastructure::Errors::AppNotInstalledError
139
- {
140
- cause_of_error: ShopifyCLI::Context.message("script.error.app_not_installed_cause"),
141
- }
142
121
  when Layers::Infrastructure::Errors::BuildError
143
122
  {
144
123
  cause_of_error: ShopifyCLI::Context.message("script.error.build_error_cause"),
@@ -217,11 +196,6 @@ module Script
217
196
  cause_of_error: ShopifyCLI::Context.message("script.error.script_repush_cause"),
218
197
  help_suggestion: ShopifyCLI::Context.message("script.error.script_repush_help"),
219
198
  }
220
- when Layers::Infrastructure::Errors::ShopAuthenticationError
221
- {
222
- cause_of_error: ShopifyCLI::Context.message("script.error.shop_auth_cause"),
223
- help_suggestion: ShopifyCLI::Context.message("script.error.shop_auth_help"),
224
- }
225
199
  when Layers::Infrastructure::Errors::BuildScriptNotFoundError
226
200
  {
227
201
  cause_of_error: ShopifyCLI::Context.message("script.error.build_script_not_found"),
@@ -250,6 +224,22 @@ module Script
250
224
  cause_of_error: ShopifyCLI::Context.message("script.error.script_upload_cause"),
251
225
  help_suggestion: ShopifyCLI::Context.message("script.error.script_upload_help"),
252
226
  }
227
+ when Layers::Infrastructure::Errors::APILibraryNotFoundError
228
+ {
229
+ cause_of_error: ShopifyCLI::Context
230
+ .message("script.error.api_library_not_found_cause", library_name: e.library_name),
231
+ help_suggestion: ShopifyCLI::Context.message("script.error.api_library_not_found_help"),
232
+ }
233
+ when Layers::Infrastructure::Errors::LanguageLibraryForAPINotFoundError
234
+ {
235
+ cause_of_error: ShopifyCLI::Context
236
+ .message(
237
+ "script.error.language_library_for_api_not_found_cause",
238
+ language: e.language,
239
+ api: e.api
240
+ ),
241
+ help_suggestion: ShopifyCLI::Context.message("script.error.language_library_for_api_not_found_help"),
242
+ }
253
243
  end
254
244
  end
255
245
  end
@@ -5,7 +5,9 @@ module Theme
5
5
  class Command
6
6
  class Serve < ShopifyCLI::SubCommand
7
7
  options do |parser, flags|
8
+ parser.on("--bind=HOST") { |bind| flags[:bind] = bind.to_s }
8
9
  parser.on("--port=PORT") { |port| flags[:port] = port.to_i }
10
+ parser.on("--poll") { flags[:poll] = true }
9
11
  end
10
12
 
11
13
  def call(*)
@@ -88,7 +88,13 @@ 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
97
+ {{command:--bind=HOST}} Set which network interface the web server listens on
92
98
  HELP
93
99
  serve: "Viewing theme…",
94
100
  open_fail: "Couldn't open the theme",
@@ -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
@@ -15,10 +15,10 @@ module ShopifyCLI
15
15
 
16
16
  module Config
17
17
  module Sections
18
- module ErrorTracking
19
- NAME = "error-tracking"
18
+ module Analytics
19
+ NAME = "analytics"
20
20
  module Fields
21
- AUTOMATIC_REPORTING = "automatic-reporting"
21
+ ENABLED = "enabled"
22
22
  end
23
23
  end
24
24
  end
@@ -39,7 +39,11 @@ module ShopifyCLI
39
39
 
40
40
  # Environments
41
41
  TEST = "SHOPIFY_CLI_TEST"
42
+ ACCEPTANCE_TEST = "SHOPIFY_CLI_ACCEPTANCE_TEST"
42
43
  DEVELOPMENT = "SHOPIFY_CLI_DEVELOPMENT"
44
+
45
+ # Monorail
46
+ MONORAIL_REAL_EVENTS = "MONORAIL_REAL_EVENTS"
43
47
  end
44
48
 
45
49
  module Identity