shopify-cli 1.9.0 → 1.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (79) hide show
  1. checksums.yaml +4 -4
  2. data/.github/PULL_REQUEST_TEMPLATE.md +1 -0
  3. data/.github/workflows/build.yml +28 -0
  4. data/.github/workflows/release.yml +2 -4
  5. data/CHANGELOG.md +27 -0
  6. data/Gemfile.lock +1 -1
  7. data/README.md +2 -1
  8. data/lib/project_types/extension/cli.rb +7 -1
  9. data/lib/project_types/extension/commands/serve.rb +69 -1
  10. data/lib/project_types/extension/commands/tunnel.rb +3 -1
  11. data/lib/project_types/extension/extension_project.rb +1 -0
  12. data/lib/project_types/extension/features/argo.rb +18 -49
  13. data/lib/project_types/extension/features/argo_runtime.rb +81 -0
  14. data/lib/project_types/extension/features/argo_serve.rb +35 -27
  15. data/lib/project_types/extension/features/argo_serve_options.rb +41 -0
  16. data/lib/project_types/extension/features/argo_setup.rb +1 -1
  17. data/lib/project_types/extension/messages/messages.rb +5 -4
  18. data/lib/project_types/extension/models/npm_package.rb +14 -0
  19. data/lib/project_types/extension/models/specification.rb +3 -2
  20. data/lib/project_types/extension/models/specification_handlers/checkout_argo_extension.rb +18 -0
  21. data/lib/project_types/extension/models/specification_handlers/default.rb +33 -3
  22. data/lib/project_types/extension/models/version.rb +1 -1
  23. data/lib/project_types/extension/tasks/choose_next_available_port.rb +36 -0
  24. data/lib/project_types/extension/tasks/configure_features.rb +2 -0
  25. data/lib/project_types/extension/tasks/find_npm_packages.rb +106 -0
  26. data/lib/project_types/node/messages/messages.rb +4 -4
  27. data/lib/project_types/rails/messages/messages.rb +4 -4
  28. data/lib/project_types/script/cli.rb +17 -12
  29. data/lib/project_types/script/commands/push.rb +1 -1
  30. data/lib/project_types/script/config/extension_points.yml +2 -3
  31. data/lib/project_types/script/graphql/app_script_update_or_create.graphql +5 -2
  32. data/lib/project_types/script/graphql/get_app_scripts.graphql +6 -0
  33. data/lib/project_types/script/layers/application/create_script.rb +2 -2
  34. data/lib/project_types/script/layers/application/push_script.rb +6 -3
  35. data/lib/project_types/script/layers/domain/errors.rb +0 -2
  36. data/lib/project_types/script/layers/domain/push_package.rb +4 -0
  37. data/lib/project_types/script/layers/domain/script_project.rb +21 -1
  38. data/lib/project_types/script/layers/infrastructure/command_runner.rb +19 -0
  39. data/lib/project_types/script/layers/infrastructure/errors.rb +30 -3
  40. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +97 -0
  41. data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +103 -0
  42. data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +26 -0
  43. data/lib/project_types/script/layers/infrastructure/languages/rust_project_creator.rb +73 -0
  44. data/lib/project_types/script/layers/infrastructure/languages/rust_task_runner.rb +60 -0
  45. data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +21 -0
  46. data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +6 -5
  47. data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +61 -34
  48. data/lib/project_types/script/layers/infrastructure/script_service.rb +14 -2
  49. data/lib/project_types/script/messages/messages.rb +20 -3
  50. data/lib/project_types/script/tasks/ensure_env.rb +85 -0
  51. data/lib/project_types/script/ui/error_handler.rb +25 -6
  52. data/lib/shopify-cli/admin_api.rb +7 -4
  53. data/lib/shopify-cli/js_system.rb +2 -2
  54. data/lib/shopify-cli/messages/messages.rb +51 -45
  55. data/lib/shopify-cli/method_object.rb +4 -4
  56. data/lib/shopify-cli/oauth.rb +9 -3
  57. data/lib/shopify-cli/packager.rb +1 -1
  58. data/lib/shopify-cli/partners_api.rb +7 -4
  59. data/lib/shopify-cli/partners_api/organizations.rb +3 -3
  60. data/lib/shopify-cli/resolve_constant.rb +1 -1
  61. data/lib/shopify-cli/resources/env_file.rb +2 -2
  62. data/lib/shopify-cli/shopifolk.rb +1 -1
  63. data/lib/shopify-cli/tasks/select_org_and_shop.rb +6 -4
  64. data/lib/shopify-cli/transform_data_structure.rb +1 -1
  65. data/lib/shopify-cli/tunnel.rb +22 -1
  66. data/lib/shopify-cli/version.rb +1 -1
  67. data/lib/shopify_cli.rb +0 -1
  68. data/vendor/deps/smart_properties/REVISION +1 -1
  69. data/vendor/deps/smart_properties/lib/smart_properties/property.rb +7 -1
  70. data/vendor/deps/smart_properties/lib/smart_properties/version.rb +1 -1
  71. metadata +19 -11
  72. data/.travis.yml +0 -14
  73. data/lib/project_types/extension/features/argo_renderer_package.rb +0 -32
  74. data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +0 -100
  75. data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +0 -95
  76. data/lib/project_types/script/layers/infrastructure/project_creator.rb +0 -24
  77. data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +0 -72
  78. data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +0 -59
  79. 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,11 +8,12 @@ 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
+ uuid: script_project.uuid,
16
17
  extension_point_type: script_project.extension_point_type,
17
18
  script_name: script_project.script_name,
18
19
  script_content: script_content,
@@ -23,13 +24,13 @@ module Script
23
24
  end
24
25
 
25
26
  def get_push_package(script_project:, compiled_type:, metadata:)
26
- build_file_path = file_path(script_project.id, script_project.script_name, compiled_type)
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
 
29
30
  script_content = ctx.binread(build_file_path)
30
-
31
31
  Domain::PushPackage.new(
32
32
  id: build_file_path,
33
+ uuid: script_project.uuid,
33
34
  extension_point_type: script_project.extension_point_type,
34
35
  script_name: script_project.script_name,
35
36
  script_content: script_content,
@@ -46,8 +47,8 @@ module Script
46
47
  ctx.binwrite(path, content)
47
48
  end
48
49
 
49
- def file_path(path_to_script, script_name, compiled_type)
50
- "#{path_to_script}/build/#{script_name}.#{compiled_type}"
50
+ def file_path(path_to_script, compiled_type)
51
+ "#{path_to_script}/build/script.#{compiled_type}"
51
52
  end
52
53
  end
53
54
  end
@@ -8,19 +8,14 @@ module Script
8
8
  property! :ctx, accepts: ShopifyCli::Context
9
9
 
10
10
  DEFAULT_CONFIG_UI_FILENAME = "config-ui.yml"
11
+ MUTABLE_ENV_VALUES = %i(uuid)
11
12
 
12
13
  def create(script_name:, extension_point_type:, language:, no_config_ui:)
13
14
  validate_metadata!(extension_point_type, language)
14
15
 
15
- optional_identifiers = {}
16
16
  config_ui_file = nil
17
-
18
- unless no_config_ui
19
- optional_identifiers.merge!(config_ui_file: DEFAULT_CONFIG_UI_FILENAME)
20
- config_ui_file = ConfigUiRepository
21
- .new(ctx: ctx)
22
- .create(DEFAULT_CONFIG_UI_FILENAME, default_config_ui_content(script_name))
23
- end
17
+ optional_identifiers = {}
18
+ optional_identifiers.merge!(config_ui_file: DEFAULT_CONFIG_UI_FILENAME) unless no_config_ui
24
19
 
25
20
  ShopifyCli::Project.write(
26
21
  ctx,
@@ -43,11 +38,6 @@ module Script
43
38
  end
44
39
 
45
40
  def get
46
- extension_point_type = project_config_value!("extension_point_type")
47
- script_name = project_config_value!("script_name")
48
- config_ui_file = project_config_value("config_ui_file")
49
- language = project_config_value("language")&.downcase || default_language
50
-
51
41
  validate_metadata!(extension_point_type, language)
52
42
 
53
43
  config_ui = ConfigUiRepository.new(ctx: ctx).get(config_ui_file)
@@ -62,8 +52,65 @@ module Script
62
52
  )
63
53
  end
64
54
 
55
+ def update_env(**args)
56
+ capture_io do
57
+ args.slice(*MUTABLE_ENV_VALUES).each do |key, value|
58
+ project.env.extra[key.to_s.upcase] = value
59
+ project.env.update(ctx, :extra, project.env.extra)
60
+ end
61
+ end
62
+
63
+ Domain::ScriptProject.new(
64
+ id: ctx.root,
65
+ env: project.env,
66
+ script_name: script_name,
67
+ extension_point_type: extension_point_type,
68
+ language: language,
69
+ config_ui: ConfigUiRepository.new(ctx: ctx).get(config_ui_file),
70
+ )
71
+ end
72
+
73
+ def create_env(api_key:, secret:, uuid:)
74
+ ShopifyCli::Resources::EnvFile.new(
75
+ api_key: api_key,
76
+ secret: secret,
77
+ extra: {
78
+ Domain::ScriptProject::UUID_ENV_KEY => uuid,
79
+ }
80
+ ).write(ctx)
81
+
82
+ Domain::ScriptProject.new(
83
+ id: ctx.root,
84
+ env: project.env,
85
+ script_name: script_name,
86
+ extension_point_type: extension_point_type,
87
+ language: language,
88
+ config_ui: ConfigUiRepository.new(ctx: ctx).get(config_ui_file),
89
+ )
90
+ end
91
+
65
92
  private
66
93
 
94
+ def capture_io(&block)
95
+ CLI::UI::StdoutRouter::Capture.new(&block).run
96
+ end
97
+
98
+ def extension_point_type
99
+ project_config_value!("extension_point_type")
100
+ end
101
+
102
+ def script_name
103
+ project_config_value!("script_name")
104
+ end
105
+
106
+ def config_ui_file
107
+ project_config_value("config_ui_file")
108
+ end
109
+
110
+ def language
111
+ project_config_value("language")&.downcase || default_language
112
+ end
113
+
67
114
  def project_config_value(key)
68
115
  return nil unless project.config.key?(key)
69
116
  project.config[key]
@@ -75,18 +122,7 @@ module Script
75
122
  end
76
123
 
77
124
  def project
78
- ShopifyCli::Project.current
79
- end
80
-
81
- def default_config_ui_content(title)
82
- require "yaml" # takes 20ms, so deferred as late as possible.
83
- YAML.dump({
84
- "version" => 1,
85
- "inputMode" => "single",
86
- "title" => title,
87
- "description" => "",
88
- "fields" => [],
89
- })
125
+ @project ||= ShopifyCli::Project.current(force_reload: true)
90
126
  end
91
127
 
92
128
  def default_language
@@ -105,15 +141,6 @@ module Script
105
141
  include SmartProperties
106
142
  property! :ctx, accepts: ShopifyCli::Context
107
143
 
108
- def create(filename, content)
109
- File.write(filename, content)
110
-
111
- Domain::ConfigUi.new(
112
- filename: filename,
113
- content: content,
114
- )
115
- end
116
-
117
144
  def get(filename)
118
145
  return nil unless filename
119
146
 
@@ -11,6 +11,7 @@ module Script
11
11
  property! :ctx, accepts: ShopifyCli::Context
12
12
 
13
13
  def push(
14
+ uuid:,
14
15
  extension_point_type:,
15
16
  script_name:,
16
17
  script_content:,
@@ -22,6 +23,7 @@ module Script
22
23
  )
23
24
  query_name = "app_script_update_or_create"
24
25
  variables = {
26
+ uuid: uuid,
25
27
  extensionPointName: extension_point_type.upcase,
26
28
  title: script_name,
27
29
  configUi: config_ui&.content,
@@ -35,16 +37,20 @@ module Script
35
37
  resp_hash = script_service_request(query_name: query_name, api_key: api_key, variables: variables)
36
38
  user_errors = resp_hash["data"]["appScriptUpdateOrCreate"]["userErrors"]
37
39
 
38
- return resp_hash if user_errors.empty?
40
+ return resp_hash["data"]["appScriptUpdateOrCreate"]["appScript"]["uuid"] if user_errors.empty?
39
41
 
40
42
  if user_errors.any? { |e| e["tag"] == "already_exists_error" }
41
- raise Errors::ScriptRepushError, api_key
43
+ raise Errors::ScriptRepushError, uuid
42
44
  elsif (e = user_errors.any? { |err| err["tag"] == "config_ui_syntax_error" })
43
45
  raise Errors::ConfigUiSyntaxError, config_ui&.filename
44
46
  elsif (e = user_errors.find { |err| err["tag"] == "config_ui_missing_keys_error" })
45
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"])
46
50
  elsif (e = user_errors.find { |err| err["tag"] == "config_ui_fields_missing_keys_error" })
47
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"])
48
54
  elsif user_errors.find { |err| %w(not_use_msgpack_error schema_version_argument_error).include?(err["tag"]) }
49
55
  raise Domain::Errors::MetadataValidationError
50
56
  else
@@ -52,6 +58,12 @@ module Script
52
58
  end
53
59
  end
54
60
 
61
+ def get_app_scripts(api_key:, extension_point_type:)
62
+ query_name = "get_app_scripts"
63
+ variables = { appKey: api_key, extensionPointName: extension_point_type.upcase }
64
+ script_service_request(query_name: query_name, api_key: api_key, variables: variables)["data"]["appScripts"]
65
+ end
66
+
55
67
  private
56
68
 
57
69
  class ScriptServiceAPI < ShopifyCli::API
@@ -64,14 +64,22 @@ module Script
64
64
  "%{missing_keys}.",
65
65
  config_ui_missing_keys_error_help: "Add the keys and try again.",
66
66
 
67
+ config_ui_invalid_input_mode_error_cause: "The UI configuration file %{filename} only accept "\
68
+ "one of the following input mode(s): %{valid_input_modes}.",
69
+ config_ui_invalid_input_mode_error_help: "Change the input modes and try again.",
70
+
67
71
  config_ui_fields_missing_keys_error_cause: "A field entry in the UI configuration file %{filename} is "\
68
72
  "missing required keys: %{missing_keys}.",
69
73
  config_ui_fields_missing_keys_error_help: "Add the keys and try again.",
70
74
 
75
+ config_ui_fields_invalid_type_error_cause: "The UI configuration file %{filename} fields only accept "\
76
+ "one of the following type(s): %{valid_types}.",
77
+ config_ui_fields_invalid_type_error_help: "Change the types and try again.",
78
+
71
79
  script_not_found_cause: "Couldn't find script %s for extension point %s",
72
80
 
73
- service_failure_cause: "Internal service error.",
74
- service_failure_help: "Ensure the 'shopify/scripts-toolchain-as' package is up to date.",
81
+ system_call_failure_cause: "An error was returned while running {{command:%{cmd}}}.",
82
+ system_call_failure_help: "Review the following error and try again.\n{{red:%{out}}}",
75
83
 
76
84
  metadata_validation_cause: "Invalid script extension metadata.",
77
85
  metadata_validation_help: "Ensure the 'shopify/scripts-toolchain-as' package is up to date.",
@@ -105,7 +113,7 @@ module Script
105
113
  graphql_error_cause: "An error was returned: %s.",
106
114
  graphql_error_help: "\nReview the error and try again.",
107
115
 
108
- script_repush_cause: "A script with the same extension point already exists on app (API key: %s).",
116
+ script_repush_cause: "A script with this UUID already exists (UUID: %s).",
109
117
  script_repush_help: "Use {{cyan:--force}} to replace the existing script.",
110
118
 
111
119
  shop_auth_cause: "Unable to authenticate with the store.",
@@ -184,6 +192,15 @@ module Script
184
192
  disabled: "Disabled",
185
193
  enabling: "Enabling",
186
194
  enabled: "Enabled",
195
+ ensure_env: {
196
+ organization: "Partner organization {{green:%s (%s)}}.",
197
+ organization_select: "Which partner organization do you want to use?",
198
+ app: "Script will be pushed to app {{green:%s}}.",
199
+ app_select: "Which app do you want to push this script to?",
200
+ ask_connect_to_existing_script: "The selected app has some scripts. Do you want to replace any of the "\
201
+ "existing scripts with the current script?",
202
+ ask_which_script_to_connect_to: "Which script do you want to replace?",
203
+ },
187
204
  },
188
205
  },
189
206
  }.freeze