shopify-cli 1.10.0 → 1.14.0

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