shopify-cli 1.14.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/CODEOWNERS +1 -1
- data/.github/CONTRIBUTING.md +7 -7
- data/.github/DESIGN.md +3 -3
- data/.github/workflows/build.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +3 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +35 -29
- data/Gemfile +4 -0
- data/Gemfile.lock +32 -0
- data/LICENSE +4 -1
- data/README.md +92 -26
- data/RELEASING.md +29 -7
- data/Rakefile +2 -2
- data/SECURITY.md +1 -1
- data/bin/load_shopify.rb +1 -1
- data/bin/shopify +3 -3
- data/dev.yml +1 -1
- data/docs/app/node/index.md +1 -1
- data/docs/app/rails/index.md +1 -1
- data/docs/core/index.md +1 -1
- data/docs/getting-started/index.md +1 -1
- data/docs/getting-started/install/index.md +1 -1
- data/docs/getting-started/migrate/index.md +1 -1
- data/docs/getting-started/uninstall/index.md +1 -1
- data/docs/getting-started/upgrade/index.md +1 -1
- data/docs/help/start-app/index.md +1 -1
- data/docs/index.md +1 -1
- data/ext/shopify-cli/extconf.rb +17 -5
- data/install.sh +1 -1
- data/lib/docgen/index_template.md.erb +2 -2
- data/lib/graphql/all_orgs_with_extensions.graphql +37 -0
- data/lib/graphql/find_organization.graphql +2 -1
- data/lib/project_types/extension/cli.rb +18 -15
- data/lib/project_types/extension/commands/build.rb +4 -5
- data/lib/project_types/extension/commands/connect.rb +35 -0
- data/lib/project_types/extension/commands/create.rb +12 -16
- data/lib/project_types/extension/commands/extension_command.rb +2 -2
- data/lib/project_types/extension/commands/info.rb +86 -0
- data/lib/project_types/extension/commands/push.rb +8 -7
- data/lib/project_types/extension/commands/register.rb +4 -5
- data/lib/project_types/extension/commands/serve.rb +5 -8
- data/lib/project_types/extension/commands/tunnel.rb +3 -1
- data/lib/project_types/extension/errors.rb +9 -0
- data/lib/project_types/extension/extension_project.rb +5 -0
- data/lib/project_types/extension/features/argo.rb +6 -6
- data/lib/project_types/extension/features/argo_runtime.rb +22 -66
- data/lib/project_types/extension/features/argo_serve.rb +25 -18
- data/lib/project_types/extension/forms/connect.rb +42 -0
- data/lib/project_types/extension/forms/questions/ask_name.rb +14 -6
- data/lib/project_types/extension/forms/questions/ask_registration.rb +51 -0
- data/lib/project_types/extension/messages/messages.rb +75 -11
- data/lib/project_types/extension/models/specification.rb +1 -0
- data/lib/project_types/extension/models/specification_handlers/{checkout_argo_extension.rb → checkout_ui_extension.rb} +3 -1
- data/lib/project_types/extension/models/specification_handlers/default.rb +13 -3
- data/lib/project_types/extension/models/specification_handlers/theme_app_extension.rb +86 -0
- data/lib/project_types/extension/models/specifications.rb +1 -0
- data/lib/project_types/extension/tasks/configure_features.rb +6 -7
- data/lib/project_types/extension/tasks/configure_options.rb +20 -0
- data/lib/project_types/extension/tasks/get_extensions.rb +32 -0
- data/lib/project_types/node/cli.rb +9 -21
- data/lib/project_types/node/commands/connect.rb +8 -2
- data/lib/project_types/node/commands/create.rb +9 -5
- data/lib/project_types/node/commands/deploy.rb +15 -5
- data/lib/project_types/node/commands/deploy/heroku.rb +29 -29
- data/lib/project_types/node/commands/generate.rb +4 -2
- data/lib/project_types/node/commands/open.rb +4 -2
- data/lib/project_types/node/commands/serve.rb +3 -2
- data/lib/project_types/node/commands/tunnel.rb +4 -2
- data/lib/project_types/node/messages/messages.rb +46 -89
- data/lib/project_types/rails/cli.rb +9 -21
- data/lib/project_types/rails/commands/connect.rb +8 -2
- data/lib/project_types/rails/commands/create.rb +10 -6
- data/lib/project_types/rails/commands/deploy.rb +15 -5
- data/lib/project_types/rails/commands/deploy/heroku.rb +84 -82
- data/lib/project_types/rails/commands/generate.rb +15 -5
- data/lib/project_types/rails/commands/generate/webhook.rb +28 -26
- data/lib/project_types/rails/commands/open.rb +4 -2
- data/lib/project_types/rails/commands/serve.rb +3 -2
- data/lib/project_types/rails/commands/tunnel.rb +4 -2
- data/lib/project_types/rails/messages/messages.rb +54 -101
- data/lib/project_types/script/cli.rb +5 -7
- data/lib/project_types/script/commands/create.rb +3 -1
- data/lib/project_types/script/commands/push.rb +4 -2
- data/lib/project_types/script/messages/messages.rb +52 -45
- data/lib/project_types/script/ui/error_handler.rb +2 -2
- data/lib/project_types/theme/cli.rb +15 -27
- data/lib/project_types/theme/commands/check.rb +33 -0
- data/lib/project_types/theme/commands/delete.rb +64 -0
- data/lib/project_types/theme/commands/language_server.rb +16 -0
- data/lib/project_types/theme/commands/package.rb +55 -0
- data/lib/project_types/theme/commands/publish.rb +43 -0
- data/lib/project_types/theme/commands/pull.rb +51 -0
- data/lib/project_types/theme/commands/push.rb +58 -32
- data/lib/project_types/theme/commands/serve.rb +7 -17
- data/lib/project_types/theme/forms/confirm_store.rb +15 -0
- data/lib/project_types/theme/forms/select.rb +59 -0
- data/lib/project_types/theme/messages/messages.rb +110 -106
- data/lib/project_types/theme/ui/sync_progress_bar.rb +20 -0
- data/lib/shopify-cli/admin_api.rb +53 -38
- data/lib/shopify-cli/admin_api/populate_resource_command.rb +6 -14
- data/lib/shopify-cli/admin_api/schema.rb +1 -10
- data/lib/shopify-cli/api.rb +29 -14
- data/lib/shopify-cli/command.rb +15 -3
- data/lib/shopify-cli/commands.rb +7 -2
- data/lib/shopify-cli/commands/help.rb +2 -29
- data/lib/shopify-cli/commands/login.rb +95 -0
- data/lib/shopify-cli/commands/logout.rb +24 -8
- data/lib/shopify-cli/commands/populate.rb +23 -0
- data/lib/{project_types/node → shopify-cli}/commands/populate/customer.rb +2 -8
- data/lib/{project_types/node → shopify-cli}/commands/populate/draft_order.rb +2 -2
- data/lib/{project_types/node → shopify-cli}/commands/populate/product.rb +2 -8
- data/lib/shopify-cli/commands/store.rb +15 -0
- data/lib/shopify-cli/commands/switch.rb +39 -0
- data/lib/shopify-cli/commands/system.rb +12 -0
- data/lib/shopify-cli/commands/whoami.rb +28 -0
- data/lib/shopify-cli/connect.rb +32 -0
- data/lib/shopify-cli/context.rb +52 -4
- data/lib/shopify-cli/core/entry_point.rb +3 -22
- data/lib/shopify-cli/db.rb +4 -4
- data/lib/shopify-cli/http_request.rb +10 -0
- data/lib/shopify-cli/identity_auth.rb +282 -0
- data/lib/shopify-cli/{oauth → identity_auth}/servlet.rb +11 -12
- data/lib/shopify-cli/messages/messages.rb +132 -39
- data/lib/shopify-cli/partners_api.rb +21 -44
- data/lib/shopify-cli/partners_api/organizations.rb +8 -0
- data/lib/shopify-cli/project_commands.rb +16 -0
- data/lib/shopify-cli/project_type.rb +0 -31
- data/lib/shopify-cli/shopifolk.rb +8 -11
- data/lib/shopify-cli/sub_command.rb +1 -0
- data/lib/shopify-cli/tasks.rb +3 -0
- data/lib/shopify-cli/tasks/confirm_store.rb +18 -0
- data/lib/shopify-cli/tasks/create_api_client.rb +2 -2
- data/lib/shopify-cli/tasks/ensure_authenticated.rb +13 -0
- data/lib/shopify-cli/tasks/ensure_loopback_url.rb +1 -1
- data/lib/shopify-cli/tasks/ensure_project_type.rb +12 -0
- data/lib/shopify-cli/tasks/select_org_and_shop.rb +0 -3
- data/lib/shopify-cli/theme/dev_server.rb +98 -0
- data/lib/shopify-cli/theme/dev_server/certificate_manager.rb +79 -0
- data/lib/shopify-cli/theme/dev_server/header_hash.rb +94 -0
- data/lib/shopify-cli/theme/dev_server/hot-reload.js +93 -0
- data/lib/shopify-cli/theme/dev_server/hot_reload.rb +76 -0
- data/lib/shopify-cli/theme/dev_server/local_assets.rb +87 -0
- data/lib/shopify-cli/theme/dev_server/proxy.rb +205 -0
- data/lib/shopify-cli/theme/dev_server/sse.rb +75 -0
- data/lib/shopify-cli/theme/dev_server/watcher.rb +59 -0
- data/lib/shopify-cli/theme/dev_server/web_server.rb +140 -0
- data/lib/shopify-cli/theme/development_theme.rb +69 -0
- data/lib/shopify-cli/theme/file.rb +112 -0
- data/lib/shopify-cli/theme/ignore_filter.rb +109 -0
- data/lib/shopify-cli/theme/mime_type.rb +34 -0
- data/lib/shopify-cli/theme/syncer.rb +328 -0
- data/lib/shopify-cli/theme/theme.rb +204 -0
- data/lib/shopify-cli/version.rb +1 -1
- data/lib/shopify_cli.rb +18 -11
- data/shopify-cli.gemspec +12 -5
- data/shopify.fish +1 -1
- data/shopify.sh +1 -1
- metadata +88 -34
- data/.github/workflows/release.yml +0 -59
- data/lib/project_types/extension/features/argo_serve_options.rb +0 -42
- data/lib/project_types/node/commands/populate.rb +0 -23
- data/lib/project_types/rails/commands/populate.rb +0 -23
- data/lib/project_types/rails/commands/populate/customer.rb +0 -31
- data/lib/project_types/rails/commands/populate/draft_order.rb +0 -28
- data/lib/project_types/rails/commands/populate/product.rb +0 -30
- data/lib/project_types/theme/commands/connect.rb +0 -54
- data/lib/project_types/theme/commands/create.rb +0 -48
- data/lib/project_types/theme/commands/deploy.rb +0 -38
- data/lib/project_types/theme/commands/generate.rb +0 -20
- data/lib/project_types/theme/commands/generate/env.rb +0 -79
- data/lib/project_types/theme/forms/connect.rb +0 -34
- data/lib/project_types/theme/forms/create.rb +0 -22
- data/lib/project_types/theme/tasks/ensure_themekit_installed.rb +0 -78
- data/lib/project_types/theme/themekit.rb +0 -113
- data/lib/shopify-cli/commands/connect.rb +0 -64
- data/lib/shopify-cli/commands/create.rb +0 -50
- data/lib/shopify-cli/oauth.rb +0 -198
@@ -1,34 +0,0 @@
|
|
1
|
-
module Theme
|
2
|
-
module Forms
|
3
|
-
class Connect < ShopifyCli::Form
|
4
|
-
attr_accessor :name
|
5
|
-
flag_arguments :themeid, :password, :store, :env
|
6
|
-
|
7
|
-
def ask
|
8
|
-
self.store ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.ask_store"), allow_empty: false)
|
9
|
-
ctx.puts(ctx.message("theme.forms.connect.private_app", store))
|
10
|
-
self.password ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.ask_password"), allow_empty: false)
|
11
|
-
|
12
|
-
errors = []
|
13
|
-
errors << "store" if store.strip.empty?
|
14
|
-
errors << "password" if password.strip.empty?
|
15
|
-
ctx.abort(ctx.message("theme.forms.errors", errors.join(", ").capitalize)) unless errors.empty?
|
16
|
-
|
17
|
-
self.themeid, self.name = ask_theme(store: store, password: password, themeid: themeid)
|
18
|
-
end
|
19
|
-
|
20
|
-
private
|
21
|
-
|
22
|
-
def ask_theme(store:, password:, themeid:)
|
23
|
-
themes = Themekit.query_themes(@ctx, store: store, password: password)
|
24
|
-
|
25
|
-
themeid ||= CLI::UI::Prompt.ask("Select theme") do |handler|
|
26
|
-
themes.each do |name, id|
|
27
|
-
handler.option(name) { id }
|
28
|
-
end
|
29
|
-
end
|
30
|
-
[themeid, themes.key(themeid.to_i)]
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
@@ -1,22 +0,0 @@
|
|
1
|
-
module Theme
|
2
|
-
module Forms
|
3
|
-
class Create < ShopifyCli::Form
|
4
|
-
attr_accessor :name
|
5
|
-
flag_arguments :title, :password, :store, :env
|
6
|
-
|
7
|
-
def ask
|
8
|
-
self.store ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.ask_store"), allow_empty: false)
|
9
|
-
ctx.puts(ctx.message("theme.forms.create.private_app", store))
|
10
|
-
self.password ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.ask_password"), allow_empty: false)
|
11
|
-
self.title ||= CLI::UI::Prompt.ask(ctx.message("theme.forms.create.ask_title"), allow_empty: false)
|
12
|
-
self.name = self.title.downcase.split(" ").join("_")
|
13
|
-
|
14
|
-
errors = []
|
15
|
-
errors << "store" if store.strip.empty?
|
16
|
-
errors << "password" if password.strip.empty?
|
17
|
-
errors << "title" if title.strip.empty?
|
18
|
-
ctx.abort(ctx.message("theme.forms.errors", errors.join(", ").capitalize)) unless errors.empty?
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
end
|
@@ -1,78 +0,0 @@
|
|
1
|
-
module Theme
|
2
|
-
module Tasks
|
3
|
-
class EnsureThemekitInstalled < ShopifyCli::Task
|
4
|
-
URL = "https://shopify-themekit.s3.amazonaws.com/releases/latest.json"
|
5
|
-
OSMAP = {
|
6
|
-
mac: "darwin-amd64",
|
7
|
-
linux: "linux-amd64",
|
8
|
-
windows: "windows-amd64",
|
9
|
-
}
|
10
|
-
VERSION_CHECK_INTERVAL = 604800
|
11
|
-
VERSION_CHECK_SECTION = "themekit_version_check"
|
12
|
-
LAST_CHECKED_AT_FIELD = "last_checked_at"
|
13
|
-
|
14
|
-
def call(ctx)
|
15
|
-
_out, stat = ctx.capture2e(Themekit::THEMEKIT)
|
16
|
-
unless stat.success?
|
17
|
-
CLI::UI::Frame.open(ctx.message("theme.tasks.ensure_themekit_installed.installing_themekit")) do
|
18
|
-
install_themekit(ctx)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
now = Time.now.to_i
|
23
|
-
if ShopifyCli::Feature.enabled?(:themekit_auto_update) && (time_of_last_check + VERSION_CHECK_INTERVAL) < now
|
24
|
-
CLI::UI::Frame.open(ctx.message("theme.tasks.ensure_themekit_installed.updating_themekit")) do
|
25
|
-
unless Themekit.update(ctx)
|
26
|
-
ctx.abort(ctx.message("theme.tasks.ensure_themekit_installed.errors.update_fail"))
|
27
|
-
end
|
28
|
-
update_time_of_last_check(now)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
end
|
32
|
-
|
33
|
-
private
|
34
|
-
|
35
|
-
def install_themekit(ctx)
|
36
|
-
require "json"
|
37
|
-
require "fileutils"
|
38
|
-
require "digest"
|
39
|
-
require "open-uri"
|
40
|
-
|
41
|
-
begin
|
42
|
-
begin
|
43
|
-
releases = JSON.parse(Net::HTTP.get(URI(URL)))
|
44
|
-
release = releases["platforms"].find { |r| r["name"] == OSMAP[ctx.os] }
|
45
|
-
rescue
|
46
|
-
ctx.abort(ctx.message("theme.tasks.ensure_themekit_installed.errors.releases_fail"))
|
47
|
-
end
|
48
|
-
|
49
|
-
ctx.puts(ctx.message("theme.tasks.ensure_themekit_installed.downloading", releases["version"]))
|
50
|
-
_out, stat = ctx.capture2e("curl", "-o", Themekit::THEMEKIT, release["url"])
|
51
|
-
ctx.abort(ctx.message("theme.tasks.ensure_themekit_installed.errors.write_fail")) unless stat.success?
|
52
|
-
|
53
|
-
ctx.puts(ctx.message("theme.tasks.ensure_themekit_installed.verifying"))
|
54
|
-
if Digest::MD5.file(Themekit::THEMEKIT) == release["digest"]
|
55
|
-
FileUtils.chmod("+x", Themekit::THEMEKIT)
|
56
|
-
ctx.puts(ctx.message("theme.tasks.ensure_themekit_installed.successful"))
|
57
|
-
|
58
|
-
auto = CLI::UI.confirm(ctx.message("theme.tasks.ensure_themekit_installed.auto_update"))
|
59
|
-
ShopifyCli::Feature.set(:themekit_auto_update, auto)
|
60
|
-
else
|
61
|
-
ctx.abort(ctx.message("theme.tasks.ensure_themekit_installed.errors.digest_fail"))
|
62
|
-
end
|
63
|
-
rescue StandardError, ShopifyCli::Abort => e
|
64
|
-
FileUtils.rm(Themekit::THEMEKIT) if File.exist?(Themekit::THEMEKIT)
|
65
|
-
raise e
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
def time_of_last_check
|
70
|
-
(val = ShopifyCli::Config.get(VERSION_CHECK_SECTION, LAST_CHECKED_AT_FIELD)) ? val.to_i : 0
|
71
|
-
end
|
72
|
-
|
73
|
-
def update_time_of_last_check(time)
|
74
|
-
ShopifyCli::Config.set(VERSION_CHECK_SECTION, LAST_CHECKED_AT_FIELD, time)
|
75
|
-
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
@@ -1,113 +0,0 @@
|
|
1
|
-
module Theme
|
2
|
-
class Themekit
|
3
|
-
THEMEKIT = File.join(ShopifyCli.cache_dir, "themekit")
|
4
|
-
|
5
|
-
class << self
|
6
|
-
def add_flags(flags)
|
7
|
-
flags.map do |key, value|
|
8
|
-
flag = "--#{key}"
|
9
|
-
flag += "=#{value}" if value.is_a?(String)
|
10
|
-
flag
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
def connect(ctx, store:, password:, themeid:, env:)
|
15
|
-
command = build_command("get", env)
|
16
|
-
command << "--password=#{password}"
|
17
|
-
command << "--store=#{store}"
|
18
|
-
command << "--themeid=#{themeid}"
|
19
|
-
|
20
|
-
stat = ctx.system(*command)
|
21
|
-
stat.success?
|
22
|
-
end
|
23
|
-
|
24
|
-
def create(ctx, password:, store:, name:, env:)
|
25
|
-
command = build_command("new", env)
|
26
|
-
command << "--password=#{password}"
|
27
|
-
command << "--store=#{store}"
|
28
|
-
command << "--name=#{name}"
|
29
|
-
|
30
|
-
stat = ctx.system(*command)
|
31
|
-
stat.success?
|
32
|
-
end
|
33
|
-
|
34
|
-
def deploy(ctx, flags: nil, env:)
|
35
|
-
unless push(ctx, flags: flags, env: env)
|
36
|
-
ctx.abort(ctx.message("theme.deploy.push_fail"))
|
37
|
-
end
|
38
|
-
ctx.done(ctx.message("theme.deploy.info.pushed"))
|
39
|
-
|
40
|
-
command = build_command("publish", env)
|
41
|
-
(command << flags).compact!
|
42
|
-
command.flatten!
|
43
|
-
|
44
|
-
stat = ctx.system(*command)
|
45
|
-
stat.success?
|
46
|
-
end
|
47
|
-
|
48
|
-
def generate_env(ctx, store:, password:, themeid:, env:)
|
49
|
-
command = build_command("configure", env)
|
50
|
-
command << "--password=#{password}"
|
51
|
-
command << "--store=#{store}"
|
52
|
-
command << "--themeid=#{themeid}"
|
53
|
-
|
54
|
-
stat = ctx.system(*command)
|
55
|
-
stat.success?
|
56
|
-
end
|
57
|
-
|
58
|
-
def push(ctx, files: nil, flags: nil, remove: false, env:)
|
59
|
-
action = remove ? "remove" : "deploy"
|
60
|
-
command = build_command(action, env)
|
61
|
-
|
62
|
-
(command << files << flags).compact!
|
63
|
-
command.flatten!
|
64
|
-
|
65
|
-
stat = ctx.system(*command)
|
66
|
-
stat.success?
|
67
|
-
end
|
68
|
-
|
69
|
-
def query_themes(ctx, store:, password:)
|
70
|
-
begin
|
71
|
-
resp = ::ShopifyCli::AdminAPI.rest_request(
|
72
|
-
ctx,
|
73
|
-
shop: store,
|
74
|
-
token: password,
|
75
|
-
path: "themes.json",
|
76
|
-
)
|
77
|
-
rescue ShopifyCli::API::APIRequestUnauthorizedError
|
78
|
-
ctx.abort(ctx.message("theme.themekit.query_themes.bad_password"))
|
79
|
-
rescue StandardError
|
80
|
-
ctx.abort(ctx.message("theme.themekit.query_themes.not_connect"))
|
81
|
-
end
|
82
|
-
|
83
|
-
resp[1]["themes"].map { |theme| [theme["name"], theme["id"]] }.to_h
|
84
|
-
end
|
85
|
-
|
86
|
-
def serve(ctx, flags: nil, env:)
|
87
|
-
command = build_command("open", env)
|
88
|
-
out, stat = ctx.capture2e(*command)
|
89
|
-
ctx.puts(out)
|
90
|
-
ctx.abort(ctx.message("theme.serve.open_fail")) unless stat.success?
|
91
|
-
|
92
|
-
command = build_command("watch", env)
|
93
|
-
(command << flags).compact!
|
94
|
-
command.flatten!
|
95
|
-
ctx.system(*command)
|
96
|
-
end
|
97
|
-
|
98
|
-
def update(ctx)
|
99
|
-
command = build_command("update")
|
100
|
-
ctx.system(*command)
|
101
|
-
end
|
102
|
-
|
103
|
-
private
|
104
|
-
|
105
|
-
def build_command(action, env = nil)
|
106
|
-
command = [THEMEKIT, action]
|
107
|
-
command << "--no-update-notifier"
|
108
|
-
command << "--env=#{env}" if env
|
109
|
-
command
|
110
|
-
end
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
require "shopify_cli"
|
2
|
-
|
3
|
-
module ShopifyCli
|
4
|
-
module Commands
|
5
|
-
class Connect < ShopifyCli::Command
|
6
|
-
class << self
|
7
|
-
def call(args, command_name)
|
8
|
-
ProjectType.load_type(args[0]) unless args.empty?
|
9
|
-
super
|
10
|
-
end
|
11
|
-
|
12
|
-
def help
|
13
|
-
ShopifyCli::Context.message("core.connect.help", ShopifyCli::TOOL_NAME)
|
14
|
-
end
|
15
|
-
end
|
16
|
-
|
17
|
-
def call(args, command_name)
|
18
|
-
if Project.current&.env
|
19
|
-
@ctx.puts(@ctx.message("core.connect.already_connected_warning"))
|
20
|
-
end
|
21
|
-
|
22
|
-
project_type = ask_project_type
|
23
|
-
|
24
|
-
klass = ProjectType.load_type(project_type)&.connect_command
|
25
|
-
|
26
|
-
if klass
|
27
|
-
klass.ctx = @ctx
|
28
|
-
klass.call(args, command_name, "connect")
|
29
|
-
else
|
30
|
-
app = default_connect(project_type)
|
31
|
-
@ctx.done(@ctx.message("core.connect.connected", app))
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
def ask_project_type
|
36
|
-
CLI::UI::Prompt.ask(@ctx.message("core.connect.project_type_select")) do |handler|
|
37
|
-
ShopifyCli::Commands::Create.all_visible_type.each do |type|
|
38
|
-
handler.option(type.project_name) { type.project_type }
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def default_connect(project_type)
|
44
|
-
org = ShopifyCli::Tasks::EnsureEnv.call(@ctx, regenerate: true)
|
45
|
-
write_cli_yml(project_type, org["id"]) unless Project.has_current?
|
46
|
-
api_key = Project.current(force_reload: true).env["api_key"]
|
47
|
-
get_app(org["apps"], api_key).first["title"]
|
48
|
-
end
|
49
|
-
|
50
|
-
def write_cli_yml(project_type, org_id)
|
51
|
-
ShopifyCli::Project.write(
|
52
|
-
@ctx,
|
53
|
-
project_type: project_type,
|
54
|
-
organization_id: org_id,
|
55
|
-
)
|
56
|
-
@ctx.done(@ctx.message("core.connect.cli_yml_saved"))
|
57
|
-
end
|
58
|
-
|
59
|
-
def get_app(apps, api_key)
|
60
|
-
apps.select { |app| app["apiKey"] == api_key }
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,50 +0,0 @@
|
|
1
|
-
require "shopify_cli"
|
2
|
-
|
3
|
-
module ShopifyCli
|
4
|
-
module Commands
|
5
|
-
class Create < ShopifyCli::Command
|
6
|
-
def self.call(args, command_name)
|
7
|
-
ProjectType.load_type(args[0]) unless args.empty?
|
8
|
-
super
|
9
|
-
end
|
10
|
-
|
11
|
-
def call(args, command_name)
|
12
|
-
unless args.empty?
|
13
|
-
@ctx.puts(@ctx.message("core.create.error.invalid_app_type", args[0]))
|
14
|
-
return @ctx.puts(self.class.help)
|
15
|
-
end
|
16
|
-
|
17
|
-
type_name = CLI::UI::Prompt.ask(@ctx.message("core.create.project_type_select")) do |handler|
|
18
|
-
self.class.all_visible_type.each do |type|
|
19
|
-
handler.option(type.project_name) { type.project_type }
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
klass = ProjectType.load_type(type_name).create_command
|
24
|
-
klass.ctx = @ctx
|
25
|
-
klass.call(args, command_name, "create")
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.all_visible_type
|
29
|
-
ProjectType
|
30
|
-
.load_all
|
31
|
-
.select { |type| !type.hidden? }
|
32
|
-
end
|
33
|
-
|
34
|
-
def self.help
|
35
|
-
project_types = all_visible_type.map(&:project_type).sort.join(" | ")
|
36
|
-
ShopifyCli::Context.message("core.create.help", ShopifyCli::TOOL_NAME, project_types)
|
37
|
-
end
|
38
|
-
|
39
|
-
def self.extended_help
|
40
|
-
<<~HELP
|
41
|
-
#{
|
42
|
-
all_visible_type.map do |type|
|
43
|
-
type.create_command.help
|
44
|
-
end.join("\n")
|
45
|
-
}
|
46
|
-
HELP
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
end
|
data/lib/shopify-cli/oauth.rb
DELETED
@@ -1,198 +0,0 @@
|
|
1
|
-
require "base64"
|
2
|
-
require "digest"
|
3
|
-
require "json"
|
4
|
-
require "net/http"
|
5
|
-
require "securerandom"
|
6
|
-
require "openssl"
|
7
|
-
require "shopify_cli"
|
8
|
-
require "uri"
|
9
|
-
require "webrick"
|
10
|
-
|
11
|
-
module ShopifyCli
|
12
|
-
class OAuth
|
13
|
-
include SmartProperties
|
14
|
-
|
15
|
-
autoload :Servlet, "shopify-cli/oauth/servlet"
|
16
|
-
|
17
|
-
class Error < StandardError; end
|
18
|
-
LocalRequest = Struct.new(:method, :path, :query, :protocol)
|
19
|
-
|
20
|
-
DEFAULT_PORT = 3456
|
21
|
-
REDIRECT_HOST = "http://127.0.0.1:#{DEFAULT_PORT}"
|
22
|
-
|
23
|
-
property! :ctx
|
24
|
-
property! :service, accepts: String
|
25
|
-
property! :client_id, accepts: String
|
26
|
-
property! :scopes
|
27
|
-
property :store, default: -> { ShopifyCli::DB.new }
|
28
|
-
property :secret, accepts: String
|
29
|
-
property :request_exchange, accepts: String
|
30
|
-
property :options, default: -> { {} }, accepts: Hash
|
31
|
-
property :auth_path, default: "/authorize", accepts: ->(path) { path.is_a?(String) && path.start_with?("/") }
|
32
|
-
property :token_path, default: "/token", accepts: ->(path) { path.is_a?(String) && path.start_with?("/") }
|
33
|
-
property :state_token, accepts: String, default: SecureRandom.hex(30)
|
34
|
-
property :code_verifier, accepts: String, default: SecureRandom.hex(30)
|
35
|
-
|
36
|
-
attr_accessor :response_query
|
37
|
-
|
38
|
-
def authenticate(url)
|
39
|
-
return if refresh_exchange_token(url)
|
40
|
-
return if refresh_access_token(url)
|
41
|
-
initiate_authentication(url)
|
42
|
-
request_access_token(url, code: receive_access_code)
|
43
|
-
request_exchange_token(url) if should_exchange
|
44
|
-
end
|
45
|
-
|
46
|
-
def code_challenge
|
47
|
-
@code_challenge ||= Base64.urlsafe_encode64(
|
48
|
-
OpenSSL::Digest::SHA256.digest(code_verifier),
|
49
|
-
padding: false,
|
50
|
-
)
|
51
|
-
end
|
52
|
-
|
53
|
-
def server
|
54
|
-
@server ||= begin
|
55
|
-
server = WEBrick::HTTPServer.new(
|
56
|
-
Port: DEFAULT_PORT,
|
57
|
-
Logger: WEBrick::Log.new(File.open(File::NULL, "w")),
|
58
|
-
AccessLog: [],
|
59
|
-
)
|
60
|
-
server.mount("/", Servlet, self, state_token)
|
61
|
-
server
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
private
|
66
|
-
|
67
|
-
def initiate_authentication(url)
|
68
|
-
@server_thread = Thread.new { server.start }
|
69
|
-
params = {
|
70
|
-
client_id: client_id,
|
71
|
-
scope: scopes,
|
72
|
-
redirect_uri: REDIRECT_HOST,
|
73
|
-
state: state_token,
|
74
|
-
response_type: :code,
|
75
|
-
}
|
76
|
-
params.merge!(challange_params) if secret.nil?
|
77
|
-
uri = URI.parse("#{url}#{auth_path}")
|
78
|
-
uri.query = URI.encode_www_form(params.merge(options))
|
79
|
-
output_authentication_info(uri)
|
80
|
-
end
|
81
|
-
|
82
|
-
def output_authentication_info(uri)
|
83
|
-
login_location = if service == "admin"
|
84
|
-
ctx.message("core.oauth.location.admin")
|
85
|
-
elsif Shopifolk.acting_as_shopify_organization?
|
86
|
-
ctx.message("core.oauth.location.shopifolk")
|
87
|
-
else
|
88
|
-
ctx.message("core.oauth.location.partner")
|
89
|
-
end
|
90
|
-
ctx.puts(ctx.message("core.oauth.authentication_required", login_location))
|
91
|
-
ctx.open_url!(uri)
|
92
|
-
end
|
93
|
-
|
94
|
-
def receive_access_code
|
95
|
-
@access_code ||= begin
|
96
|
-
@server_thread.join(240)
|
97
|
-
raise Error, ctx.message("core.oauth.error.timeout") if response_query.nil?
|
98
|
-
raise Error, response_query["error_description"] unless response_query["error"].nil?
|
99
|
-
response_query["code"]
|
100
|
-
end
|
101
|
-
end
|
102
|
-
|
103
|
-
def request_access_token(url, code:)
|
104
|
-
resp = post_token_request(
|
105
|
-
"#{url}#{token_path}",
|
106
|
-
{
|
107
|
-
grant_type: :authorization_code,
|
108
|
-
code: code,
|
109
|
-
redirect_uri: REDIRECT_HOST,
|
110
|
-
client_id: client_id,
|
111
|
-
}.merge(confirmation_param)
|
112
|
-
)
|
113
|
-
store.set(
|
114
|
-
"#{service}_access_token".to_sym => resp["access_token"],
|
115
|
-
"#{service}_refresh_token".to_sym => resp["refresh_token"],
|
116
|
-
)
|
117
|
-
end
|
118
|
-
|
119
|
-
def refresh_access_token(url)
|
120
|
-
return false if !store.exists?("#{service}_access_token".to_sym) ||
|
121
|
-
!store.exists?("#{service}_refresh_token".to_sym)
|
122
|
-
refresh_token(url)
|
123
|
-
request_exchange_token(url) if should_exchange
|
124
|
-
true
|
125
|
-
rescue
|
126
|
-
store.del("#{service}_access_token".to_sym, "#{service}_refresh_token".to_sym)
|
127
|
-
false
|
128
|
-
end
|
129
|
-
|
130
|
-
def refresh_token(url)
|
131
|
-
resp = post_token_request(
|
132
|
-
"#{url}#{token_path}",
|
133
|
-
grant_type: :refresh_token,
|
134
|
-
access_token: store.get("#{service}_access_token".to_sym),
|
135
|
-
refresh_token: store.get("#{service}_refresh_token".to_sym),
|
136
|
-
client_id: client_id,
|
137
|
-
)
|
138
|
-
store.set(
|
139
|
-
"#{service}_access_token".to_sym => resp["access_token"],
|
140
|
-
"#{service}_refresh_token".to_sym => resp["refresh_token"],
|
141
|
-
)
|
142
|
-
end
|
143
|
-
|
144
|
-
def refresh_exchange_token(url)
|
145
|
-
return false if !should_exchange || !store.exists?("#{service}_exchange_token".to_sym)
|
146
|
-
request_exchange_token(url)
|
147
|
-
true
|
148
|
-
rescue
|
149
|
-
store.del("#{service}_exchange_token".to_sym)
|
150
|
-
false
|
151
|
-
end
|
152
|
-
|
153
|
-
def request_exchange_token(url)
|
154
|
-
resp = post_token_request(
|
155
|
-
"#{url}#{token_path}",
|
156
|
-
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
157
|
-
requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
|
158
|
-
subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
|
159
|
-
client_id: client_id,
|
160
|
-
audience: request_exchange,
|
161
|
-
scope: scopes,
|
162
|
-
subject_token: store.get("#{service}_access_token".to_sym),
|
163
|
-
)
|
164
|
-
store.set("#{service}_exchange_token".to_sym => resp["access_token"])
|
165
|
-
end
|
166
|
-
|
167
|
-
def post_token_request(url, params)
|
168
|
-
uri = URI.parse(url)
|
169
|
-
https = Net::HTTP.new(uri.host, uri.port)
|
170
|
-
https.use_ssl = true
|
171
|
-
request = Net::HTTP::Post.new(uri.path)
|
172
|
-
request["User-Agent"] = "Shopify App CLI #{::ShopifyCli::VERSION}"
|
173
|
-
request.body = URI.encode_www_form(params)
|
174
|
-
res = https.request(request)
|
175
|
-
raise Error, JSON.parse(res.body)["error_description"] unless res.is_a?(Net::HTTPSuccess)
|
176
|
-
JSON.parse(res.body)
|
177
|
-
end
|
178
|
-
|
179
|
-
def challange_params
|
180
|
-
{
|
181
|
-
code_challenge: code_challenge,
|
182
|
-
code_challenge_method: "S256",
|
183
|
-
}
|
184
|
-
end
|
185
|
-
|
186
|
-
def confirmation_param
|
187
|
-
if secret.nil?
|
188
|
-
{ code_verifier: code_verifier }
|
189
|
-
else
|
190
|
-
{ client_secret: secret }
|
191
|
-
end
|
192
|
-
end
|
193
|
-
|
194
|
-
def should_exchange
|
195
|
-
!request_exchange.nil? && !request_exchange.empty?
|
196
|
-
end
|
197
|
-
end
|
198
|
-
end
|