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,6 +1,6 @@
|
|
1
1
|
require "shopify_cli"
|
2
2
|
|
3
|
-
module
|
3
|
+
module ShopifyCli
|
4
4
|
module Commands
|
5
5
|
class Populate
|
6
6
|
class Customer < ShopifyCli::AdminAPI::PopulateResourceCommand
|
@@ -17,13 +17,7 @@ module Node
|
|
17
17
|
def message(data)
|
18
18
|
ret = data["customerCreate"]["customer"]
|
19
19
|
id = ShopifyCli::API.gid_to_id(ret["id"])
|
20
|
-
@ctx.message(
|
21
|
-
"node.populate.customer.added",
|
22
|
-
ret["displayName"],
|
23
|
-
ShopifyCli::Project.current.env.shop,
|
24
|
-
admin_url,
|
25
|
-
id
|
26
|
-
)
|
20
|
+
@ctx.message("core.populate.customer.added", ret["displayName"], @shop, admin_url, id)
|
27
21
|
end
|
28
22
|
end
|
29
23
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require "shopify_cli"
|
2
2
|
|
3
|
-
module
|
3
|
+
module ShopifyCli
|
4
4
|
module Commands
|
5
5
|
class Populate
|
6
6
|
class DraftOrder < ShopifyCli::AdminAPI::PopulateResourceCommand
|
@@ -20,7 +20,7 @@ module Node
|
|
20
20
|
def message(data)
|
21
21
|
ret = data["draftOrderCreate"]["draftOrder"]
|
22
22
|
id = ShopifyCli::API.gid_to_id(ret["id"])
|
23
|
-
@ctx.message("
|
23
|
+
@ctx.message("core.populate.draft_order.added", @shop, admin_url, id)
|
24
24
|
end
|
25
25
|
end
|
26
26
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
require "shopify_cli"
|
2
2
|
|
3
|
-
module
|
3
|
+
module ShopifyCli
|
4
4
|
module Commands
|
5
5
|
class Populate
|
6
6
|
class Product < ShopifyCli::AdminAPI::PopulateResourceCommand
|
@@ -16,13 +16,7 @@ module Node
|
|
16
16
|
def message(data)
|
17
17
|
ret = data["productCreate"]["product"]
|
18
18
|
id = ShopifyCli::API.gid_to_id(ret["id"])
|
19
|
-
@ctx.message(
|
20
|
-
"node.populate.product.added",
|
21
|
-
ret["title"],
|
22
|
-
ShopifyCli::Project.current.env.shop,
|
23
|
-
admin_url,
|
24
|
-
id
|
25
|
-
)
|
19
|
+
@ctx.message("core.populate.product.added", ret["title"], @shop, admin_url, id)
|
26
20
|
end
|
27
21
|
end
|
28
22
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "shopify_cli"
|
2
|
+
|
3
|
+
module ShopifyCli
|
4
|
+
module Commands
|
5
|
+
class Store < ShopifyCli::Command
|
6
|
+
def call(_args, _name)
|
7
|
+
@ctx.puts(@ctx.message("core.store.shop", ShopifyCli::AdminAPI.get_shop_or_abort(@ctx)))
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.help
|
11
|
+
ShopifyCli::Context.message("core.store.help", ShopifyCli::TOOL_NAME)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require "shopify_cli"
|
2
|
+
|
3
|
+
module ShopifyCli
|
4
|
+
module Commands
|
5
|
+
class Switch < ShopifyCli::Command
|
6
|
+
options do |parser, flags|
|
7
|
+
parser.on("--store=STORE") { |url| flags[:shop] = url }
|
8
|
+
# backwards compatibility allow 'shop' for now
|
9
|
+
parser.on("--shop=SHOP") { |url| flags[:shop] = url }
|
10
|
+
end
|
11
|
+
|
12
|
+
def call(*)
|
13
|
+
if Shopifolk.acting_as_shopify_organization?
|
14
|
+
@ctx.puts(@ctx.message("core.switch.disabled_as_shopify_org"))
|
15
|
+
return
|
16
|
+
end
|
17
|
+
|
18
|
+
shop = if options.flags[:shop]
|
19
|
+
Login.validate_shop(options.flags[:shop])
|
20
|
+
elsif (org_id = DB.get(:organization_id))
|
21
|
+
res = ShopifyCli::Tasks::SelectOrgAndShop.call(@ctx, organization_id: org_id)
|
22
|
+
res[:shop_domain]
|
23
|
+
else
|
24
|
+
AdminAPI.get_shop_or_abort(@ctx)
|
25
|
+
res = ShopifyCli::Tasks::SelectOrgAndShop.call(@ctx)
|
26
|
+
res[:shop_domain]
|
27
|
+
end
|
28
|
+
DB.set(shop: shop)
|
29
|
+
IdentityAuth.new(ctx: @ctx).reauthenticate
|
30
|
+
|
31
|
+
@ctx.puts(@ctx.message("core.switch.success", shop))
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.help
|
35
|
+
ShopifyCli::Context.message("core.switch.help", ShopifyCli::TOOL_NAME)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -20,6 +20,7 @@ module ShopifyCli
|
|
20
20
|
display_environment if show_all_details
|
21
21
|
|
22
22
|
display_cli_constants(show_all_details)
|
23
|
+
display_shopify_store(show_all_details)
|
23
24
|
display_cli_ruby(show_all_details)
|
24
25
|
display_utility_commands(show_all_details)
|
25
26
|
display_project_commands(show_all_details)
|
@@ -59,6 +60,17 @@ module ShopifyCli
|
|
59
60
|
end
|
60
61
|
end
|
61
62
|
|
63
|
+
def display_shopify_store(_show_all_details)
|
64
|
+
shop = if ShopifyCli::DB.exists?(:shop)
|
65
|
+
ShopifyCli::AdminAPI.get_shop_or_abort(@ctx)
|
66
|
+
else
|
67
|
+
@ctx.message("core.populate.error.no_shop", ShopifyCli::TOOL_NAME)
|
68
|
+
end
|
69
|
+
|
70
|
+
@ctx.puts("\n" + @ctx.message("core.system.shop_header"))
|
71
|
+
@ctx.puts(" " + shop)
|
72
|
+
end
|
73
|
+
|
62
74
|
def display_cli_ruby(_show_all_details)
|
63
75
|
rbconfig_constants = %w(host RUBY_VERSION_NAME)
|
64
76
|
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require "shopify_cli"
|
2
|
+
|
3
|
+
module ShopifyCli
|
4
|
+
module Commands
|
5
|
+
class Whoami < ShopifyCli::Command
|
6
|
+
def call(_args, _name)
|
7
|
+
shop = ShopifyCli::DB.get(:shop)
|
8
|
+
org_id = ShopifyCli::DB.get(:organization_id)
|
9
|
+
org = ShopifyCli::PartnersAPI::Organizations.fetch(@ctx, id: org_id) unless org_id.nil?
|
10
|
+
|
11
|
+
output = if shop.nil? && org.nil?
|
12
|
+
@ctx.message("core.whoami.not_logged_in", ShopifyCli::TOOL_NAME)
|
13
|
+
elsif !shop.nil? && org.nil?
|
14
|
+
@ctx.message("core.whoami.logged_in_shop_only", shop)
|
15
|
+
elsif shop.nil? && !org.nil?
|
16
|
+
@ctx.message("core.whoami.logged_in_partner_only", org["businessName"])
|
17
|
+
else
|
18
|
+
@ctx.message("core.whoami.logged_in_partner_and_shop", shop, org["businessName"])
|
19
|
+
end
|
20
|
+
@ctx.puts(output)
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.help
|
24
|
+
ShopifyCli::Context.message("core.whoami.help", ShopifyCli::TOOL_NAME)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require "shopify_cli"
|
2
|
+
|
3
|
+
module ShopifyCli
|
4
|
+
class Connect
|
5
|
+
def initialize(ctx)
|
6
|
+
@ctx = ctx
|
7
|
+
end
|
8
|
+
|
9
|
+
def default_connect(project_type)
|
10
|
+
if Project.current&.env
|
11
|
+
@ctx.puts(@ctx.message("core.connect.already_connected_warning"))
|
12
|
+
end
|
13
|
+
org = ShopifyCli::Tasks::EnsureEnv.call(@ctx, regenerate: true)
|
14
|
+
write_cli_yml(project_type, org["id"]) unless Project.has_current?
|
15
|
+
api_key = Project.current(force_reload: true).env["api_key"]
|
16
|
+
get_app(org["apps"], api_key).first["title"]
|
17
|
+
end
|
18
|
+
|
19
|
+
def write_cli_yml(project_type, org_id)
|
20
|
+
ShopifyCli::Project.write(
|
21
|
+
@ctx,
|
22
|
+
project_type: project_type,
|
23
|
+
organization_id: org_id,
|
24
|
+
)
|
25
|
+
@ctx.done(@ctx.message("core.connect.cli_yml_saved"))
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_app(apps, api_key)
|
29
|
+
apps.select { |app| app["apiKey"] == api_key }
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/shopify-cli/context.rb
CHANGED
@@ -61,9 +61,10 @@ module ShopifyCli
|
|
61
61
|
# will return which operating system that the cli is running on [:mac, :linux]
|
62
62
|
def os
|
63
63
|
host = uname
|
64
|
-
return :mac if /darwin
|
65
|
-
return :
|
66
|
-
return :
|
64
|
+
return :mac if /darwin/i.match(host)
|
65
|
+
return :windows if /mswin|mingw|cygwin/i.match(host)
|
66
|
+
return :linux if /linux|bsd/i.match(host)
|
67
|
+
:unknown
|
67
68
|
end
|
68
69
|
|
69
70
|
# will return true if the cli is running on an apple computer.
|
@@ -81,6 +82,16 @@ module ShopifyCli
|
|
81
82
|
os == :windows
|
82
83
|
end
|
83
84
|
|
85
|
+
# will return true if the os is unknown
|
86
|
+
def unknown_os?
|
87
|
+
os == :unknown
|
88
|
+
end
|
89
|
+
|
90
|
+
# will return true if being launched from a tty
|
91
|
+
def tty?
|
92
|
+
$stdin.tty? && !testing?
|
93
|
+
end
|
94
|
+
|
84
95
|
# will return true if the cli is being run from an installation, and not a
|
85
96
|
# development instance. The gem installation will not have a 'test' directory.
|
86
97
|
# See `#development?` for checking for development environment.
|
@@ -106,6 +117,12 @@ module ShopifyCli
|
|
106
117
|
ENV["CI"]
|
107
118
|
end
|
108
119
|
|
120
|
+
##
|
121
|
+
# will return true if the cli is running with the DEBUG flag
|
122
|
+
def debug?
|
123
|
+
getenv("DEBUG")
|
124
|
+
end
|
125
|
+
|
109
126
|
# get a environment variable value by name.
|
110
127
|
#
|
111
128
|
# #### Parameters
|
@@ -299,6 +316,28 @@ module ShopifyCli
|
|
299
316
|
puts(help)
|
300
317
|
end
|
301
318
|
|
319
|
+
# will output to the console a link for the user to either copy/paste
|
320
|
+
# or click on.
|
321
|
+
#
|
322
|
+
# #### Parameters
|
323
|
+
# * `uri` - a http URI to open in a browser
|
324
|
+
#
|
325
|
+
def open_browser_url!(uri)
|
326
|
+
if tty?
|
327
|
+
if linux? && which("xdg-open")
|
328
|
+
system("xdg-open", uri.to_s)
|
329
|
+
elsif windows?
|
330
|
+
system("start", uri.to_s)
|
331
|
+
elsif mac?
|
332
|
+
system("open", uri.to_s)
|
333
|
+
else
|
334
|
+
open_url!(uri)
|
335
|
+
end
|
336
|
+
else
|
337
|
+
open_url!(uri)
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
302
341
|
# will output a message, prefixed by a yellow star, indicating that task
|
303
342
|
# started.
|
304
343
|
#
|
@@ -318,6 +357,15 @@ module ShopifyCli
|
|
318
357
|
Kernel.puts(CLI::UI.fmt(*args))
|
319
358
|
end
|
320
359
|
|
360
|
+
# a wrapper around Kernel.warn to allow for easy formatting
|
361
|
+
#
|
362
|
+
# #### Parameters
|
363
|
+
# * `text` - a string message to output
|
364
|
+
#
|
365
|
+
def warn(*args)
|
366
|
+
Kernel.warn(CLI::UI.fmt(*args))
|
367
|
+
end
|
368
|
+
|
321
369
|
# outputs a message, prefixed by a checkmark indicating that something completed
|
322
370
|
#
|
323
371
|
# #### Parameters
|
@@ -344,7 +392,7 @@ module ShopifyCli
|
|
344
392
|
# * `text` - a string message to output
|
345
393
|
#
|
346
394
|
def debug(text)
|
347
|
-
puts("{{red:DEBUG}} #{text}") if
|
395
|
+
puts("{{red:DEBUG}} #{text}") if debug?
|
348
396
|
end
|
349
397
|
|
350
398
|
# proxy call to Context.message.
|
@@ -5,35 +5,16 @@ module ShopifyCli
|
|
5
5
|
module EntryPoint
|
6
6
|
class << self
|
7
7
|
def call(args, ctx = Context.new)
|
8
|
-
# Check if the shim is set up by checking whether the old Finalizer FD exists
|
9
|
-
begin
|
10
|
-
is_shell_shim = false
|
11
|
-
IO.open(9) { is_shell_shim = true }
|
12
|
-
rescue Errno::EBADF
|
13
|
-
# This is expected if the descriptor doesn't exist
|
14
|
-
rescue ArgumentError => e
|
15
|
-
# This can happen on RVM, because it can use fd 9 itself and block access to it. That only happens if the fd
|
16
|
-
# did not exist beforehand, so that means there was no fd 9 before Ruby started.
|
17
|
-
unless e.message == "The given fd is not accessible because RubyVM reserves it"
|
18
|
-
raise e
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
if !ctx.testing? && is_shell_shim
|
23
|
-
ctx.puts(ctx.message("core.warning.shell_shim"))
|
24
|
-
return
|
25
|
-
end
|
26
|
-
|
27
8
|
if ctx.development?
|
28
|
-
ctx.
|
9
|
+
ctx.warn(
|
29
10
|
ctx.message("core.warning.development_version", File.join(ShopifyCli::ROOT, "bin", ShopifyCli::TOOL_NAME))
|
30
11
|
)
|
31
12
|
elsif !ctx.testing?
|
32
13
|
new_version = ctx.new_version
|
33
|
-
ctx.
|
14
|
+
ctx.warn(ctx.message("core.warning.new_version", ShopifyCli::VERSION, new_version)) unless new_version.nil?
|
34
15
|
end
|
35
16
|
|
36
|
-
ProjectType.
|
17
|
+
ProjectType.load_all
|
37
18
|
|
38
19
|
task_registry = ShopifyCli::Tasks::Registry
|
39
20
|
|
data/lib/shopify-cli/db.rb
CHANGED
@@ -42,7 +42,7 @@ module ShopifyCli
|
|
42
42
|
#
|
43
43
|
# #### Usage
|
44
44
|
#
|
45
|
-
# exists = ShopifyCli::DB.exists?('
|
45
|
+
# exists = ShopifyCli::DB.exists?('shopify_exchange_token')
|
46
46
|
#
|
47
47
|
def exists?(key)
|
48
48
|
db.transaction(true) { db.root?(key) }
|
@@ -55,7 +55,7 @@ module ShopifyCli
|
|
55
55
|
#
|
56
56
|
# #### Usage
|
57
57
|
#
|
58
|
-
# ShopifyCli::DB.set(
|
58
|
+
# ShopifyCli::DB.set(shopify_exchange_token: 'token', metric_consent: true)
|
59
59
|
#
|
60
60
|
def set(**args)
|
61
61
|
db.transaction do
|
@@ -80,7 +80,7 @@ module ShopifyCli
|
|
80
80
|
#
|
81
81
|
# #### Usage
|
82
82
|
#
|
83
|
-
# ShopifyCli::DB.get(:
|
83
|
+
# ShopifyCli::DB.get(:shopify_exchange_token)
|
84
84
|
#
|
85
85
|
def get(key)
|
86
86
|
val = db.transaction(true) { db[key] }
|
@@ -95,7 +95,7 @@ module ShopifyCli
|
|
95
95
|
#
|
96
96
|
# #### Usage
|
97
97
|
#
|
98
|
-
# ShopifyCli::DB.del(:
|
98
|
+
# ShopifyCli::DB.del(:shopify_exchange_token)
|
99
99
|
#
|
100
100
|
def del(*args)
|
101
101
|
db.transaction { args.each { |key| db.delete(key) } }
|
@@ -8,11 +8,21 @@ module ShopifyCli
|
|
8
8
|
request(uri, body, headers, req)
|
9
9
|
end
|
10
10
|
|
11
|
+
def put(uri, body, headers)
|
12
|
+
req = ::Net::HTTP::Put.new(uri.request_uri)
|
13
|
+
request(uri, body, headers, req)
|
14
|
+
end
|
15
|
+
|
11
16
|
def get(uri, body, headers)
|
12
17
|
req = ::Net::HTTP::Get.new(uri.request_uri)
|
13
18
|
request(uri, body, headers, req)
|
14
19
|
end
|
15
20
|
|
21
|
+
def delete(uri, body, headers)
|
22
|
+
req = ::Net::HTTP::Delete.new(uri.request_uri)
|
23
|
+
request(uri, body, headers, req)
|
24
|
+
end
|
25
|
+
|
16
26
|
def request(uri, body, headers, req)
|
17
27
|
http = ::Net::HTTP.new(uri.host, uri.port)
|
18
28
|
http.use_ssl = true
|
@@ -0,0 +1,282 @@
|
|
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 IdentityAuth
|
13
|
+
include SmartProperties
|
14
|
+
|
15
|
+
autoload :Servlet, "shopify-cli/identity_auth/servlet"
|
16
|
+
|
17
|
+
class Error < StandardError; end
|
18
|
+
class Timeout < StandardError; end
|
19
|
+
LocalRequest = Struct.new(:method, :path, :query, :protocol)
|
20
|
+
LOCAL_DEBUG = "SHOPIFY_APP_CLI_LOCAL_PARTNERS"
|
21
|
+
|
22
|
+
DEFAULT_PORT = 3456
|
23
|
+
REDIRECT_HOST = "http://127.0.0.1:#{DEFAULT_PORT}"
|
24
|
+
|
25
|
+
APPLICATION_SCOPES = {
|
26
|
+
"shopify" => %w[https://api.shopify.com/auth/shop.admin.graphql https://api.shopify.com/auth/shop.admin.themes https://api.shopify.com/auth/partners.collaborator-relationships.readonly],
|
27
|
+
"storefront_renderer_production" => %w[https://api.shopify.com/auth/shop.storefront-renderer.devtools],
|
28
|
+
"partners" => %w[https://api.shopify.com/auth/partners.app.cli.access],
|
29
|
+
}
|
30
|
+
|
31
|
+
APPLICATION_CLIENT_IDS = {
|
32
|
+
"shopify" => "7ee65a63608843c577db8b23c4d7316ea0a01bd2f7594f8a9c06ea668c1b775c",
|
33
|
+
"storefront_renderer_production" => "ee139b3d-5861-4d45-b387-1bc3ada7811c",
|
34
|
+
"partners" => "271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6",
|
35
|
+
}
|
36
|
+
|
37
|
+
DEV_APPLICATION_CLIENT_IDS = {
|
38
|
+
"shopify" => "e92482cebb9bfb9fb5a0199cc770fde3de6c8d16b798ee73e36c9d815e070e52",
|
39
|
+
"storefront_renderer_production" => "46f603de-894f-488d-9471-5b721280ff49",
|
40
|
+
"partners" => "df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978",
|
41
|
+
}
|
42
|
+
|
43
|
+
EXCHANGE_TOKENS = APPLICATION_SCOPES.keys.map do |key|
|
44
|
+
"#{key}_exchange_token".to_sym
|
45
|
+
end
|
46
|
+
|
47
|
+
IDENTITY_ACCESS_TOKENS = %i[
|
48
|
+
identity_access_token
|
49
|
+
identity_refresh_token
|
50
|
+
]
|
51
|
+
|
52
|
+
property! :ctx
|
53
|
+
property :store, default: -> { ShopifyCli::DB.new }
|
54
|
+
property :state_token, accepts: String, default: SecureRandom.hex(30)
|
55
|
+
property :code_verifier, accepts: String, default: SecureRandom.hex(30)
|
56
|
+
|
57
|
+
attr_accessor :response_query
|
58
|
+
|
59
|
+
def authenticate
|
60
|
+
return if refresh_exchange_tokens || refresh_access_tokens
|
61
|
+
|
62
|
+
initiate_authentication
|
63
|
+
|
64
|
+
begin
|
65
|
+
request_access_token(code: receive_access_code)
|
66
|
+
rescue IdentityAuth::Timeout => e
|
67
|
+
ctx.abort(e.message)
|
68
|
+
end
|
69
|
+
request_exchange_tokens
|
70
|
+
end
|
71
|
+
|
72
|
+
def reauthenticate
|
73
|
+
return if refresh_exchange_tokens || refresh_access_tokens
|
74
|
+
ctx.abort(ctx.message("core.identity_auth.error.reauthenticate", ShopifyCli::TOOL_NAME))
|
75
|
+
end
|
76
|
+
|
77
|
+
def code_challenge
|
78
|
+
@code_challenge ||= Base64.urlsafe_encode64(
|
79
|
+
OpenSSL::Digest::SHA256.digest(code_verifier),
|
80
|
+
padding: false,
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def server
|
85
|
+
@server ||= begin
|
86
|
+
server = WEBrick::HTTPServer.new(
|
87
|
+
Port: DEFAULT_PORT,
|
88
|
+
Logger: WEBrick::Log.new(File.open(File::NULL, "w")),
|
89
|
+
AccessLog: [],
|
90
|
+
)
|
91
|
+
server.mount("/", Servlet, self, state_token)
|
92
|
+
server
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def self.delete_tokens_and_keys
|
97
|
+
ShopifyCli::DB.del(*IDENTITY_ACCESS_TOKENS)
|
98
|
+
ShopifyCli::DB.del(*EXCHANGE_TOKENS)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def initiate_authentication
|
104
|
+
@server_thread = Thread.new { server.start }
|
105
|
+
params = {
|
106
|
+
client_id: client_id,
|
107
|
+
scope: scopes(APPLICATION_SCOPES.values.flatten),
|
108
|
+
redirect_uri: REDIRECT_HOST,
|
109
|
+
state: state_token,
|
110
|
+
response_type: :code,
|
111
|
+
}
|
112
|
+
params.merge!(challange_params)
|
113
|
+
uri = URI.parse("#{auth_url}/authorize")
|
114
|
+
uri.query = URI.encode_www_form(params)
|
115
|
+
open_browser_authentication(uri)
|
116
|
+
end
|
117
|
+
|
118
|
+
def open_browser_authentication(uri)
|
119
|
+
ctx.open_browser_url!(uri)
|
120
|
+
end
|
121
|
+
|
122
|
+
def receive_access_code
|
123
|
+
@access_code ||= begin
|
124
|
+
@server_thread.join(240)
|
125
|
+
raise Timeout, ctx.message("core.identity_auth.error.timeout") if response_query.nil?
|
126
|
+
raise Error, response_query["error_description"] unless response_query["error"].nil?
|
127
|
+
response_query["code"]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
def request_access_token(code:)
|
132
|
+
resp = post_token_request(
|
133
|
+
grant_type: :authorization_code,
|
134
|
+
code: code,
|
135
|
+
redirect_uri: REDIRECT_HOST,
|
136
|
+
client_id: client_id,
|
137
|
+
code_verifier: code_verifier,
|
138
|
+
)
|
139
|
+
store.set(
|
140
|
+
identity_access_token: resp["access_token"],
|
141
|
+
identity_refresh_token: resp["refresh_token"],
|
142
|
+
)
|
143
|
+
end
|
144
|
+
|
145
|
+
def refresh_access_tokens
|
146
|
+
return false unless IDENTITY_ACCESS_TOKENS.all? { |key| store.exists?(key) }
|
147
|
+
|
148
|
+
resp = post_token_request(
|
149
|
+
grant_type: :refresh_token,
|
150
|
+
access_token: store.get(:identity_access_token),
|
151
|
+
refresh_token: store.get(:identity_refresh_token),
|
152
|
+
client_id: client_id,
|
153
|
+
)
|
154
|
+
store.set(
|
155
|
+
identity_access_token: resp["access_token"],
|
156
|
+
identity_refresh_token: resp["refresh_token"],
|
157
|
+
)
|
158
|
+
|
159
|
+
# Need to refresh the exchange token on successful access token refresh
|
160
|
+
request_exchange_tokens
|
161
|
+
|
162
|
+
true
|
163
|
+
rescue
|
164
|
+
store.del(*IDENTITY_ACCESS_TOKENS)
|
165
|
+
false
|
166
|
+
end
|
167
|
+
|
168
|
+
def refresh_exchange_tokens
|
169
|
+
return false unless EXCHANGE_TOKENS.all? { |key| store.exists?(key) }
|
170
|
+
|
171
|
+
request_exchange_tokens
|
172
|
+
|
173
|
+
true
|
174
|
+
rescue
|
175
|
+
store.del(*EXCHANGE_TOKENS)
|
176
|
+
false
|
177
|
+
end
|
178
|
+
|
179
|
+
def request_exchange_tokens
|
180
|
+
APPLICATION_SCOPES.each do |key, scopes|
|
181
|
+
request_exchange_token(key, client_id_for_application(key), scopes)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
def request_exchange_token(name, audience, additional_scopes)
|
186
|
+
return if name == "shopify" && !store.exists?(:shop)
|
187
|
+
|
188
|
+
params = {
|
189
|
+
grant_type: "urn:ietf:params:oauth:grant-type:token-exchange",
|
190
|
+
requested_token_type: "urn:ietf:params:oauth:token-type:access_token",
|
191
|
+
subject_token_type: "urn:ietf:params:oauth:token-type:access_token",
|
192
|
+
client_id: client_id,
|
193
|
+
audience: audience,
|
194
|
+
scope: scopes(additional_scopes),
|
195
|
+
subject_token: store.get(:identity_access_token),
|
196
|
+
}.tap do |result|
|
197
|
+
if name == "shopify"
|
198
|
+
result[:destination] = "https://#{store.get(:shop)}/admin"
|
199
|
+
end
|
200
|
+
end
|
201
|
+
# ctx.debug(params)
|
202
|
+
resp = post_token_request(params)
|
203
|
+
store.set("#{name}_exchange_token".to_sym => resp["access_token"])
|
204
|
+
ctx.debug("#{name}_exchange_token: " + resp["access_token"])
|
205
|
+
end
|
206
|
+
|
207
|
+
def post_token_request(params)
|
208
|
+
post_request("/token", params)
|
209
|
+
end
|
210
|
+
|
211
|
+
def post_request(endpoint, params)
|
212
|
+
uri = URI.parse("#{auth_url}#{endpoint}")
|
213
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
214
|
+
https.use_ssl = true
|
215
|
+
request = Net::HTTP::Post.new(uri.path)
|
216
|
+
request["User-Agent"] = "Shopify CLI #{::ShopifyCli::VERSION}"
|
217
|
+
request.body = URI.encode_www_form(params)
|
218
|
+
res = https.request(request)
|
219
|
+
unless res.is_a?(Net::HTTPSuccess)
|
220
|
+
error_msg = JSON.parse(res.body)["error_description"]
|
221
|
+
shop = store.get(:shop)
|
222
|
+
if error_msg.include?("destination")
|
223
|
+
store.del(:shop)
|
224
|
+
ctx.abort(ctx.message("core.identity_auth.error.invalid_destination", shop))
|
225
|
+
end
|
226
|
+
raise Error, error_msg
|
227
|
+
end
|
228
|
+
JSON.parse(res.body)
|
229
|
+
end
|
230
|
+
|
231
|
+
def challange_params
|
232
|
+
{
|
233
|
+
code_challenge: code_challenge,
|
234
|
+
code_challenge_method: "S256",
|
235
|
+
}
|
236
|
+
end
|
237
|
+
|
238
|
+
def auth_url
|
239
|
+
return "https://accounts.shopify.com/oauth" if ENV[LOCAL_DEBUG].nil?
|
240
|
+
"https://identity.myshopify.io/oauth"
|
241
|
+
end
|
242
|
+
|
243
|
+
def client_id_for_application(application_name)
|
244
|
+
client_ids = if ENV[LOCAL_DEBUG]
|
245
|
+
DEV_APPLICATION_CLIENT_IDS
|
246
|
+
else
|
247
|
+
APPLICATION_CLIENT_IDS
|
248
|
+
end
|
249
|
+
|
250
|
+
client_ids[application_name]
|
251
|
+
end
|
252
|
+
|
253
|
+
def scopes(additional_scopes = [])
|
254
|
+
(["openid"] + additional_scopes).tap do |result|
|
255
|
+
result << "employee" if ShopifyCli::Shopifolk.acting_as_shopify_organization?
|
256
|
+
end.join(" ")
|
257
|
+
end
|
258
|
+
|
259
|
+
def client_id
|
260
|
+
return "fbdb2649-e327-4907-8f67-908d24cfd7e3" if ENV[LOCAL_DEBUG].nil?
|
261
|
+
|
262
|
+
ctx.abort(ctx.message("core.identity_auth.error.local_identity_not_running")) unless local_identity_running?
|
263
|
+
|
264
|
+
# Fetch the client ID from the local Identity Dynamic Registration endpoint
|
265
|
+
response = post_request("/client", {
|
266
|
+
name: "shopify-cli-development",
|
267
|
+
public_type: "native",
|
268
|
+
})
|
269
|
+
|
270
|
+
response["client_id"]
|
271
|
+
end
|
272
|
+
|
273
|
+
def local_identity_running?
|
274
|
+
Net::HTTP.start("identity.myshopify.io", 443, use_ssl: true, open_timeout: 1, read_timeout: 10) do |http|
|
275
|
+
req = Net::HTTP::Get.new(URI.join("https://identity.myshopify.io", "/services/ping"))
|
276
|
+
http.request(req).is_a?(Net::HTTPSuccess)
|
277
|
+
end
|
278
|
+
rescue Timeout::Error, Errno::EHOSTUNREACH, Errno::EHOSTDOWN, Errno::EADDRNOTAVAIL, Errno::ECONNREFUSED
|
279
|
+
false
|
280
|
+
end
|
281
|
+
end
|
282
|
+
end
|