shopify-cli 1.10.0 → 1.14.0

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 +1 -0
  3. data/.github/workflows/release.yml +2 -4
  4. data/CHANGELOG.md +31 -1
  5. data/Gemfile.lock +1 -1
  6. data/lib/project_types/extension/cli.rb +6 -2
  7. data/lib/project_types/extension/commands/serve.rb +69 -1
  8. data/lib/project_types/extension/commands/tunnel.rb +3 -1
  9. data/lib/project_types/extension/extension_project.rb +1 -0
  10. data/lib/project_types/extension/features/argo.rb +15 -24
  11. data/lib/project_types/extension/features/argo_runtime.rb +91 -0
  12. data/lib/project_types/extension/features/argo_serve.rb +35 -27
  13. data/lib/project_types/extension/features/argo_serve_options.rb +42 -0
  14. data/lib/project_types/extension/messages/messages.rb +3 -0
  15. data/lib/project_types/extension/models/npm_package.rb +14 -0
  16. data/lib/project_types/extension/models/specification.rb +1 -0
  17. data/lib/project_types/extension/models/specification_handlers/checkout_argo_extension.rb +18 -0
  18. data/lib/project_types/extension/models/specification_handlers/default.rb +33 -3
  19. data/lib/project_types/extension/tasks/choose_next_available_port.rb +36 -0
  20. data/lib/project_types/extension/tasks/configure_features.rb +2 -0
  21. data/lib/project_types/extension/tasks/find_npm_packages.rb +106 -0
  22. data/lib/project_types/script/cli.rb +14 -13
  23. data/lib/project_types/script/commands/push.rb +8 -3
  24. data/lib/project_types/script/config/extension_points.yml +0 -3
  25. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +9 -3
  26. data/lib/project_types/script/layers/application/create_script.rb +6 -5
  27. data/lib/project_types/script/layers/application/push_script.rb +2 -1
  28. data/lib/project_types/script/layers/domain/errors.rb +6 -13
  29. data/lib/project_types/script/layers/domain/push_package.rb +4 -8
  30. data/lib/project_types/script/layers/domain/script_json.rb +32 -0
  31. data/lib/project_types/script/layers/domain/script_project.rb +1 -1
  32. data/lib/project_types/script/layers/infrastructure/command_runner.rb +19 -0
  33. data/lib/project_types/script/layers/infrastructure/errors.rb +25 -20
  34. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +105 -0
  35. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +103 -0
  36. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +26 -0
  37. data/lib/project_types/script/layers/infrastructure/languages/rust_project_creator.rb +73 -0
  38. data/lib/project_types/script/layers/infrastructure/languages/rust_task_runner.rb +60 -0
  39. data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +21 -0
  40. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +6 -8
  41. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +44 -59
  42. data/lib/project_types/script/layers/infrastructure/script_service.rb +21 -15
  43. data/lib/project_types/script/messages/messages.rb +28 -22
  44. data/lib/project_types/script/tasks/ensure_env.rb +32 -3
  45. data/lib/project_types/script/ui/error_handler.rb +37 -36
  46. data/lib/shopify-cli/admin_api.rb +7 -4
  47. data/lib/shopify-cli/context.rb +13 -0
  48. data/lib/shopify-cli/messages/messages.rb +48 -43
  49. data/lib/shopify-cli/method_object.rb +4 -4
  50. data/lib/shopify-cli/oauth.rb +7 -1
  51. data/lib/shopify-cli/partners_api.rb +7 -4
  52. data/lib/shopify-cli/partners_api/organizations.rb +3 -3
  53. data/lib/shopify-cli/resources/env_file.rb +1 -1
  54. data/lib/shopify-cli/shopifolk.rb +1 -1
  55. data/lib/shopify-cli/tasks/select_org_and_shop.rb +6 -4
  56. data/lib/shopify-cli/tunnel.rb +22 -1
  57. data/lib/shopify-cli/version.rb +1 -1
  58. metadata +17 -11
  59. data/lib/project_types/extension/features/argo_renderer_package.rb +0 -47
  60. data/lib/project_types/script/layers/domain/config_ui.rb +0 -16
  61. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +0 -100
  62. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +0 -95
  63. data/lib/project_types/script/layers/infrastructure/project_creator.rb +0 -24
  64. data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +0 -72
  65. data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +0 -59
  66. data/lib/project_types/script/layers/infrastructure/task_runner.rb +0 -19
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Script
4
+ module Layers
5
+ module Infrastructure
6
+ module Languages
7
+ class ProjectCreator
8
+ PROJECT_CREATORS = {
9
+ "assemblyscript" => AssemblyScriptProjectCreator,
10
+ "rust" => RustProjectCreator,
11
+ }
12
+
13
+ def self.for(ctx, language, extension_point, script_name, path_to_project)
14
+ raise Errors::ProjectCreatorNotFoundError unless PROJECT_CREATORS[language]
15
+ PROJECT_CREATORS[language].new(
16
+ ctx: ctx,
17
+ extension_point: extension_point,
18
+ script_name: script_name,
19
+ path_to_project: path_to_project
20
+ )
21
+ end
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Script
4
+ module Layers
5
+ module Infrastructure
6
+ module Languages
7
+ class RustProjectCreator
8
+ include SmartProperties
9
+ property! :ctx, accepts: ShopifyCli::Context
10
+ property! :extension_point, accepts: Domain::ExtensionPoint
11
+ property! :script_name, accepts: String
12
+ property! :path_to_project, accepts: String
13
+
14
+ ORIGIN_BRANCH = "main"
15
+ SAMPLE_PATH = "default"
16
+
17
+ def setup_dependencies
18
+ git_init
19
+ setup_remote
20
+ setup_sparse_checkout
21
+ pull
22
+ clean
23
+ set_script_name
24
+ end
25
+
26
+ def bootstrap
27
+ end
28
+
29
+ private
30
+
31
+ def command_runner
32
+ @command_runner ||= CommandRunner.new(ctx: ctx)
33
+ end
34
+
35
+ def git_init
36
+ command_runner.call("git init")
37
+ end
38
+
39
+ def setup_remote
40
+ repo = extension_point.sdks.rust.package
41
+ command_runner.call("git remote add -f origin #{repo}")
42
+ end
43
+
44
+ def setup_sparse_checkout
45
+ type = extension_point.type
46
+ command_runner.call("git config core.sparsecheckout true")
47
+ command_runner.call("echo #{type}/#{SAMPLE_PATH} >> .git/info/sparse-checkout")
48
+ end
49
+
50
+ def pull
51
+ command_runner.call("git pull origin #{ORIGIN_BRANCH}")
52
+ end
53
+
54
+ def clean
55
+ type = extension_point.type
56
+ ctx.rm_rf(".git")
57
+ source = File.join(path_to_project, File.join(type, SAMPLE_PATH))
58
+ FileUtils.copy_entry(source, path_to_project)
59
+ ctx.rm_rf(type)
60
+ end
61
+
62
+ def set_script_name
63
+ config_file = "Cargo.toml"
64
+ upstream_name = "#{extension_point.type.gsub("_", "-")}-default"
65
+ contents = File.read(config_file)
66
+ new_contents = contents.sub(upstream_name, script_name)
67
+ File.write(config_file, new_contents)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+ module Script
3
+ module Layers
4
+ module Infrastructure
5
+ module Languages
6
+ class RustTaskRunner
7
+ attr_reader :ctx
8
+
9
+ BUILD_TARGET = "wasm32-unknown-unknown"
10
+ METADATA_FILE = "build/metadata.json"
11
+ CARGO_BUILD_CMD = "cargo build --target=#{BUILD_TARGET} --release"
12
+
13
+ def initialize(ctx, _)
14
+ @ctx = ctx
15
+ end
16
+
17
+ def dependencies_installed?
18
+ true
19
+ end
20
+
21
+ def install_dependencies
22
+ end
23
+
24
+ def build
25
+ compile
26
+ bytecode
27
+ end
28
+
29
+ def compiled_type
30
+ "wasm"
31
+ end
32
+
33
+ def metadata
34
+ unless @ctx.file_exist?(METADATA_FILE)
35
+ msg = @ctx.message("script.error.metadata_not_found_cause", METADATA_FILE)
36
+ raise Domain::Errors::MetadataNotFoundError, msg
37
+ end
38
+
39
+ raw_contents = File.read(METADATA_FILE)
40
+ Domain::Metadata.create_from_json(@ctx, raw_contents)
41
+ end
42
+
43
+ private
44
+
45
+ def compile
46
+ CommandRunner.new(ctx: ctx).call(CARGO_BUILD_CMD)
47
+ end
48
+
49
+ def bytecode
50
+ binary_name = "script.wasm"
51
+ binary_path = "target/#{BUILD_TARGET}/release/#{binary_name}"
52
+ raise Errors::WebAssemblyBinaryNotFoundError unless ctx.file_exist?(binary_path)
53
+
54
+ ctx.binread(binary_path)
55
+ end
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Script
4
+ module Layers
5
+ module Infrastructure
6
+ module Languages
7
+ class TaskRunner
8
+ TASK_RUNNERS = {
9
+ "assemblyscript" => AssemblyScriptTaskRunner,
10
+ "rust" => RustTaskRunner,
11
+ }
12
+
13
+ def self.for(ctx, language, script_name)
14
+ raise Errors::TaskRunnerNotFoundError unless TASK_RUNNERS[language]
15
+ TASK_RUNNERS[language].new(ctx, script_name)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
@@ -8,23 +8,22 @@ module Script
8
8
  property! :ctx, accepts: ShopifyCli::Context
9
9
 
10
10
  def create_push_package(script_project:, script_content:, compiled_type:, metadata:)
11
- build_file_path = file_path(script_project.id, script_project.script_name, compiled_type)
11
+ build_file_path = file_path(script_project.id, compiled_type)
12
12
  write_to_path(build_file_path, script_content)
13
13
 
14
14
  Domain::PushPackage.new(
15
15
  id: build_file_path,
16
16
  uuid: script_project.uuid,
17
17
  extension_point_type: script_project.extension_point_type,
18
- script_name: script_project.script_name,
19
18
  script_content: script_content,
20
19
  compiled_type: compiled_type,
21
20
  metadata: metadata,
22
- config_ui: script_project.config_ui,
21
+ script_json: script_project.script_json,
23
22
  )
24
23
  end
25
24
 
26
25
  def get_push_package(script_project:, compiled_type:, metadata:)
27
- build_file_path = file_path(script_project.id, script_project.script_name, compiled_type)
26
+ build_file_path = file_path(script_project.id, compiled_type)
28
27
  raise Domain::PushPackageNotFoundError unless ctx.file_exist?(build_file_path)
29
28
 
30
29
  script_content = ctx.binread(build_file_path)
@@ -32,11 +31,10 @@ module Script
32
31
  id: build_file_path,
33
32
  uuid: script_project.uuid,
34
33
  extension_point_type: script_project.extension_point_type,
35
- script_name: script_project.script_name,
36
34
  script_content: script_content,
37
35
  compiled_type: compiled_type,
38
36
  metadata: metadata,
39
- config_ui: script_project.config_ui,
37
+ script_json: script_project.script_json,
40
38
  )
41
39
  end
42
40
 
@@ -47,8 +45,8 @@ module Script
47
45
  ctx.binwrite(path, content)
48
46
  end
49
47
 
50
- def file_path(path_to_script, script_name, compiled_type)
51
- "#{path_to_script}/build/#{script_name}.#{compiled_type}"
48
+ def file_path(path_to_script, compiled_type)
49
+ "#{path_to_script}/build/script.#{compiled_type}"
52
50
  end
53
51
  end
54
52
  end
@@ -7,30 +7,19 @@ module Script
7
7
  include SmartProperties
8
8
  property! :ctx, accepts: ShopifyCli::Context
9
9
 
10
- DEFAULT_CONFIG_UI_FILENAME = "config-ui.yml"
10
+ SCRIPT_JSON_FILENAME = "script.json"
11
11
  MUTABLE_ENV_VALUES = %i(uuid)
12
12
 
13
- def create(script_name:, extension_point_type:, language:, no_config_ui:)
13
+ def create(script_name:, extension_point_type:, language:)
14
14
  validate_metadata!(extension_point_type, language)
15
15
 
16
- optional_identifiers = {}
17
- config_ui_file = nil
18
-
19
- unless no_config_ui
20
- optional_identifiers.merge!(config_ui_file: DEFAULT_CONFIG_UI_FILENAME)
21
- config_ui_file = ConfigUiRepository
22
- .new(ctx: ctx)
23
- .create(DEFAULT_CONFIG_UI_FILENAME, default_config_ui_content(script_name))
24
- end
25
-
26
16
  ShopifyCli::Project.write(
27
17
  ctx,
28
18
  project_type: :script,
29
19
  organization_id: nil,
30
20
  extension_point_type: extension_point_type,
31
21
  script_name: script_name,
32
- language: language,
33
- **optional_identifiers
22
+ language: language
34
23
  )
35
24
 
36
25
  Domain::ScriptProject.new(
@@ -38,23 +27,20 @@ module Script
38
27
  env: project.env,
39
28
  script_name: script_name,
40
29
  extension_point_type: extension_point_type,
41
- language: language,
42
- config_ui: config_ui_file
30
+ language: language
43
31
  )
44
32
  end
45
33
 
46
34
  def get
47
35
  validate_metadata!(extension_point_type, language)
48
36
 
49
- config_ui = ConfigUiRepository.new(ctx: ctx).get(config_ui_file)
50
-
51
37
  Domain::ScriptProject.new(
52
38
  id: project.directory,
53
39
  env: project.env,
54
40
  script_name: script_name,
55
41
  extension_point_type: extension_point_type,
56
42
  language: language,
57
- config_ui: config_ui
43
+ script_json: ScriptJsonRepository.new(ctx: ctx).get
58
44
  )
59
45
  end
60
46
 
@@ -72,7 +58,7 @@ module Script
72
58
  script_name: script_name,
73
59
  extension_point_type: extension_point_type,
74
60
  language: language,
75
- config_ui: ConfigUiRepository.new(ctx: ctx).get(config_ui_file),
61
+ script_json: ScriptJsonRepository.new(ctx: ctx).get,
76
62
  )
77
63
  end
78
64
 
@@ -91,7 +77,22 @@ module Script
91
77
  script_name: script_name,
92
78
  extension_point_type: extension_point_type,
93
79
  language: language,
94
- config_ui: ConfigUiRepository.new(ctx: ctx).get(config_ui_file),
80
+ script_json: ScriptJsonRepository.new(ctx: ctx).get,
81
+ )
82
+ end
83
+
84
+ def update_or_create_script_json(title:, configuration_ui: false)
85
+ script_json = ScriptJsonRepository
86
+ .new(ctx: ctx)
87
+ .update_or_create(title: title, configuration_ui: configuration_ui)
88
+
89
+ Domain::ScriptProject.new(
90
+ id: ctx.root,
91
+ env: project.env,
92
+ script_name: script_name,
93
+ extension_point_type: extension_point_type,
94
+ language: language,
95
+ script_json: script_json,
95
96
  )
96
97
  end
97
98
 
@@ -109,10 +110,6 @@ module Script
109
110
  project_config_value!("script_name")
110
111
  end
111
112
 
112
- def config_ui_file
113
- project_config_value("config_ui_file")
114
- end
115
-
116
113
  def language
117
114
  project_config_value("language")&.downcase || default_language
118
115
  end
@@ -131,17 +128,6 @@ module Script
131
128
  @project ||= ShopifyCli::Project.current(force_reload: true)
132
129
  end
133
130
 
134
- def default_config_ui_content(title)
135
- require "yaml" # takes 20ms, so deferred as late as possible.
136
- YAML.dump({
137
- "version" => 1,
138
- "inputMode" => "single",
139
- "title" => title,
140
- "description" => "",
141
- "fields" => [],
142
- })
143
- end
144
-
145
131
  def default_language
146
132
  Domain::ExtensionPoint::ExtensionPointAssemblyScriptSDK.language
147
133
  end
@@ -154,41 +140,40 @@ module Script
154
140
  end
155
141
  end
156
142
 
157
- class ConfigUiRepository
143
+ class ScriptJsonRepository
158
144
  include SmartProperties
159
145
  property! :ctx, accepts: ShopifyCli::Context
160
146
 
161
- def create(filename, content)
162
- File.write(filename, content)
163
-
164
- Domain::ConfigUi.new(
165
- filename: filename,
166
- content: content,
167
- )
147
+ def get
148
+ current_script_json || raise(Domain::Errors::NoScriptJsonFile)
168
149
  end
169
150
 
170
- def get(filename)
171
- return nil unless filename
172
-
173
- path = File.join(ctx.root, filename)
174
- raise Domain::Errors::MissingSpecifiedConfigUiDefinitionError, filename unless File.exist?(path)
151
+ def update_or_create(title:, configuration_ui:)
152
+ json = current_script_json&.content || {}
153
+ json["version"] ||= "1"
154
+ json["title"] = title
155
+ json["configurationUi"] = !!configuration_ui
175
156
 
176
- content = File.read(path)
177
- raise Domain::Errors::InvalidConfigUiDefinitionError, filename unless valid_config_ui?(content)
157
+ ctx.write(SCRIPT_JSON_FILENAME, JSON.pretty_generate(json))
178
158
 
179
- Domain::ConfigUi.new(
180
- filename: filename,
181
- content: content,
182
- )
159
+ Domain::ScriptJson.new(content: json)
183
160
  end
184
161
 
185
162
  private
186
163
 
187
- def valid_config_ui?(raw_yaml)
188
- require "yaml" # takes 20ms, so deferred as late as possible.
189
- YAML.safe_load(raw_yaml)
164
+ def current_script_json
165
+ return nil unless ctx.file_exist?(SCRIPT_JSON_FILENAME)
166
+
167
+ content = ctx.read(SCRIPT_JSON_FILENAME)
168
+ raise Domain::Errors::InvalidScriptJsonDefinitionError unless valid_script_json?(content)
169
+
170
+ Domain::ScriptJson.new(content: JSON.parse(content))
171
+ end
172
+
173
+ def valid_script_json?(content)
174
+ JSON.parse(content)
190
175
  true
191
- rescue Psych::SyntaxError
176
+ rescue JSON::ParserError
192
177
  false
193
178
  end
194
179
  end
@@ -13,26 +13,28 @@ module Script
13
13
  def push(
14
14
  uuid:,
15
15
  extension_point_type:,
16
- script_name:,
17
16
  script_content:,
18
17
  compiled_type:,
19
18
  api_key: nil,
20
19
  force: false,
21
20
  metadata:,
22
- config_ui:
21
+ script_json:
23
22
  )
24
23
  query_name = "app_script_update_or_create"
25
24
  variables = {
26
25
  uuid: uuid,
27
26
  extensionPointName: extension_point_type.upcase,
28
- title: script_name,
29
- configUi: config_ui&.content,
27
+ title: script_json.title,
28
+ description: script_json.description,
30
29
  sourceCode: Base64.encode64(script_content),
31
30
  language: compiled_type,
32
31
  force: force,
33
32
  schemaMajorVersion: metadata.schema_major_version.to_s, # API expects string value
34
33
  schemaMinorVersion: metadata.schema_minor_version.to_s, # API expects string value
35
34
  useMsgpack: metadata.use_msgpack,
35
+ scriptJsonVersion: script_json.version,
36
+ configurationUi: script_json.configuration_ui,
37
+ configurationDefinition: script_json.configuration&.to_json,
36
38
  }
37
39
  resp_hash = script_service_request(query_name: query_name, api_key: api_key, variables: variables)
38
40
  user_errors = resp_hash["data"]["appScriptUpdateOrCreate"]["userErrors"]
@@ -40,17 +42,21 @@ module Script
40
42
  return resp_hash["data"]["appScriptUpdateOrCreate"]["appScript"]["uuid"] if user_errors.empty?
41
43
 
42
44
  if user_errors.any? { |e| e["tag"] == "already_exists_error" }
43
- raise Errors::ScriptRepushError, api_key
44
- elsif (e = user_errors.any? { |err| err["tag"] == "config_ui_syntax_error" })
45
- raise Errors::ConfigUiSyntaxError, config_ui&.filename
46
- elsif (e = user_errors.find { |err| err["tag"] == "config_ui_missing_keys_error" })
47
- raise Errors::ConfigUiMissingKeysError.new(config_ui&.filename, e["message"])
48
- elsif (e = user_errors.find { |err| err["tag"] == "config_ui_invalid_input_mode_error" })
49
- raise Errors::ConfigUiInvalidInputModeError.new(config_ui&.filename, e["message"])
50
- elsif (e = user_errors.find { |err| err["tag"] == "config_ui_fields_missing_keys_error" })
51
- raise Errors::ConfigUiFieldsMissingKeysError.new(config_ui&.filename, e["message"])
52
- elsif (e = user_errors.find { |err| err["tag"] == "config_ui_fields_invalid_type_error" })
53
- raise Errors::ConfigUiFieldsInvalidTypeError.new(config_ui&.filename, e["message"])
45
+ raise Errors::ScriptRepushError, uuid
46
+ elsif (e = user_errors.any? { |err| err["tag"] == "configuration_syntax_error" })
47
+ raise Errors::ScriptJsonSyntaxError
48
+ elsif (e = user_errors.find { |err| err["tag"] == "configuration_definition_missing_keys_error" })
49
+ raise Errors::ScriptJsonMissingKeysError, e["message"]
50
+ elsif (e = user_errors.find { |err| err["tag"] == "configuration_definition_invalid_value_error" })
51
+ raise Errors::ScriptJsonInvalidValueError, e["message"]
52
+ elsif (e = user_errors.find do |err|
53
+ err["tag"] == "configuration_definition_schema_field_missing_keys_error"
54
+ end)
55
+ raise Errors::ScriptJsonFieldsMissingKeysError, e["message"]
56
+ elsif (e = user_errors.find do |err|
57
+ err["tag"] == "configuration_definition_schema_field_invalid_value_error"
58
+ end)
59
+ raise Errors::ScriptJsonFieldsInvalidValueError, e["message"]
54
60
  elsif user_errors.find { |err| %w(not_use_msgpack_error schema_version_argument_error).include?(err["tag"]) }
55
61
  raise Domain::Errors::MetadataValidationError
56
62
  else