shopify-cli 2.10.1 → 2.11.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/bug_report.yaml +117 -0
- data/.github/ISSUE_TEMPLATE/enhancement.yaml +38 -0
- data/.github/ISSUE_TEMPLATE/feature.yaml +47 -0
- data/.github/ISSUE_TEMPLATE.md +18 -0
- data/CHANGELOG.md +38 -3
- data/Gemfile.lock +1 -1
- data/bin/shopify +9 -0
- data/dev.yml +3 -0
- data/lib/project_types/extension/commands/check.rb +2 -0
- data/lib/project_types/extension/commands/create.rb +2 -0
- data/lib/project_types/extension/commands/push.rb +15 -0
- data/lib/project_types/extension/commands/serve.rb +2 -0
- data/lib/project_types/extension/loaders/project.rb +28 -8
- data/lib/project_types/extension/messages/messages.rb +10 -2
- data/lib/project_types/extension/models/specification_handlers/default.rb +1 -1
- data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +7 -1
- data/lib/project_types/extension/tasks/convert_server_config.rb +3 -1
- data/lib/project_types/script/cli.rb +5 -0
- data/lib/project_types/script/commands/connect.rb +3 -1
- data/lib/project_types/script/commands/create.rb +2 -0
- data/lib/project_types/script/commands/push.rb +6 -0
- data/lib/project_types/script/config/extension_points.yml +12 -0
- data/lib/project_types/script/graphql/module_upload_url_generate.graphql +5 -1
- data/lib/project_types/script/layers/application/build_script.rb +6 -2
- data/lib/project_types/script/layers/application/create_script.rb +1 -1
- data/lib/project_types/script/layers/application/project_dependencies.rb +1 -1
- data/lib/project_types/script/layers/application/push_script.rb +39 -31
- data/lib/project_types/script/layers/domain/errors.rb +7 -1
- data/lib/project_types/script/layers/domain/extension_point.rb +2 -2
- data/lib/project_types/script/layers/infrastructure/errors.rb +13 -3
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +3 -16
- data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +1 -0
- data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +35 -8
- data/lib/project_types/script/layers/infrastructure/languages/typescript_task_runner.rb +3 -16
- data/lib/project_types/script/layers/infrastructure/languages/wasm_project_creator.rb +15 -0
- data/lib/project_types/script/layers/infrastructure/languages/wasm_task_runner.rb +32 -0
- data/lib/project_types/script/layers/infrastructure/metadata_repository.rb +18 -0
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +1 -1
- data/lib/project_types/script/layers/infrastructure/script_service.rb +12 -8
- data/lib/project_types/script/layers/infrastructure/script_uploader.rb +22 -9
- data/lib/project_types/script/loaders/project.rb +2 -1
- data/lib/project_types/script/messages/messages.rb +92 -84
- data/lib/project_types/script/ui/error_handler.rb +39 -14
- data/lib/project_types/theme/commands/check.rb +2 -0
- data/lib/project_types/theme/commands/delete.rb +2 -0
- data/lib/project_types/theme/commands/init.rb +2 -0
- data/lib/project_types/theme/commands/language_server.rb +2 -0
- data/lib/project_types/theme/commands/package.rb +2 -0
- data/lib/project_types/theme/commands/publish.rb +2 -0
- data/lib/project_types/theme/commands/pull.rb +9 -2
- data/lib/project_types/theme/commands/push.rb +7 -4
- data/lib/project_types/theme/commands/serve.rb +2 -0
- data/lib/shopify_cli/command/sub_command.rb +2 -0
- data/lib/shopify_cli/command.rb +74 -0
- data/lib/shopify_cli/commands/app/create/node.rb +3 -0
- data/lib/shopify_cli/commands/app/create/rails.rb +3 -0
- data/lib/shopify_cli/commands/app/deploy.rb +2 -0
- data/lib/shopify_cli/commands/app/serve.rb +2 -0
- data/lib/shopify_cli/constants.rb +13 -1
- data/lib/shopify_cli/environment.rb +55 -35
- data/lib/shopify_cli/exception_reporter.rb +9 -0
- data/lib/shopify_cli/github/issue_url_generator.rb +19 -8
- data/lib/shopify_cli/identity_auth/env_auth_token.rb +34 -0
- data/lib/shopify_cli/identity_auth.rb +33 -15
- data/lib/shopify_cli/messages/messages.rb +3 -2
- data/lib/shopify_cli/partners_api.rb +7 -2
- data/lib/shopify_cli/services/app/create/rails_service.rb +37 -13
- data/lib/shopify_cli/theme/dev_server/hot_reload/remote_file_reloader.rb +63 -0
- data/lib/shopify_cli/theme/dev_server/hot_reload.rb +22 -6
- data/lib/shopify_cli/theme/dev_server/proxy.rb +4 -5
- data/lib/shopify_cli/theme/dev_server.rb +1 -3
- data/lib/shopify_cli/theme/development_theme.rb +11 -0
- data/lib/shopify_cli/theme/file.rb +4 -0
- data/lib/shopify_cli/theme/include_filter.rb +39 -17
- data/lib/shopify_cli/theme/theme.rb +0 -4
- data/lib/shopify_cli/utilities.rb +7 -0
- data/lib/shopify_cli/version.rb +1 -1
- data/lib/shopify_cli.rb +1 -0
- data/vendor/deps/cli-kit/lib/cli/kit/system.rb +11 -6
- data/vendor/deps/cli-kit/lib/cli/kit/util.rb +5 -1
- data/vendor/deps/cli-ui/lib/cli/ui/os.rb +6 -4
- data/vendor/lib/semantic/version.rb +0 -1
- metadata +11 -3
- data/lib/project_types/rails/commands/create.rb +0 -210
data/lib/shopify_cli/command.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require "shopify_cli"
|
3
|
+
require "semantic/semantic"
|
3
4
|
|
4
5
|
module ShopifyCLI
|
5
6
|
class Command < CLI::Kit::BaseCommand
|
@@ -7,6 +8,8 @@ module ShopifyCLI
|
|
7
8
|
autoload :AppSubCommand, "shopify_cli/command/app_sub_command"
|
8
9
|
autoload :ProjectCommand, "shopify_cli/command/project_command"
|
9
10
|
|
11
|
+
VersionRange = Struct.new(:from, :to, keyword_init: true)
|
12
|
+
|
10
13
|
extend Feature::Set
|
11
14
|
|
12
15
|
attr_writer :ctx
|
@@ -26,6 +29,8 @@ module ShopifyCLI
|
|
26
29
|
cmd = new(@ctx)
|
27
30
|
cmd.options.parse(@_options, args)
|
28
31
|
return call_help(command_name) if cmd.options.help
|
32
|
+
check_ruby_version
|
33
|
+
check_node_version
|
29
34
|
run_prerequisites
|
30
35
|
cmd.call(args, command_name)
|
31
36
|
end
|
@@ -58,6 +63,75 @@ module ShopifyCLI
|
|
58
63
|
)
|
59
64
|
end
|
60
65
|
|
66
|
+
def recommend_ruby(from:, to:)
|
67
|
+
@compatible_ruby_range = VersionRange.new(
|
68
|
+
from: Semantic::Version.new(from),
|
69
|
+
to: Semantic::Version.new(to)
|
70
|
+
)
|
71
|
+
end
|
72
|
+
|
73
|
+
def recommend_default_ruby_range
|
74
|
+
recommend_ruby(
|
75
|
+
from: Constants::SupportedVersions::Ruby::FROM,
|
76
|
+
to: Constants::SupportedVersions::Ruby::TO
|
77
|
+
)
|
78
|
+
end
|
79
|
+
|
80
|
+
def check_ruby_version
|
81
|
+
check_version(
|
82
|
+
Environment.ruby_version,
|
83
|
+
range: @compatible_ruby_range,
|
84
|
+
runtime: "Ruby"
|
85
|
+
)
|
86
|
+
end
|
87
|
+
|
88
|
+
def recommend_node(from:, to:)
|
89
|
+
@compatible_node_range = VersionRange.new(
|
90
|
+
from: Semantic::Version.new(from),
|
91
|
+
to: Semantic::Version.new(to)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
def recommend_default_node_range
|
96
|
+
recommend_node(
|
97
|
+
from: Constants::SupportedVersions::Node::FROM,
|
98
|
+
to: Constants::SupportedVersions::Node::TO
|
99
|
+
)
|
100
|
+
end
|
101
|
+
|
102
|
+
def check_node_version
|
103
|
+
return unless @compatible_node_range
|
104
|
+
|
105
|
+
context = Context.new
|
106
|
+
if context.which("node").nil?
|
107
|
+
raise ShopifyCLI::Abort, context.message("core.errors.missing_node")
|
108
|
+
end
|
109
|
+
|
110
|
+
check_version(
|
111
|
+
Environment.node_version,
|
112
|
+
range: @compatible_node_range,
|
113
|
+
runtime: "Node",
|
114
|
+
context: context
|
115
|
+
)
|
116
|
+
end
|
117
|
+
|
118
|
+
def check_version(version, range:, runtime:, context: Context.new)
|
119
|
+
return if Environment.test?
|
120
|
+
return if range.nil?
|
121
|
+
|
122
|
+
version_without_pre_nor_build = Utilities.version_dropping_pre_and_build(version)
|
123
|
+
is_higher_than_bottom = version_without_pre_nor_build >= Utilities.version_dropping_pre_and_build(range.from)
|
124
|
+
is_lower_than_top = version_without_pre_nor_build < Utilities.version_dropping_pre_and_build(range.to)
|
125
|
+
return if is_higher_than_bottom && is_lower_than_top
|
126
|
+
|
127
|
+
context.warn("Your environment #{runtime} version, #{version},"\
|
128
|
+
" is outside of the range supported by the CLI,"\
|
129
|
+
" #{range.from}..<#{range.to},"\
|
130
|
+
" and might cause incompatibility issues.")
|
131
|
+
rescue StandardError => error
|
132
|
+
ExceptionReporter.report_error_silently(error)
|
133
|
+
end
|
134
|
+
|
61
135
|
def prerequisite_task(*tasks_without_args, **tasks_with_args)
|
62
136
|
@prerequisite_tasks ||= []
|
63
137
|
@prerequisite_tasks += tasks_without_args.map { |t| PrerequisiteTask.new(t) }
|
@@ -5,6 +5,9 @@ module ShopifyCLI
|
|
5
5
|
class Node < ShopifyCLI::Command::AppSubCommand
|
6
6
|
prerequisite_task :ensure_authenticated
|
7
7
|
|
8
|
+
recommend_default_node_range
|
9
|
+
recommend_default_ruby_range
|
10
|
+
|
8
11
|
options do |parser, flags|
|
9
12
|
parser.on("--name=NAME") { |t| flags[:name] = t }
|
10
13
|
parser.on("--organization-id=ID") { |id| flags[:organization_id] = id }
|
@@ -5,6 +5,9 @@ module ShopifyCLI
|
|
5
5
|
class Rails < ShopifyCLI::Command::AppSubCommand
|
6
6
|
prerequisite_task :ensure_authenticated
|
7
7
|
|
8
|
+
recommend_default_ruby_range
|
9
|
+
recommend_default_node_range
|
10
|
+
|
8
11
|
options do |parser, flags|
|
9
12
|
parser.on("--name=NAME") { |t| flags[:name] = t }
|
10
13
|
parser.on("--organization-id=ID") { |id| flags[:organization_id] = id }
|
@@ -38,7 +38,7 @@ module ShopifyCLI
|
|
38
38
|
|
39
39
|
# When true the CLI points to spin instances of services
|
40
40
|
SPIN = "SPIN"
|
41
|
-
|
41
|
+
SPIN_INSTANCE = "SPIN_INSTANCE"
|
42
42
|
SPIN_WORKSPACE = "SPIN_WORKSPACE"
|
43
43
|
SPIN_NAMESPACE = "SPIN_NAMESPACE"
|
44
44
|
SPIN_HOST = "SPIN_HOST"
|
@@ -58,6 +58,18 @@ module ShopifyCLI
|
|
58
58
|
MONORAIL_REAL_EVENTS = "MONORAIL_REAL_EVENTS"
|
59
59
|
end
|
60
60
|
|
61
|
+
module SupportedVersions
|
62
|
+
module Ruby
|
63
|
+
FROM = "2.6.6"
|
64
|
+
TO = "3.1.0"
|
65
|
+
end
|
66
|
+
|
67
|
+
module Node
|
68
|
+
FROM = "14.5.0"
|
69
|
+
TO = "17.0.0"
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
61
73
|
module Identity
|
62
74
|
CLIENT_ID_DEV = "e5380e02-312a-7408-5718-e07017e9cf52"
|
63
75
|
CLIENT_ID = "fbdb2649-e327-4907-8f67-908d24cfd7e3"
|
@@ -1,8 +1,29 @@
|
|
1
|
+
require "semantic/semantic"
|
2
|
+
|
1
3
|
module ShopifyCLI
|
2
4
|
# The environment module provides an interface to get information from
|
3
5
|
# the environment in which the CLI runs
|
4
6
|
module Environment
|
5
7
|
TRUTHY_ENV_VARIABLE_VALUES = ["1", "true", "TRUE", "yes", "YES"]
|
8
|
+
SPIN_OVERRIDE_ENV_NAMES = [
|
9
|
+
Constants::EnvironmentVariables::SPIN_WORKSPACE,
|
10
|
+
Constants::EnvironmentVariables::SPIN_NAMESPACE,
|
11
|
+
Constants::EnvironmentVariables::SPIN_HOST,
|
12
|
+
]
|
13
|
+
|
14
|
+
def self.ruby_version(context: Context.new)
|
15
|
+
out, err, stat = context.capture3('ruby -e "puts RUBY_VERSION"')
|
16
|
+
raise ShopifyCLI::Abort, err unless stat.success?
|
17
|
+
out = out.gsub('"', "")
|
18
|
+
::Semantic::Version.new(out.chomp)
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.node_version(context: Context.new)
|
22
|
+
out, err, stat = context.capture3("node", "--version")
|
23
|
+
raise ShopifyCLI::Abort, err unless stat.success?
|
24
|
+
out = out.gsub("v", "")
|
25
|
+
::Semantic::Version.new(out.chomp)
|
26
|
+
end
|
6
27
|
|
7
28
|
def self.interactive=(interactive)
|
8
29
|
@interactive = interactive
|
@@ -71,6 +92,20 @@ module ShopifyCLI
|
|
71
92
|
end
|
72
93
|
end
|
73
94
|
|
95
|
+
def self.spin_url_override(env_variables: ENV)
|
96
|
+
tokens = SPIN_OVERRIDE_ENV_NAMES.map do |name|
|
97
|
+
env_variables[name]
|
98
|
+
end
|
99
|
+
|
100
|
+
return if tokens.all?(&:nil?)
|
101
|
+
|
102
|
+
if tokens.any?(&:nil?)
|
103
|
+
raise "To manually target a spin instance, you must set #{SPIN_OVERRIDE_ENV_NAMES}"
|
104
|
+
else
|
105
|
+
tokens.join(".")
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
74
109
|
def self.use_spin?(env_variables: ENV)
|
75
110
|
env_variable_truthy?(
|
76
111
|
Constants::EnvironmentVariables::SPIN,
|
@@ -81,24 +116,31 @@ module ShopifyCLI
|
|
81
116
|
)
|
82
117
|
end
|
83
118
|
|
84
|
-
def self.infer_spin?(env_variables: ENV)
|
85
|
-
env_variable_truthy?(
|
86
|
-
Constants::EnvironmentVariables::INFER_SPIN,
|
87
|
-
env_variables: env_variables
|
88
|
-
)
|
89
|
-
end
|
90
|
-
|
91
119
|
def self.spin_url(env_variables: ENV)
|
92
|
-
|
93
|
-
|
120
|
+
override = spin_url_override(env_variables: env_variables)
|
121
|
+
return override unless override.nil?
|
122
|
+
|
123
|
+
spin_response = if env_variables.key?(
|
124
|
+
Constants::EnvironmentVariables::SPIN_INSTANCE
|
125
|
+
)
|
126
|
+
spin_show
|
94
127
|
else
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
128
|
+
spin_show(latest: true)
|
129
|
+
end
|
130
|
+
|
131
|
+
begin
|
132
|
+
instance = JSON.parse(spin_response)
|
133
|
+
raise "Missing key 'fqdn' from spin show. Actual response: #{instance}" unless instance.include?("fqdn")
|
134
|
+
instance["fqdn"]
|
135
|
+
rescue => e
|
136
|
+
raise "Failed to infer spin environment from spin show response #{spin_response}: #{e}"
|
99
137
|
end
|
100
138
|
end
|
101
139
|
|
140
|
+
def self.spin_show(latest: false)
|
141
|
+
latest ? %x(spin show --latest --json) : %x(spin show --json)
|
142
|
+
end
|
143
|
+
|
102
144
|
def self.send_monorail_events?(env_variables: ENV)
|
103
145
|
env_variable_truthy?(
|
104
146
|
Constants::EnvironmentVariables::MONORAIL_REAL_EVENTS,
|
@@ -113,27 +155,5 @@ module ShopifyCLI
|
|
113
155
|
def self.env_variable_truthy?(variable_name, env_variables: ENV)
|
114
156
|
TRUTHY_ENV_VARIABLE_VALUES.include?(env_variables[variable_name.to_s])
|
115
157
|
end
|
116
|
-
|
117
|
-
def self.spin_workspace(env_variables: ENV)
|
118
|
-
env_value = env_variables[Constants::EnvironmentVariables::SPIN_WORKSPACE]
|
119
|
-
return env_value unless env_value.nil?
|
120
|
-
|
121
|
-
if env_value.nil?
|
122
|
-
raise "No value set for #{Constants::EnvironmentVariables::SPIN_WORKSPACE}"
|
123
|
-
end
|
124
|
-
end
|
125
|
-
|
126
|
-
def self.spin_namespace(env_variables: ENV)
|
127
|
-
env_value = env_variables[Constants::EnvironmentVariables::SPIN_NAMESPACE]
|
128
|
-
return env_value unless env_value.nil?
|
129
|
-
|
130
|
-
if env_value.nil?
|
131
|
-
raise "No value set for #{Constants::EnvironmentVariables::SPIN_NAMESPACE}"
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
def self.spin_host(env_variables: ENV)
|
136
|
-
env_variables[Constants::EnvironmentVariables::SPIN_HOST] || "us.spin.dev"
|
137
|
-
end
|
138
158
|
end
|
139
159
|
end
|
@@ -1,5 +1,10 @@
|
|
1
1
|
module ShopifyCLI
|
2
2
|
module ExceptionReporter
|
3
|
+
def self.report_error_silently(error)
|
4
|
+
return unless ReportingConfigurationController.reporting_enabled?
|
5
|
+
report_to_bugsnag(error: error)
|
6
|
+
end
|
7
|
+
|
3
8
|
def self.report(error, _logs = nil, _api_key = nil, custom_metadata = {})
|
4
9
|
context = ShopifyCLI::Context.new
|
5
10
|
unless ShopifyCLI::Environment.development?
|
@@ -19,6 +24,10 @@ module ShopifyCLI
|
|
19
24
|
return unless reportable_error?(error)
|
20
25
|
|
21
26
|
return unless report?(context: context)
|
27
|
+
report_to_bugsnag(error: error, custom_metadata: custom_metadata)
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.report_to_bugsnag(error:, custom_metadata: {})
|
22
31
|
ENV["BUGSNAG_DISABLE_AUTOCONFIGURE"] = "1"
|
23
32
|
require "bugsnag"
|
24
33
|
|
@@ -2,17 +2,28 @@ module ShopifyCLI
|
|
2
2
|
module GitHub
|
3
3
|
module IssueURLGenerator
|
4
4
|
def self.error_url(error)
|
5
|
-
title = "#{error.class}: #{error.message}"
|
5
|
+
title = "[Bug]: #{error.class}: #{error.message}"
|
6
6
|
labels = "type:bug"
|
7
|
-
content = File.read(File.join(ShopifyCLI::ROOT, ".github/ISSUE_TEMPLATE.md"))
|
8
7
|
|
9
8
|
# take at most 5 lines from backtrace
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
9
|
+
stacktrace_text =
|
10
|
+
if error.backtrace # Sometimes errors seem to appear without backtrace, see https://github.com/Shopify/shopify-cli/issues/1972#issuecomment-1028013630
|
11
|
+
stacktrace = error.backtrace.length < 5 ? error.backtrace : error.backtrace[0..4]
|
12
|
+
stacktrace.join("\n").to_s
|
13
|
+
else
|
14
|
+
""
|
15
|
+
end
|
16
|
+
query = URI.encode_www_form({
|
17
|
+
title: title,
|
18
|
+
labels: labels,
|
19
|
+
template: "bug_report.yaml",
|
20
|
+
stack_trace: stacktrace_text,
|
21
|
+
os: RUBY_PLATFORM,
|
22
|
+
cli_version: ShopifyCLI::VERSION,
|
23
|
+
ruby_version: "#{RUBY_VERSION}p#{RUBY_PATCHLEVEL}",
|
24
|
+
shell: ENV["SHELL"],
|
25
|
+
})
|
26
|
+
"#{ShopifyCLI::Constants::Links::NEW_ISSUE}?#{query}"
|
16
27
|
end
|
17
28
|
end
|
18
29
|
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ShopifyCLI
|
2
|
+
class IdentityAuth
|
3
|
+
class EnvAuthToken
|
4
|
+
Token = Struct.new(:token, :expires_at, keyword_init: true)
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor :exchanged_partners_token
|
8
|
+
|
9
|
+
def partners_token_present?
|
10
|
+
Environment.auth_token
|
11
|
+
end
|
12
|
+
|
13
|
+
def fetch_exchanged_partners_token
|
14
|
+
current_time = Time.now.to_i
|
15
|
+
|
16
|
+
# If we have an in-memory token that hasn't expired yet, we reuse it.
|
17
|
+
if exchanged_partners_token && current_time < exchanged_partners_token.expires_at.to_i
|
18
|
+
return exchanged_partners_token.token
|
19
|
+
end
|
20
|
+
|
21
|
+
new_exchanged_token = yield(Environment.auth_token)
|
22
|
+
token = new_exchanged_token["access_token"]
|
23
|
+
expires_in = new_exchanged_token["expires_in"].to_i
|
24
|
+
expires_at = Time.at(current_time + expires_in)
|
25
|
+
|
26
|
+
token = Token.new(token: token, expires_at: expires_at)
|
27
|
+
|
28
|
+
self.exchanged_partners_token = token
|
29
|
+
token.token
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -13,6 +13,7 @@ module ShopifyCLI
|
|
13
13
|
include SmartProperties
|
14
14
|
|
15
15
|
autoload :Servlet, "shopify_cli/identity_auth/servlet"
|
16
|
+
autoload :EnvAuthToken, "shopify_cli/identity_auth/env_auth_token"
|
16
17
|
|
17
18
|
class Error < StandardError; end
|
18
19
|
class Timeout < StandardError; end
|
@@ -68,9 +69,12 @@ module ShopifyCLI
|
|
68
69
|
request_exchange_tokens
|
69
70
|
end
|
70
71
|
|
71
|
-
def
|
72
|
-
|
73
|
-
|
72
|
+
def fetch_or_auth_partners_token
|
73
|
+
if EnvAuthToken.partners_token_present?
|
74
|
+
return EnvAuthToken.fetch_exchanged_partners_token do |env_token|
|
75
|
+
exchange_partners_auth_token(env_token)
|
76
|
+
end
|
77
|
+
end
|
74
78
|
|
75
79
|
ShopifyCLI::DB.get(:partners_exchange_token) do
|
76
80
|
IdentityAuth.new(ctx: ctx).authenticate
|
@@ -78,6 +82,15 @@ module ShopifyCLI
|
|
78
82
|
end
|
79
83
|
end
|
80
84
|
|
85
|
+
def exchange_partners_auth_token(subject_token)
|
86
|
+
application = "partners"
|
87
|
+
request_exchange_token(
|
88
|
+
audience: client_id_for_application(application),
|
89
|
+
scopes: APPLICATION_SCOPES[application],
|
90
|
+
subject_token: subject_token,
|
91
|
+
)
|
92
|
+
end
|
93
|
+
|
81
94
|
def self.environment_auth_token?
|
82
95
|
!!Environment.auth_token
|
83
96
|
end
|
@@ -195,30 +208,35 @@ module ShopifyCLI
|
|
195
208
|
|
196
209
|
def request_exchange_tokens
|
197
210
|
APPLICATION_SCOPES.each do |key, scopes|
|
198
|
-
|
211
|
+
request_and_save_exchange_token(key, client_id_for_application(key), scopes)
|
199
212
|
end
|
200
213
|
end
|
201
214
|
|
202
|
-
def
|
215
|
+
def request_and_save_exchange_token(name, audience, additional_scopes)
|
203
216
|
return if name == "shopify" && !store.exists?(:shop)
|
217
|
+
access_token = request_exchange_token(
|
218
|
+
audience: audience,
|
219
|
+
scopes: scopes(additional_scopes),
|
220
|
+
subject_token: store.get(:identity_access_token),
|
221
|
+
destination: name == "shopify" ? "https://#{store.get(:shop)}/admin" : nil
|
222
|
+
)["access_token"]
|
223
|
+
store.set("#{name}_exchange_token".to_sym => access_token)
|
224
|
+
ctx.debug("#{name}_exchange_token: " + access_token)
|
225
|
+
end
|
204
226
|
|
227
|
+
def request_exchange_token(audience:, scopes:, subject_token:, destination: nil)
|
205
228
|
params = {
|
206
229
|
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
207
230
|
requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
|
208
231
|
subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
|
209
232
|
client_id: client_id,
|
210
233
|
audience: audience,
|
211
|
-
scope: scopes
|
212
|
-
subject_token:
|
213
|
-
|
214
|
-
|
215
|
-
result[:destination] = "https://#{store.get(:shop)}/admin"
|
216
|
-
end
|
217
|
-
end
|
234
|
+
scope: scopes,
|
235
|
+
subject_token: subject_token,
|
236
|
+
destination: destination,
|
237
|
+
}.compact
|
218
238
|
# ctx.debug(params)
|
219
|
-
|
220
|
-
store.set("#{name}_exchange_token".to_sym => resp["access_token"])
|
221
|
-
ctx.debug("#{name}_exchange_token: " + resp["access_token"])
|
239
|
+
post_token_request(params)
|
222
240
|
end
|
223
241
|
|
224
242
|
def post_token_request(params)
|
@@ -15,6 +15,7 @@ module ShopifyCLI
|
|
15
15
|
},
|
16
16
|
core: {
|
17
17
|
errors: {
|
18
|
+
missing_node: "Node is required to continue. Install node here: https://nodejs.org/en/download.",
|
18
19
|
option_parser: {
|
19
20
|
invalid_option: "The option {{command:%s}} is not supported.",
|
20
21
|
missing_argument: "The required argument {{command:%s}} is missing.",
|
@@ -41,7 +42,7 @@ module ShopifyCLI
|
|
41
42
|
invalid_type: "The type %s is not supported. The only supported types are"\
|
42
43
|
" {{command:[ rails | node | php ]}}",
|
43
44
|
help: <<~HELP,
|
44
|
-
{{command:%s app create}}: Creates a
|
45
|
+
{{command:%s app create}}: Creates a new project in a subdirectory.
|
45
46
|
Usage: {{command:%s app create [ rails | node | php ]}}
|
46
47
|
HELP
|
47
48
|
rails: {
|
@@ -53,7 +54,7 @@ module ShopifyCLI
|
|
53
54
|
{{command:--organization-id=ID}} Partner organization ID. Must be an existing organization.
|
54
55
|
{{command:--store-domain=MYSHOPIFYDOMAIN }} Development store URL. Must be an existing development store.
|
55
56
|
{{command:--db=DB}} Database type. Must be one of: mysql, postgresql, sqlite3, oracle, frontbase, ibm_db, sqlserver, jdbcmysql, jdbcsqlite3, jdbcpostgresql, jdbc.
|
56
|
-
{{command:--rails-opts=RAILSOPTS}} Additional options. Must be string containing one or more valid Rails options, separated by spaces.
|
57
|
+
{{command:--rails-opts=RAILSOPTS}} Additional options. Must be a string containing one or more valid Rails options, separated by spaces.
|
57
58
|
HELP
|
58
59
|
|
59
60
|
error: {
|
@@ -38,7 +38,11 @@ module ShopifyCLI
|
|
38
38
|
def query(ctx, query_name, **variables)
|
39
39
|
CLI::Kit::Util.begin do
|
40
40
|
api_client(ctx).query(query_name, variables: variables)
|
41
|
-
end.retry_after(
|
41
|
+
end.retry_after(
|
42
|
+
API::APIRequestUnauthorizedError,
|
43
|
+
retries: 1,
|
44
|
+
only: -> { !IdentityAuth::EnvAuthToken.partners_token_present? }
|
45
|
+
) do
|
42
46
|
ShopifyCLI::IdentityAuth.new(ctx: ctx).reauthenticate
|
43
47
|
end
|
44
48
|
rescue API::APIRequestUnauthorizedError => e
|
@@ -60,9 +64,10 @@ module ShopifyCLI
|
|
60
64
|
private
|
61
65
|
|
62
66
|
def api_client(ctx)
|
67
|
+
identity_auth = ShopifyCLI::IdentityAuth.new(ctx: ctx)
|
63
68
|
new(
|
64
69
|
ctx: ctx,
|
65
|
-
token:
|
70
|
+
token: identity_auth.fetch_or_auth_partners_token,
|
66
71
|
url: "https://#{Environment.partners_domain}/api/cli/graphql",
|
67
72
|
)
|
68
73
|
end
|
@@ -41,12 +41,7 @@ module ShopifyCLI
|
|
41
41
|
|
42
42
|
raise ShopifyCLI::AbortSilent if form.nil?
|
43
43
|
|
44
|
-
|
45
|
-
context.abort(context.message("core.app.create.rails.error.invalid_ruby_version")) unless
|
46
|
-
ruby_version.satisfies?("~>2.5") || ruby_version.satisfies?("~>3.0.0")
|
47
|
-
|
48
|
-
check_node
|
49
|
-
check_yarn
|
44
|
+
check_dependencies
|
50
45
|
|
51
46
|
build(form.name, form.db)
|
52
47
|
set_custom_ua
|
@@ -106,6 +101,18 @@ module ShopifyCLI
|
|
106
101
|
end
|
107
102
|
end
|
108
103
|
|
104
|
+
def check_dependencies
|
105
|
+
check_ruby
|
106
|
+
check_node
|
107
|
+
check_yarn
|
108
|
+
end
|
109
|
+
|
110
|
+
def check_ruby
|
111
|
+
ruby_version = Rails::Ruby.version(context)
|
112
|
+
return if ruby_version.satisfies?("~>2.5") || ruby_version.satisfies?("~>3.0.0")
|
113
|
+
context.abort(context.message("core.app.create.rails.error.invalid_ruby_version"))
|
114
|
+
end
|
115
|
+
|
109
116
|
def check_node
|
110
117
|
cmd_path = context.which("node")
|
111
118
|
if cmd_path.nil?
|
@@ -148,11 +155,13 @@ module ShopifyCLI
|
|
148
155
|
end
|
149
156
|
|
150
157
|
def build(name, db)
|
151
|
-
|
152
|
-
"rails"
|
153
|
-
|
154
|
-
|
155
|
-
|
158
|
+
unless install_gem("rails")
|
159
|
+
context.abort(context.message("core.app.create.rails.error.install_failure", "rails"))
|
160
|
+
end
|
161
|
+
|
162
|
+
unless install_gem("bundler", "~>2.0")
|
163
|
+
context.abort(context.message("core.app.create.rails.error.install_failure", "bundler ~>2.0"))
|
164
|
+
end
|
156
165
|
|
157
166
|
full_path = File.join(context.root, name)
|
158
167
|
context.abort(context.message("core.app.create.rails.error.dir_exists", name)) if Dir.exist?(full_path)
|
@@ -173,7 +182,7 @@ module ShopifyCLI
|
|
173
182
|
|
174
183
|
context.puts(context.message("core.app.create.rails.adding_shopify_gem"))
|
175
184
|
File.open(File.join(context.root, "Gemfile"), "a") do |f|
|
176
|
-
f.puts "\ngem 'shopify_app', '>=
|
185
|
+
f.puts "\ngem 'shopify_app', '>=18.1.0'"
|
177
186
|
end
|
178
187
|
CLI::UI::Frame.open(context.message("core.app.create.rails.running_bundle_install")) do
|
179
188
|
syscall(%w(bundle install))
|
@@ -188,7 +197,7 @@ module ShopifyCLI
|
|
188
197
|
syscall(%w(rails db:migrate RAILS_ENV=development))
|
189
198
|
end
|
190
199
|
|
191
|
-
|
200
|
+
if install_webpacker?
|
192
201
|
CLI::UI::Frame.open(context.message("core.app.create.rails.running_webpacker_install")) do
|
193
202
|
syscall(%w(rails webpacker:install))
|
194
203
|
end
|
@@ -208,6 +217,21 @@ module ShopifyCLI
|
|
208
217
|
def install_gem(name, version = nil)
|
209
218
|
Rails::Gem.install(context, name, version)
|
210
219
|
end
|
220
|
+
|
221
|
+
def install_webpacker?
|
222
|
+
rails_version < ::Semantic::Version.new("7.0.0") &&
|
223
|
+
!File.exist?(File.join(context.root, "config/webpacker.yml"))
|
224
|
+
end
|
225
|
+
|
226
|
+
def rails_version
|
227
|
+
output, status = context.capture2e("rails", "--version")
|
228
|
+
unless status.success?
|
229
|
+
context.abort(context.message("core.app.create.rails.error.install_failure", "rails"))
|
230
|
+
end
|
231
|
+
|
232
|
+
version = output.scan(/Rails \d+\.\d+\.\d+/).first.split(" ").last
|
233
|
+
::Semantic::Version.new(version)
|
234
|
+
end
|
211
235
|
end
|
212
236
|
end
|
213
237
|
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ShopifyCLI
|
4
|
+
module Theme
|
5
|
+
module DevServer
|
6
|
+
class HotReload
|
7
|
+
class RemoteFileReloader
|
8
|
+
def initialize(ctx, theme:, streams:)
|
9
|
+
@ctx = ctx
|
10
|
+
@theme = theme
|
11
|
+
@streams = streams
|
12
|
+
end
|
13
|
+
|
14
|
+
def reload(file)
|
15
|
+
retries = 6
|
16
|
+
|
17
|
+
until retries.zero?
|
18
|
+
retries -= 1
|
19
|
+
|
20
|
+
_status, body = fetch_asset(file)
|
21
|
+
retries = 0 if updated_file?(body, file)
|
22
|
+
|
23
|
+
wait
|
24
|
+
end
|
25
|
+
|
26
|
+
notify(file)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
def updated_file?(body, file)
|
32
|
+
remote_checksum = body.dig("asset", "checksum")
|
33
|
+
local_checksum = file.checksum
|
34
|
+
|
35
|
+
remote_checksum == local_checksum
|
36
|
+
end
|
37
|
+
|
38
|
+
def notify(file)
|
39
|
+
@streams.broadcast(JSON.generate(modified: [file]))
|
40
|
+
@ctx.debug("[RemoteFileReloader] Modified #{file}")
|
41
|
+
end
|
42
|
+
|
43
|
+
def wait
|
44
|
+
sleep(1)
|
45
|
+
end
|
46
|
+
|
47
|
+
def fetch_asset(file)
|
48
|
+
ShopifyCLI::AdminAPI.rest_request(
|
49
|
+
@ctx,
|
50
|
+
shop: @theme.shop,
|
51
|
+
path: "themes/#{@theme.id}/assets.json",
|
52
|
+
method: "GET",
|
53
|
+
api_version: "unstable",
|
54
|
+
query: URI.encode_www_form("asset[key]" => file.relative_path.to_s),
|
55
|
+
)
|
56
|
+
rescue ShopifyCLI::API::APIRequestNotFoundError
|
57
|
+
[404, {}]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|