shopify-cli 2.10.1 → 2.11.2
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.
- 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
|