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,42 @@
1
+ module Extension
2
+ module Features
3
+ class ArgoServeOptions
4
+ include SmartProperties
5
+
6
+ property! :argo_runtime, accepts: Features::ArgoRuntime
7
+ property! :context, accepts: ShopifyCli::Context
8
+ property :port, accepts: Integer, default: 39351
9
+ property :public_url, accepts: String, default: ""
10
+ property! :required_fields, accepts: Array, default: -> { [] }
11
+ property! :renderer_package, accepts: Models::NpmPackage
12
+
13
+ YARN_SERVE_COMMAND = %w(server)
14
+ NPM_SERVE_COMMAND = %w(run-script server)
15
+
16
+ def yarn_serve_command
17
+ YARN_SERVE_COMMAND + options
18
+ end
19
+
20
+ def npm_serve_command
21
+ NPM_SERVE_COMMAND + ["--"] + options
22
+ end
23
+
24
+ private
25
+
26
+ def options
27
+ project = ExtensionProject.current
28
+ api_key = project.env.api_key
29
+
30
+ @serve_options ||= [].tap do |options|
31
+ options << "--port=#{port}" if argo_runtime.accepts_port?
32
+ options << "--shop=#{project.env.shop}" if required_fields.include?(:shop) && argo_runtime.accepts_shop?
33
+ options << "--apiKey=#{api_key}" if required_fields.include?(:api_key) && argo_runtime.accepts_api_key?
34
+ options << "--argoVersion=#{renderer_package.version}" if argo_runtime.accepts_argo_version?
35
+ options << "--uuid=#{project.registration_uuid}" if argo_runtime.accepts_uuid?
36
+ options << "--publicUrl=#{public_url}" if argo_runtime.accepts_tunnel_url?
37
+ options << "--name=#{project.title}" if argo_runtime.accepts_name?
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
@@ -56,8 +56,10 @@ module Extension
56
56
  },
57
57
  serve: {
58
58
  frame_title: "Serving extension...",
59
+ no_available_ports_found: "No available ports found to run extension.",
59
60
  serve_failure_message: "Failed to run extension code.",
60
61
  serve_missing_information: "Missing shop or api_key.",
62
+ tunnel_already_running: "A tunnel running on another port has been detected. Close the tunnel and try again.",
61
63
  },
62
64
  tunnel: {
63
65
  missing_token: "{{x}} {{red:auth requires a token argument}}. "\
@@ -115,6 +117,7 @@ module Extension
115
117
  },
116
118
  errors: {
117
119
  unknown_type: "Unknown extension type %s",
120
+ package_not_found: "`%s` package not found.",
118
121
  },
119
122
  }
120
123
 
@@ -0,0 +1,14 @@
1
+ require "semantic/semantic"
2
+
3
+ module Extension
4
+ module Models
5
+ NpmPackage = Struct.new(:name, :version, keyword_init: true) do
6
+ include Comparable
7
+
8
+ def <=>(other)
9
+ return nil unless name == other.name
10
+ Semantic::Version.new(version) <=> Semantic::Version.new(other.version)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -12,6 +12,7 @@ module Extension
12
12
  property! :git_template, converts: :to_str
13
13
  property! :required_fields, accepts: Array, default: -> { [] }
14
14
  property! :required_shop_beta_flags, accepts: Array, default: -> { [] }
15
+ property! :cli_package_name, accepts: String, converts: :to_str, default: ""
15
16
  end
16
17
 
17
18
  def self.build(feature_set_attributes)
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Extension
4
+ module Models
5
+ module SpecificationHandlers
6
+ class CheckoutArgoExtension < Default
7
+ PERMITTED_CONFIG_KEYS = [:metafields, :extension_points]
8
+
9
+ def config(context)
10
+ {
11
+ **Features::ArgoConfig.parse_yaml(context, PERMITTED_CONFIG_KEYS),
12
+ **argo.config(context),
13
+ }
14
+ end
15
+ end
16
+ end
17
+ end
18
+ end
@@ -42,20 +42,50 @@ module Extension
42
42
  []
43
43
  end
44
44
 
45
- def serve(context)
46
- Features::ArgoServe.new(specification_handler: self, context: context).call
45
+ def choose_port?(context)
46
+ argo_runtime(context).accepts_port?
47
+ end
48
+
49
+ def establish_tunnel?(context)
50
+ argo_runtime(context).accepts_tunnel_url?
51
+ end
52
+
53
+ def serve(context:, port:, tunnel_url:)
54
+ Features::ArgoServe.new(
55
+ specification_handler: self,
56
+ argo_runtime: argo_runtime(context),
57
+ context: context,
58
+ port: port,
59
+ tunnel_url: tunnel_url,
60
+ ).call
47
61
  end
48
62
 
49
63
  def renderer_package(context)
50
64
  argo.renderer_package(context)
51
65
  end
52
66
 
67
+ def argo_runtime(context)
68
+ @argo_runtime ||= Features::ArgoRuntime.new(
69
+ renderer: renderer_package(context),
70
+ cli: cli_package(context),
71
+ )
72
+ end
73
+
74
+ def cli_package(context)
75
+ cli_package_name = specification.features.argo&.cli_package_name
76
+ return unless cli_package_name
77
+
78
+ js_system = ShopifyCli::JsSystem.new(ctx: context)
79
+ Tasks::FindNpmPackages.exactly_one_of(cli_package_name, js_system: js_system)
80
+ .unwrap { |_e| context.abort(context.message("errors.package_not_found", cli_package_name)) }
81
+ end
82
+
53
83
  protected
54
84
 
55
85
  def argo
56
86
  Features::Argo.new(
57
87
  git_template: specification.features.argo.git_template,
58
- renderer_package_name: specification.features.argo.renderer_package_name,
88
+ renderer_package_name: specification.features.argo.renderer_package_name
59
89
  )
60
90
  end
61
91
 
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+ require "shopify_cli"
3
+ require "socket"
4
+
5
+ module Extension
6
+ module Tasks
7
+ class ChooseNextAvailablePort
8
+ include ShopifyCli::MethodObject
9
+
10
+ property! :from
11
+ property! :to, default: -> { from + 10 }
12
+ property! :host, default: "localhost"
13
+
14
+ def call
15
+ available_port = port_range(from: from, to: to).find { |p| available?(host, p) }
16
+ raise ArgumentError, "Ports between #{from} and #{to} are unavailable" if available_port.nil?
17
+ available_port
18
+ end
19
+
20
+ private
21
+
22
+ def port_range(from:, to:)
23
+ (from..to)
24
+ end
25
+
26
+ def available?(host, port)
27
+ Socket.tcp(host, port, connect_timeout: 1) do |socket|
28
+ socket.close
29
+ false
30
+ end
31
+ rescue Errno::ECONNREFUSED
32
+ true
33
+ end
34
+ end
35
+ end
36
+ end
@@ -42,10 +42,12 @@ module Extension
42
42
  renderer_package_name: "@shopify/argo-admin",
43
43
  required_fields: [:shop, :api_key],
44
44
  required_shop_beta_flags: [:argo_admin_beta],
45
+ cli_package_name: "@shopify/argo-admin-cli",
45
46
  },
46
47
  checkout: {
47
48
  git_template: "https://github.com/Shopify/argo-checkout-template.git",
48
49
  renderer_package_name: "@shopify/argo-checkout",
50
+ cli_package_name: "@shopify/argo-run",
49
51
  },
50
52
  }
51
53
  end
@@ -0,0 +1,106 @@
1
+ module Extension
2
+ module Tasks
3
+ class FindNpmPackages
4
+ include ShopifyCli::MethodObject
5
+
6
+ property! :js_system, accepts: ShopifyCli::JsSystem
7
+ property! :production_only, accepts: [true, false], default: false, reader: :production_only?
8
+
9
+ def self.at_least_one_of(*package_names, **config)
10
+ new(**config).at_least_one_of(*package_names)
11
+ end
12
+
13
+ def self.all(*package_names, **config)
14
+ new(**config).all(*package_names)
15
+ end
16
+
17
+ def self.exactly_one_of(*package_names, **config)
18
+ new(**config).exactly_one_of(*package_names)
19
+ end
20
+
21
+ def all(*package_names)
22
+ call(*package_names) do |found_packages|
23
+ found_package_names = found_packages.map(&:name)
24
+ next found_packages if Set.new(found_package_names) == Set.new(package_names)
25
+ raise PackageResolutionFailed, format(
26
+ "Missing packages: %s",
27
+ (package_names - found_package_names).join(", ")
28
+ )
29
+ end
30
+ end
31
+
32
+ def at_least_one_of(*package_names)
33
+ call(*package_names) do |found_packages|
34
+ found_package_names = found_packages.map(&:name)
35
+ next found_packages unless (found_package_names & package_names).empty?
36
+ raise PackageResolutionFailed, format(
37
+ "Expected at least one of the following packages: %s",
38
+ package_names.join(", ")
39
+ )
40
+ end
41
+ end
42
+
43
+ def exactly_one_of(*package_names)
44
+ call(*package_names) do |found_packages|
45
+ case found_packages.count
46
+ when 0
47
+ raise PackageResolutionFailed, format(
48
+ "Expected one of the following packages: %s",
49
+ package_names.join(", ")
50
+ )
51
+ when 1
52
+ found_packages.first.tap do |found_package|
53
+ next found_package if package_names.include?(found_package.name)
54
+ raise PackageResolutionFailed, format(
55
+ "Expected the following package: %s",
56
+ found_package.name
57
+ )
58
+ end
59
+ else
60
+ raise PackageResolutionFailed, "Found more than one package"
61
+ end
62
+ end
63
+ end
64
+
65
+ def call(*package_names, &validate)
66
+ validate ||= ->(found_packages) { found_packages }
67
+
68
+ unless package_names.all? { |name| name.is_a?(String) }
69
+ raise ArgumentError, "Expected a list of package names"
70
+ end
71
+
72
+ ShopifyCli::Result
73
+ .call(&method(:list_packages))
74
+ .then(&method(:search_packages).curry[package_names])
75
+ .then(&method(:filter_duplicates))
76
+ .then(&validate)
77
+ end
78
+
79
+ def list_packages
80
+ result, error, status =
81
+ js_system.call(yarn: yarn_list, npm: npm_list, capture_response: true)
82
+ raise error unless status.success?
83
+ result
84
+ end
85
+
86
+ def yarn_list
87
+ production_only? ? %w[list --production] : %w[list]
88
+ end
89
+
90
+ def npm_list
91
+ production_only? ? %w[list --prod --depth=1] : %w[list --depth=1]
92
+ end
93
+
94
+ def search_packages(packages, package_list)
95
+ pattern = /(#{packages.join("|")})@(\d.*)$/
96
+ package_list.scan(pattern).map do |(name, version)|
97
+ Models::NpmPackage.new(name: name, version: version.strip)
98
+ end
99
+ end
100
+
101
+ def filter_duplicates(packages)
102
+ packages.reject { |p| p.version.match(/deduped/) }.uniq
103
+ end
104
+ end
105
+ end
106
+ end
@@ -39,31 +39,32 @@ module Script
39
39
 
40
40
  module Domain
41
41
  autoload :Errors, Project.project_filepath("layers/domain/errors")
42
- autoload :ConfigUi, Project.project_filepath("layers/domain/config_ui")
43
42
  autoload :PushPackage, Project.project_filepath("layers/domain/push_package")
44
43
  autoload :Metadata, Project.project_filepath("layers/domain/metadata")
45
44
  autoload :ExtensionPoint, Project.project_filepath("layers/domain/extension_point")
45
+ autoload :ScriptJson, Project.project_filepath("layers/domain/script_json")
46
46
  autoload :ScriptProject, Project.project_filepath("layers/domain/script_project")
47
47
  end
48
48
 
49
49
  module Infrastructure
50
50
  autoload :Errors, Project.project_filepath("layers/infrastructure/errors")
51
- autoload :AssemblyScriptDependencyManager,
52
- Project.project_filepath("layers/infrastructure/assemblyscript_dependency_manager")
53
- autoload :AssemblyScriptProjectCreator,
54
- Project.project_filepath("layers/infrastructure/assemblyscript_project_creator")
55
- autoload :AssemblyScriptTaskRunner, Project.project_filepath("layers/infrastructure/assemblyscript_task_runner")
56
- autoload :AssemblyScriptTsConfig, Project.project_filepath("layers/infrastructure/assemblyscript_tsconfig")
57
- autoload :RustProjectCreator,
58
- Project.project_filepath("layers/infrastructure/rust_project_creator.rb")
59
- autoload :RustTaskRunner, Project.project_filepath("layers/infrastructure/rust_task_runner")
60
-
51
+ autoload :CommandRunner, Project.project_filepath("layers/infrastructure/command_runner")
61
52
  autoload :PushPackageRepository, Project.project_filepath("layers/infrastructure/push_package_repository")
62
53
  autoload :ExtensionPointRepository, Project.project_filepath("layers/infrastructure/extension_point_repository")
63
- autoload :ProjectCreator, Project.project_filepath("layers/infrastructure/project_creator")
64
54
  autoload :ScriptProjectRepository, Project.project_filepath("layers/infrastructure/script_project_repository")
65
55
  autoload :ScriptService, Project.project_filepath("layers/infrastructure/script_service")
66
- autoload :TaskRunner, Project.project_filepath("layers/infrastructure/task_runner")
56
+
57
+ module Languages
58
+ autoload :AssemblyScriptProjectCreator,
59
+ Project.project_filepath("layers/infrastructure/languages/assemblyscript_project_creator")
60
+ autoload :AssemblyScriptTaskRunner,
61
+ Project.project_filepath("layers/infrastructure/languages/assemblyscript_task_runner")
62
+ autoload :ProjectCreator, Project.project_filepath("layers/infrastructure/languages/project_creator")
63
+ autoload :RustProjectCreator,
64
+ Project.project_filepath("layers/infrastructure/languages/rust_project_creator.rb")
65
+ autoload :RustTaskRunner, Project.project_filepath("layers/infrastructure/languages/rust_task_runner")
66
+ autoload :TaskRunner, Project.project_filepath("layers/infrastructure/languages/task_runner")
67
+ end
67
68
  end
68
69
  end
69
70
 
@@ -8,15 +8,20 @@ module Script
8
8
  end
9
9
 
10
10
  def call(_args, _name)
11
- Tasks::EnsureEnv.call(@ctx)
11
+ fresh_env = Tasks::EnsureEnv.call(@ctx)
12
+ force = options.flags.key?(:force) || !!fresh_env
12
13
 
13
14
  api_key = Layers::Infrastructure::ScriptProjectRepository.new(ctx: @ctx).get.api_key
14
15
  return @ctx.puts(self.class.help) unless api_key
15
16
 
16
- Layers::Application::PushScript.call(ctx: @ctx, force: options.flags.key?(:force))
17
+ Layers::Application::PushScript.call(ctx: @ctx, force: force)
17
18
  @ctx.puts(@ctx.message("script.push.script_pushed", api_key: api_key))
18
19
  rescue StandardError => e
19
- msg = @ctx.message("script.push.error.operation_failed", api_key: api_key)
20
+ msg = if api_key
21
+ @ctx.message("script.push.error.operation_failed_with_api_key", api_key: api_key)
22
+ else
23
+ @ctx.message("script.push.error.operation_failed_no_api_key")
24
+ end
20
25
  UI::ErrorHandler.pretty_print_and_raise(e, failed_op: msg)
21
26
  end
22
27
 
@@ -36,11 +36,8 @@ payment_methods:
36
36
  assemblyscript:
37
37
  package: "@shopify/scripts-checkout-apis"
38
38
  toolchain-version: "^5.0.0"
39
- sdk-version: "^9.0.0"
40
39
  shipping_methods:
41
40
  domain: 'checkout'
42
41
  assemblyscript:
43
42
  package: "@shopify/scripts-checkout-apis"
44
- sdk-version: "^9.0.0"
45
43
  toolchain-version: "^5.0.0"
46
-
@@ -1,19 +1,22 @@
1
1
  mutation AppScriptUpdateOrCreate(
2
2
  $extensionPointName: ExtensionPointName!,
3
3
  $title: String,
4
- $configUi: String,
4
+ $description: String,
5
5
  $sourceCode: String,
6
6
  $language: String,
7
7
  $force: Boolean,
8
8
  $schemaMajorVersion: String,
9
9
  $schemaMinorVersion: String,
10
10
  $useMsgpack: Boolean,
11
- $uuid: String
11
+ $uuid: String,
12
+ $configurationUi: Boolean,
13
+ $scriptJsonVersion: String,
14
+ $configurationDefinition: String,
12
15
  ) {
13
16
  appScriptUpdateOrCreate(
14
17
  extensionPointName: $extensionPointName
15
18
  title: $title
16
- configUi: $configUi
19
+ description: $description
17
20
  sourceCode: $sourceCode
18
21
  language: $language
19
22
  force: $force
@@ -21,6 +24,9 @@ mutation AppScriptUpdateOrCreate(
21
24
  schemaMinorVersion: $schemaMinorVersion
22
25
  useMsgpack: $useMsgpack,
23
26
  uuid: $uuid
27
+ configurationUi: $configurationUi
28
+ scriptJsonVersion: $scriptJsonVersion
29
+ configurationDefinition: $configurationDefinition
24
30
  ) {
25
31
  userErrors {
26
32
  field
@@ -12,16 +12,17 @@ module Script
12
12
 
13
13
  in_new_directory_context(ctx, script_name) do
14
14
  extension_point = ExtensionPoints.get(type: extension_point_type)
15
- project = Infrastructure::ScriptProjectRepository.new(ctx: ctx).create(
15
+ script_project_repo = Infrastructure::ScriptProjectRepository.new(ctx: ctx)
16
+ project = script_project_repo.create(
16
17
  script_name: script_name,
17
18
  extension_point_type: extension_point_type,
18
- language: language,
19
- no_config_ui: no_config_ui
19
+ language: language
20
20
  )
21
- project_creator = Infrastructure::ProjectCreator
21
+ project_creator = Infrastructure::Languages::ProjectCreator
22
22
  .for(ctx, language, extension_point, script_name, project.id)
23
23
  install_dependencies(ctx, language, script_name, project_creator)
24
24
  bootstrap(ctx, project_creator)
25
+ script_project_repo.update_or_create_script_json(title: script_name, configuration_ui: !no_config_ui)
25
26
  project
26
27
  end
27
28
  end
@@ -29,7 +30,7 @@ module Script
29
30
  private
30
31
 
31
32
  def install_dependencies(ctx, language, script_name, project_creator)
32
- task_runner = Infrastructure::TaskRunner.for(ctx, language, script_name)
33
+ task_runner = Infrastructure::Languages::TaskRunner.for(ctx, language, script_name)
33
34
  project_creator.setup_dependencies
34
35
  ProjectDependencies.install(ctx: ctx, task_runner: task_runner)
35
36
  end