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