shopify-cli 1.10.0 → 1.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/PULL_REQUEST_TEMPLATE.md +1 -0
- data/CHANGELOG.md +9 -1
- data/Gemfile.lock +1 -1
- data/lib/project_types/extension/cli.rb +6 -2
- data/lib/project_types/extension/commands/serve.rb +69 -1
- data/lib/project_types/extension/commands/tunnel.rb +3 -1
- data/lib/project_types/extension/extension_project.rb +1 -0
- data/lib/project_types/extension/features/argo.rb +15 -24
- data/lib/project_types/extension/features/argo_runtime.rb +63 -0
- data/lib/project_types/extension/features/argo_serve.rb +35 -25
- data/lib/project_types/extension/features/argo_serve_options.rb +40 -0
- data/lib/project_types/extension/messages/messages.rb +3 -0
- data/lib/project_types/extension/models/npm_package.rb +14 -0
- data/lib/project_types/extension/models/specification.rb +1 -0
- data/lib/project_types/extension/models/specification_handlers/checkout_argo_extension.rb +18 -0
- data/lib/project_types/extension/models/specification_handlers/default.rb +28 -3
- data/lib/project_types/extension/tasks/choose_next_available_port.rb +36 -0
- data/lib/project_types/extension/tasks/configure_features.rb +2 -0
- data/lib/project_types/extension/tasks/find_npm_packages.rb +106 -0
- data/lib/project_types/script/cli.rb +1 -0
- data/lib/project_types/script/layers/domain/errors.rb +0 -2
- data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +12 -17
- data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +13 -7
- data/lib/project_types/script/layers/infrastructure/command_runner.rb +19 -0
- data/lib/project_types/script/layers/infrastructure/errors.rb +12 -3
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +4 -4
- data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +9 -10
- data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +5 -6
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +2 -28
- data/lib/project_types/script/layers/infrastructure/script_service.rb +1 -1
- data/lib/project_types/script/messages/messages.rb +6 -4
- data/lib/project_types/script/tasks/ensure_env.rb +10 -2
- data/lib/project_types/script/ui/error_handler.rb +7 -6
- data/lib/shopify-cli/messages/messages.rb +47 -43
- data/lib/shopify-cli/method_object.rb +4 -4
- data/lib/shopify-cli/oauth.rb +7 -1
- data/lib/shopify-cli/partners_api/organizations.rb +3 -3
- data/lib/shopify-cli/tasks/select_org_and_shop.rb +6 -4
- data/lib/shopify-cli/tunnel.rb +22 -1
- data/lib/shopify-cli/version.rb +1 -1
- metadata +10 -4
- data/lib/project_types/extension/features/argo_renderer_package.rb +0 -47
@@ -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,45 @@ module Extension
|
|
42
42
|
[]
|
43
43
|
end
|
44
44
|
|
45
|
-
def
|
46
|
-
|
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(specification_handler: self, argo_runtime: argo_runtime(context),
|
55
|
+
context: context, port: port, tunnel_url: tunnel_url).call
|
47
56
|
end
|
48
57
|
|
49
58
|
def renderer_package(context)
|
50
59
|
argo.renderer_package(context)
|
51
60
|
end
|
52
61
|
|
62
|
+
def argo_runtime(context)
|
63
|
+
@argo_runtime ||= Features::ArgoRuntime.new(
|
64
|
+
renderer: renderer_package(context),
|
65
|
+
cli: cli_package(context)
|
66
|
+
)
|
67
|
+
end
|
68
|
+
|
69
|
+
def cli_package(context)
|
70
|
+
cli_package_name = specification.features.argo&.cli_package_name
|
71
|
+
return unless cli_package_name
|
72
|
+
|
73
|
+
js_system = ShopifyCli::JsSystem.new(ctx: context)
|
74
|
+
Tasks::FindNpmPackages.exactly_one_of(cli_package_name, js_system: js_system)
|
75
|
+
.unwrap { |_e| context.abort(context.message("errors.package_not_found", cli_package_name)) }
|
76
|
+
end
|
77
|
+
|
53
78
|
protected
|
54
79
|
|
55
80
|
def argo
|
56
81
|
Features::Argo.new(
|
57
82
|
git_template: specification.features.argo.git_template,
|
58
|
-
renderer_package_name: specification.features.argo.renderer_package_name
|
83
|
+
renderer_package_name: specification.features.argo.renderer_package_name
|
59
84
|
)
|
60
85
|
end
|
61
86
|
|
@@ -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
|
@@ -54,6 +54,7 @@ module Script
|
|
54
54
|
Project.project_filepath("layers/infrastructure/assemblyscript_project_creator")
|
55
55
|
autoload :AssemblyScriptTaskRunner, Project.project_filepath("layers/infrastructure/assemblyscript_task_runner")
|
56
56
|
autoload :AssemblyScriptTsConfig, Project.project_filepath("layers/infrastructure/assemblyscript_tsconfig")
|
57
|
+
autoload :CommandRunner, Project.project_filepath("layers/infrastructure/command_runner")
|
57
58
|
autoload :RustProjectCreator,
|
58
59
|
Project.project_filepath("layers/infrastructure/rust_project_creator.rb")
|
59
60
|
autoload :RustTaskRunner, Project.project_filepath("layers/infrastructure/rust_task_runner")
|
@@ -12,7 +12,7 @@ module Script
|
|
12
12
|
|
13
13
|
BOOTSTRAP = "npx --no-install shopify-scripts-toolchain-as bootstrap --from %{extension_point} --dest %{base}"
|
14
14
|
BUILD = "shopify-scripts-toolchain-as build --src src/shopify_main.ts " \
|
15
|
-
"--binary build
|
15
|
+
"--binary build/script.wasm --metadata build/metadata.json"
|
16
16
|
MIN_NODE_VERSION = "14.5.0"
|
17
17
|
ASC_ARGS = "-- --lib node_modules --optimize --use Date="
|
18
18
|
|
@@ -22,28 +22,24 @@ module Script
|
|
22
22
|
end
|
23
23
|
|
24
24
|
def bootstrap
|
25
|
-
|
26
|
-
raise Domain::Errors::ServiceFailureError, out unless status.success?
|
25
|
+
command_runner.call(bootstap_command)
|
27
26
|
end
|
28
27
|
|
29
28
|
private
|
30
29
|
|
30
|
+
def command_runner
|
31
|
+
@command_runner ||= CommandRunner.new(ctx: ctx)
|
32
|
+
end
|
33
|
+
|
31
34
|
def write_npmrc
|
32
|
-
|
33
|
-
|
34
|
-
)
|
35
|
-
ctx.system(
|
36
|
-
"npm", "--userconfig", "./.npmrc", "config", "set", "engine-strict", "true"
|
37
|
-
)
|
35
|
+
command_runner.call("npm --userconfig ./.npmrc config set @shopify:registry https://registry.npmjs.com")
|
36
|
+
command_runner.call("npm --userconfig ./.npmrc config set engine-strict true")
|
38
37
|
end
|
39
38
|
|
40
39
|
def extension_point_version
|
41
|
-
if extension_point.sdks.assemblyscript.versioned?
|
42
|
-
return extension_point.sdks.assemblyscript.version
|
43
|
-
end
|
40
|
+
return extension_point.sdks.assemblyscript.version if extension_point.sdks.assemblyscript.versioned?
|
44
41
|
|
45
|
-
out
|
46
|
-
raise Domain::Errors::ServiceFailureError, out unless status.success?
|
42
|
+
out = command_runner.call("npm show #{extension_point.sdks.assemblyscript.package} version --json")
|
47
43
|
"^#{JSON.parse(out)}"
|
48
44
|
end
|
49
45
|
|
@@ -85,13 +81,12 @@ module Script
|
|
85
81
|
|
86
82
|
def build_command
|
87
83
|
type = extension_point.dasherize_type
|
88
|
-
base_command = format(BUILD, script_name: script_name)
|
89
84
|
domain = extension_point.domain
|
90
85
|
|
91
86
|
if domain.nil?
|
92
|
-
"#{
|
87
|
+
"#{BUILD} #{ASC_ARGS}"
|
93
88
|
else
|
94
|
-
"#{
|
89
|
+
"#{BUILD} --domain #{domain} --ep #{type} #{ASC_ARGS}"
|
95
90
|
end
|
96
91
|
end
|
97
92
|
end
|
@@ -63,9 +63,7 @@ module Script
|
|
63
63
|
|
64
64
|
def compile
|
65
65
|
check_compilation_dependencies!
|
66
|
-
|
67
|
-
out, status = ctx.capture2e(SCRIPT_SDK_BUILD)
|
68
|
-
raise Domain::Errors::ServiceFailureError, out unless status.success?
|
66
|
+
CommandRunner.new(ctx: ctx).call(SCRIPT_SDK_BUILD)
|
69
67
|
end
|
70
68
|
|
71
69
|
def check_compilation_dependencies!
|
@@ -81,11 +79,19 @@ module Script
|
|
81
79
|
end
|
82
80
|
|
83
81
|
def bytecode
|
84
|
-
|
85
|
-
|
82
|
+
legacy_filename = format(BYTECODE_FILE, name: script_name)
|
83
|
+
filename = format(BYTECODE_FILE, name: "script")
|
84
|
+
|
85
|
+
bytecode_file = if ctx.file_exist?(filename)
|
86
|
+
filename
|
87
|
+
elsif ctx.file_exist?(legacy_filename)
|
88
|
+
legacy_filename
|
89
|
+
else
|
90
|
+
raise Errors::WebAssemblyBinaryNotFoundError
|
91
|
+
end
|
86
92
|
|
87
|
-
contents = ctx.binread(
|
88
|
-
ctx.rm(
|
93
|
+
contents = ctx.binread(bytecode_file)
|
94
|
+
ctx.rm(bytecode_file)
|
89
95
|
|
90
96
|
contents
|
91
97
|
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Script
|
4
|
+
module Layers
|
5
|
+
module Infrastructure
|
6
|
+
class CommandRunner
|
7
|
+
include SmartProperties
|
8
|
+
|
9
|
+
property! :ctx, accepts: ShopifyCli::Context
|
10
|
+
|
11
|
+
def call(cmd)
|
12
|
+
out, status = ctx.capture2e(cmd)
|
13
|
+
raise Errors::SystemCallFailureError.new(out: out.chomp, cmd: cmd) unless status.success?
|
14
|
+
out
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -69,11 +69,20 @@ module Script
|
|
69
69
|
|
70
70
|
class ProjectCreatorNotFoundError < ScriptProjectError; end
|
71
71
|
|
72
|
+
class SystemCallFailureError < ScriptProjectError
|
73
|
+
attr_reader :out, :cmd
|
74
|
+
def initialize(out:, cmd:)
|
75
|
+
super()
|
76
|
+
@out = out
|
77
|
+
@cmd = cmd
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
72
81
|
class ScriptRepushError < ScriptProjectError
|
73
|
-
attr_reader :
|
74
|
-
def initialize(
|
82
|
+
attr_reader :uuid
|
83
|
+
def initialize(uuid)
|
75
84
|
super()
|
76
|
-
@
|
85
|
+
@uuid = uuid
|
77
86
|
end
|
78
87
|
end
|
79
88
|
|
@@ -8,7 +8,7 @@ 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,
|
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(
|
@@ -24,7 +24,7 @@ module Script
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def get_push_package(script_project:, compiled_type:, metadata:)
|
27
|
-
build_file_path = file_path(script_project.id,
|
27
|
+
build_file_path = file_path(script_project.id, compiled_type)
|
28
28
|
raise Domain::PushPackageNotFoundError unless ctx.file_exist?(build_file_path)
|
29
29
|
|
30
30
|
script_content = ctx.binread(build_file_path)
|
@@ -47,8 +47,8 @@ module Script
|
|
47
47
|
ctx.binwrite(path, content)
|
48
48
|
end
|
49
49
|
|
50
|
-
def file_path(path_to_script,
|
51
|
-
"#{path_to_script}/build
|
50
|
+
def file_path(path_to_script, compiled_type)
|
51
|
+
"#{path_to_script}/build/script.#{compiled_type}"
|
52
52
|
end
|
53
53
|
end
|
54
54
|
end
|