shopify-cli 1.9.0 → 1.13.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 (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