shopify-cli 2.6.2 → 2.6.6

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 (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