shopify-cli 1.11.0 → 2.0.0
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/CODEOWNERS +1 -1
- data/.github/CONTRIBUTING.md +7 -7
- data/.github/DESIGN.md +3 -3
- data/.github/PULL_REQUEST_TEMPLATE.md +1 -1
- data/.github/workflows/build.yml +1 -1
- data/.gitignore +3 -0
- data/.rubocop.yml +3 -1
- data/.ruby-version +1 -1
- data/CHANGELOG.md +48 -20
- 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 -38
- data/lib/project_types/extension/features/argo_serve.rb +25 -20
- 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 +21 -6
- 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 +18 -20
- data/lib/project_types/script/commands/create.rb +3 -1
- data/lib/project_types/script/commands/push.rb +12 -5
- data/lib/project_types/script/config/extension_points.yml +0 -3
- data/lib/project_types/script/graphql/app_script_update_or_create.graphql +9 -3
- data/lib/project_types/script/layers/application/create_script.rb +6 -5
- data/lib/project_types/script/layers/application/push_script.rb +2 -1
- data/lib/project_types/script/layers/domain/errors.rb +6 -11
- data/lib/project_types/script/layers/domain/push_package.rb +4 -8
- data/lib/project_types/script/layers/domain/script_json.rb +32 -0
- data/lib/project_types/script/layers/domain/script_project.rb +1 -1
- data/lib/project_types/script/layers/infrastructure/errors.rb +14 -18
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_project_creator.rb +105 -0
- data/lib/project_types/script/layers/infrastructure/languages/assemblyscript_task_runner.rb +103 -0
- data/lib/project_types/script/layers/infrastructure/languages/project_creator.rb +26 -0
- data/lib/project_types/script/layers/infrastructure/languages/rust_project_creator.rb +73 -0
- data/lib/project_types/script/layers/infrastructure/languages/rust_task_runner.rb +60 -0
- data/lib/project_types/script/layers/infrastructure/languages/task_runner.rb +21 -0
- data/lib/project_types/script/layers/infrastructure/push_package_repository.rb +2 -4
- data/lib/project_types/script/layers/infrastructure/script_project_repository.rb +45 -34
- data/lib/project_types/script/layers/infrastructure/script_service.rb +20 -14
- data/lib/project_types/script/messages/messages.rb +66 -55
- data/lib/project_types/script/tasks/ensure_env.rb +22 -1
- data/lib/project_types/script/ui/error_handler.rb +32 -32
- 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 -35
- 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 +65 -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 +133 -39
- data/lib/shopify-cli/partners_api.rb +21 -41
- 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/resources/env_file.rb +1 -1
- 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 +95 -41
- data/.github/workflows/release.yml +0 -61
- data/lib/project_types/extension/features/argo_serve_options.rb +0 -40
- 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/script/layers/domain/config_ui.rb +0 -16
- data/lib/project_types/script/layers/infrastructure/assemblyscript_project_creator.rb +0 -95
- data/lib/project_types/script/layers/infrastructure/assemblyscript_task_runner.rb +0 -101
- data/lib/project_types/script/layers/infrastructure/project_creator.rb +0 -24
- data/lib/project_types/script/layers/infrastructure/rust_project_creator.rb +0 -71
- data/lib/project_types/script/layers/infrastructure/rust_task_runner.rb +0 -58
- data/lib/project_types/script/layers/infrastructure/task_runner.rb +0 -19
- 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
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module Theme
|
|
2
|
+
module UI
|
|
3
|
+
class SyncProgressBar
|
|
4
|
+
def initialize(syncer)
|
|
5
|
+
@syncer = syncer
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def progress(method, **args)
|
|
9
|
+
@syncer.delay_errors!
|
|
10
|
+
CLI::UI::Progress.progress do |bar|
|
|
11
|
+
@syncer.public_send(method, **args) do |left, total|
|
|
12
|
+
bar.tick(set_percent: 1 - left.to_f / total)
|
|
13
|
+
end
|
|
14
|
+
bar.tick(set_percent: 1)
|
|
15
|
+
end
|
|
16
|
+
@syncer.report_errors!
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -38,9 +38,15 @@ module ShopifyCli
|
|
|
38
38
|
# ShopifyCli::AdminAPI.query(@ctx, 'all_organizations')
|
|
39
39
|
#
|
|
40
40
|
def query(ctx, query_name, shop:, api_version: nil, **variables)
|
|
41
|
-
|
|
41
|
+
CLI::Kit::Util.begin do
|
|
42
42
|
api_client(ctx, api_version, shop).query(query_name, variables: variables)
|
|
43
|
+
end.retry_after(API::APIRequestUnauthorizedError, retries: 1) do
|
|
44
|
+
ShopifyCli::IdentityAuth.new(ctx: ctx).reauthenticate
|
|
43
45
|
end
|
|
46
|
+
rescue API::APIRequestUnauthorizedError
|
|
47
|
+
ctx.abort(ctx.message("core.api.error.failed_auth"))
|
|
48
|
+
rescue API::APIRequestForbiddenError
|
|
49
|
+
ctx.abort(ctx.message("core.api.error.forbidden", ShopifyCli::TOOL_NAME))
|
|
44
50
|
end
|
|
45
51
|
|
|
46
52
|
##
|
|
@@ -75,49 +81,47 @@ module ShopifyCli
|
|
|
75
81
|
# path: 'data.json',
|
|
76
82
|
# token: 'password')
|
|
77
83
|
#
|
|
78
|
-
def rest_request(ctx, shop:, path:, body: nil, method: "GET", api_version: nil, token: nil)
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
def rest_request(ctx, shop:, path:, query: nil, body: nil, method: "GET", api_version: nil, token: nil)
|
|
85
|
+
CLI::Kit::Util.begin do
|
|
86
|
+
ShopifyCli::DB.set(shopify_exchange_token: token) unless token.nil?
|
|
87
|
+
url = URI::HTTPS.build(
|
|
88
|
+
host: shop,
|
|
89
|
+
path: "/admin/api/#{fetch_api_version(ctx, api_version, shop)}/#{path}",
|
|
90
|
+
query: query,
|
|
91
|
+
)
|
|
92
|
+
resp = api_client(ctx, api_version, shop, path: path).request(url: url.to_s, body: body, method: method)
|
|
93
|
+
ShopifyCli::DB.set(shopify_exchange_token: nil) unless token.nil?
|
|
94
|
+
resp
|
|
95
|
+
end.retry_after(API::APIRequestUnauthorizedError) do
|
|
96
|
+
ShopifyCli::IdentityAuth.new(ctx: ctx).reauthenticate
|
|
97
|
+
end
|
|
84
98
|
end
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
authenticate(ctx, shop)
|
|
92
|
-
retry
|
|
100
|
+
def get_shop_or_abort(ctx)
|
|
101
|
+
ctx.abort(
|
|
102
|
+
ctx.message("core.populate.error.no_shop", ShopifyCli::TOOL_NAME)
|
|
103
|
+
) unless ShopifyCli::DB.exists?(:shop)
|
|
104
|
+
ShopifyCli::DB.get(:shop)
|
|
93
105
|
end
|
|
94
106
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
service: "admin",
|
|
100
|
-
client_id: env.api_key,
|
|
101
|
-
secret: env.secret,
|
|
102
|
-
scopes: env.scopes,
|
|
103
|
-
token_path: "/access_token",
|
|
104
|
-
options: { "grant_options[]" => "per user" },
|
|
105
|
-
).authenticate("https://#{shop}/admin/oauth")
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def authenticate(ctx, _shop)
|
|
110
|
+
ShopifyCli::IdentityAuth.new(ctx: ctx).authenticate
|
|
106
111
|
end
|
|
107
112
|
|
|
108
113
|
def api_client(ctx, api_version, shop, path: "graphql.json")
|
|
109
114
|
new(
|
|
110
115
|
ctx: ctx,
|
|
111
|
-
|
|
112
|
-
token: admin_access_token(ctx, shop),
|
|
116
|
+
token: access_token(ctx, shop),
|
|
113
117
|
url: "https://#{shop}/admin/api/#{fetch_api_version(ctx, api_version, shop)}/#{path}",
|
|
114
118
|
)
|
|
115
119
|
end
|
|
116
120
|
|
|
117
|
-
def
|
|
118
|
-
ShopifyCli::DB.get(:
|
|
121
|
+
def access_token(ctx, shop)
|
|
122
|
+
ShopifyCli::DB.get(:shopify_exchange_token) do
|
|
119
123
|
authenticate(ctx, shop)
|
|
120
|
-
ShopifyCli::DB.get(:
|
|
124
|
+
ShopifyCli::DB.get(:shopify_exchange_token)
|
|
121
125
|
end
|
|
122
126
|
end
|
|
123
127
|
|
|
@@ -125,14 +129,28 @@ module ShopifyCli
|
|
|
125
129
|
return api_version unless api_version.nil?
|
|
126
130
|
client = new(
|
|
127
131
|
ctx: ctx,
|
|
128
|
-
|
|
129
|
-
token: admin_access_token(ctx, shop),
|
|
132
|
+
token: access_token(ctx, shop),
|
|
130
133
|
url: "https://#{shop}/admin/api/unstable/graphql.json",
|
|
131
134
|
)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
+
CLI::Kit::Util.begin do
|
|
136
|
+
versions = client.query("api_versions")["data"]["publicApiVersions"]
|
|
137
|
+
latest = versions.find { |version| version["displayName"].include?("Latest") }
|
|
138
|
+
latest["handle"]
|
|
139
|
+
end.retry_after(API::APIRequestUnauthorizedError, retries: 1) do
|
|
140
|
+
ShopifyCli::IdentityAuth.new(ctx: ctx).reauthenticate
|
|
141
|
+
end
|
|
142
|
+
rescue API::APIRequestUnauthorizedError
|
|
143
|
+
ctx.abort(ctx.message("core.api.error.failed_auth"))
|
|
144
|
+
rescue API::APIRequestForbiddenError
|
|
145
|
+
ctx.abort(ctx.message("core.api.error.forbidden", ShopifyCli::TOOL_NAME))
|
|
135
146
|
end
|
|
136
147
|
end
|
|
148
|
+
|
|
149
|
+
def auth_headers(token)
|
|
150
|
+
{
|
|
151
|
+
Authorization: "Bearer #{token}",
|
|
152
|
+
"X-Shopify-Access-Token" => token, # TODO: Remove when we no longer need private apps
|
|
153
|
+
}
|
|
154
|
+
end
|
|
137
155
|
end
|
|
138
156
|
end
|
|
@@ -25,12 +25,11 @@ module ShopifyCli
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def call(args, _)
|
|
28
|
-
return unless Project.current
|
|
29
|
-
Tasks::EnsureEnv.call(@ctx)
|
|
30
28
|
@args = args
|
|
31
29
|
@input = Hash.new
|
|
32
30
|
@count = DEFAULT_COUNT
|
|
33
31
|
@help = false
|
|
32
|
+
@skip_shop_confirmation = false
|
|
34
33
|
input_options
|
|
35
34
|
resource_options.parse(@args)
|
|
36
35
|
|
|
@@ -41,8 +40,8 @@ module ShopifyCli
|
|
|
41
40
|
return @ctx.puts(output)
|
|
42
41
|
end
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
|
|
43
|
+
ShopifyCli::Tasks::ConfirmStore.call(@ctx) unless @skip_shop_confirmation
|
|
44
|
+
@shop = AdminAPI.get_shop_or_abort(@ctx)
|
|
46
45
|
if @silent
|
|
47
46
|
spin_group = CLI::UI::SpinGroup.new
|
|
48
47
|
spin_group.add(@ctx.message("core.populate.populating", @count, camel_case_resource_type)) do |spinner|
|
|
@@ -89,7 +88,7 @@ module ShopifyCli
|
|
|
89
88
|
|
|
90
89
|
opts.on("--silent") { |v| @silent = v }
|
|
91
90
|
|
|
92
|
-
opts.on("--shop
|
|
91
|
+
opts.on("--skip-shop-confirmation") { |v| @skip_shop_confirmation = v }
|
|
93
92
|
end
|
|
94
93
|
end
|
|
95
94
|
|
|
@@ -130,7 +129,7 @@ module ShopifyCli
|
|
|
130
129
|
"core.populate.completion_message",
|
|
131
130
|
@count,
|
|
132
131
|
"#{camel_case_resource_type}#{plural}",
|
|
133
|
-
|
|
132
|
+
@shop,
|
|
134
133
|
camel_case_resource_type,
|
|
135
134
|
admin_url,
|
|
136
135
|
snake_case_resource_type
|
|
@@ -138,7 +137,7 @@ module ShopifyCli
|
|
|
138
137
|
end
|
|
139
138
|
|
|
140
139
|
def admin_url
|
|
141
|
-
"https://#{
|
|
140
|
+
"https://#{@shop}/admin/"
|
|
142
141
|
end
|
|
143
142
|
|
|
144
143
|
def price
|
|
@@ -147,13 +146,6 @@ module ShopifyCli
|
|
|
147
146
|
|
|
148
147
|
private
|
|
149
148
|
|
|
150
|
-
def get_shop(ctx)
|
|
151
|
-
res = ShopifyCli::Tasks::SelectOrgAndShop.call(ctx)
|
|
152
|
-
domain = res[:shop_domain]
|
|
153
|
-
Project.current.env.update(ctx, :shop, domain)
|
|
154
|
-
domain
|
|
155
|
-
end
|
|
156
|
-
|
|
157
149
|
def camel_case_resource_type
|
|
158
150
|
@camel_case_resource_type ||= self.class.to_s.split("::").last
|
|
159
151
|
end
|
|
@@ -6,7 +6,7 @@ module ShopifyCli
|
|
|
6
6
|
class << self
|
|
7
7
|
def get(ctx)
|
|
8
8
|
unless ShopifyCli::DB.exists?(:shopify_admin_schema)
|
|
9
|
-
shop =
|
|
9
|
+
shop = AdminAPI.get_shop_or_abort(ctx)
|
|
10
10
|
schema = AdminAPI.query(ctx, "admin_introspection", shop: shop)
|
|
11
11
|
ShopifyCli::DB.set(shopify_admin_schema: JSON.dump(schema))
|
|
12
12
|
end
|
|
@@ -15,15 +15,6 @@ module ShopifyCli
|
|
|
15
15
|
# available
|
|
16
16
|
self[JSON.parse(ShopifyCli::DB.get(:shopify_admin_schema))]
|
|
17
17
|
end
|
|
18
|
-
|
|
19
|
-
private
|
|
20
|
-
|
|
21
|
-
def get_shop(ctx)
|
|
22
|
-
res = ShopifyCli::Tasks::SelectOrgAndShop.call(ctx)
|
|
23
|
-
domain = res[:shop_domain]
|
|
24
|
-
Project.current.env.update(ctx, :shop, domain)
|
|
25
|
-
domain
|
|
26
|
-
end
|
|
27
18
|
end
|
|
28
19
|
|
|
29
20
|
def type(name)
|
data/lib/shopify-cli/api.rb
CHANGED
|
@@ -9,12 +9,22 @@ module ShopifyCli
|
|
|
9
9
|
property :auth_header, accepts: String
|
|
10
10
|
property! :url, accepts: String
|
|
11
11
|
|
|
12
|
-
class APIRequestError < StandardError
|
|
12
|
+
class APIRequestError < StandardError
|
|
13
|
+
attr_reader :response
|
|
14
|
+
|
|
15
|
+
def initialize(message = nil, response: nil)
|
|
16
|
+
super(message)
|
|
17
|
+
@response = response
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
13
21
|
class APIRequestNotFoundError < APIRequestError; end
|
|
14
22
|
class APIRequestClientError < APIRequestError; end
|
|
15
23
|
class APIRequestUnauthorizedError < APIRequestClientError; end
|
|
24
|
+
class APIRequestForbiddenError < APIRequestClientError; end
|
|
16
25
|
class APIRequestUnexpectedError < APIRequestError; end
|
|
17
26
|
class APIRequestRetriableError < APIRequestError; end
|
|
27
|
+
class APIRequestTimeoutError < APIRequestRetriableError; end
|
|
18
28
|
class APIRequestServerError < APIRequestRetriableError; end
|
|
19
29
|
class APIRequestThrottledError < APIRequestRetriableError; end
|
|
20
30
|
|
|
@@ -46,26 +56,33 @@ module ShopifyCli
|
|
|
46
56
|
headers = default_headers.merge(headers)
|
|
47
57
|
response = if method == "POST"
|
|
48
58
|
HttpRequest.post(uri, body, headers)
|
|
59
|
+
elsif method == "PUT"
|
|
60
|
+
HttpRequest.put(uri, body, headers)
|
|
49
61
|
elsif method == "GET"
|
|
50
62
|
HttpRequest.get(uri, body, headers)
|
|
63
|
+
elsif method == "DELETE"
|
|
64
|
+
HttpRequest.delete(uri, body, headers)
|
|
51
65
|
end
|
|
52
|
-
|
|
53
66
|
case response.code.to_i
|
|
54
67
|
when 200..399
|
|
55
|
-
[response.code.to_i, JSON.parse(response.body)]
|
|
68
|
+
[response.code.to_i, JSON.parse(response.body), response]
|
|
56
69
|
when 401
|
|
57
|
-
raise APIRequestUnauthorizedError
|
|
70
|
+
raise APIRequestUnauthorizedError.new("#{response.code}\n#{response.body}", response: response)
|
|
71
|
+
when 403
|
|
72
|
+
raise APIRequestForbiddenError.new("#{response.code}\n#{response.body}", response: response)
|
|
58
73
|
when 404
|
|
59
|
-
raise APIRequestNotFoundError
|
|
74
|
+
raise APIRequestNotFoundError.new("#{response.code}\n#{response.body}", response: response)
|
|
60
75
|
when 429
|
|
61
|
-
raise APIRequestThrottledError
|
|
76
|
+
raise APIRequestThrottledError.new("#{response.code}\n#{response.body}", response: response)
|
|
62
77
|
when 400..499
|
|
63
|
-
raise APIRequestClientError
|
|
78
|
+
raise APIRequestClientError.new("#{response.code}\n#{response.body}", response: response)
|
|
64
79
|
when 500..599
|
|
65
|
-
raise APIRequestServerError
|
|
80
|
+
raise APIRequestServerError.new("#{response.code}\n#{response.body}", response: response)
|
|
66
81
|
else
|
|
67
|
-
raise APIRequestUnexpectedError
|
|
82
|
+
raise APIRequestUnexpectedError.new("#{response.code}\n#{response.body}", response: response)
|
|
68
83
|
end
|
|
84
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Net::WriteTimeout, Errno::ETIMEDOUT, Timeout::Error
|
|
85
|
+
raise APIRequestTimeoutError.new("Timeout")
|
|
69
86
|
end.retry_after(APIRequestRetriableError, retries: 3) do |e|
|
|
70
87
|
sleep(1) if e.is_a?(APIRequestThrottledError)
|
|
71
88
|
end
|
|
@@ -87,13 +104,11 @@ module ShopifyCli
|
|
|
87
104
|
|
|
88
105
|
private
|
|
89
106
|
|
|
90
|
-
def current_sha
|
|
91
|
-
@current_sha ||= Git.sha(dir: ShopifyCli::ROOT)
|
|
92
|
-
end
|
|
93
|
-
|
|
94
107
|
def default_headers
|
|
95
108
|
{
|
|
96
|
-
"User-Agent" => "Shopify
|
|
109
|
+
"User-Agent" => "Shopify CLI; v=#{ShopifyCli::VERSION}",
|
|
110
|
+
"Sec-CH-UA" => "Shopify CLI; v=#{ShopifyCli::VERSION} sha=#{ShopifyCli.sha}",
|
|
111
|
+
"Sec-CH-UA-PLATFORM" => ctx.os,
|
|
97
112
|
}.tap do |headers|
|
|
98
113
|
headers["X-Shopify-Cli-Employee"] = "1" if Shopifolk.acting_as_shopify_organization?
|
|
99
114
|
end.merge(auth_headers(token))
|
data/lib/shopify-cli/command.rb
CHANGED
|
@@ -42,13 +42,16 @@ module ShopifyCli
|
|
|
42
42
|
)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
|
-
def prerequisite_task(*
|
|
45
|
+
def prerequisite_task(*tasks_without_args, **tasks_with_args)
|
|
46
46
|
@prerequisite_tasks ||= []
|
|
47
|
-
@prerequisite_tasks +=
|
|
47
|
+
@prerequisite_tasks += tasks_without_args.map { |t| PrerequisiteTask.new(t) }
|
|
48
|
+
@prerequisite_tasks += tasks_with_args.map { |t, args| PrerequisiteTask.new(t, args) }
|
|
48
49
|
end
|
|
49
50
|
|
|
50
51
|
def run_prerequisites
|
|
51
|
-
(@prerequisite_tasks || []).each
|
|
52
|
+
(@prerequisite_tasks || []).each do |task|
|
|
53
|
+
task_registry[task.name]&.call(@ctx, *task.args)
|
|
54
|
+
end
|
|
52
55
|
end
|
|
53
56
|
|
|
54
57
|
def task_registry
|
|
@@ -59,6 +62,15 @@ module ShopifyCli
|
|
|
59
62
|
help = Commands::Help.new(@ctx)
|
|
60
63
|
help.call(cmds, nil)
|
|
61
64
|
end
|
|
65
|
+
|
|
66
|
+
class PrerequisiteTask
|
|
67
|
+
attr_reader :name, :args
|
|
68
|
+
|
|
69
|
+
def initialize(name, args = [])
|
|
70
|
+
@name = name
|
|
71
|
+
@args = args
|
|
72
|
+
end
|
|
73
|
+
end
|
|
62
74
|
end
|
|
63
75
|
|
|
64
76
|
def initialize(ctx = nil)
|
data/lib/shopify-cli/commands.rb
CHANGED
|
@@ -19,11 +19,16 @@ module ShopifyCli
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
register :Config, "config", "shopify-cli/commands/config", true
|
|
22
|
-
register :Connect, "connect", "shopify-cli/commands/connect", true
|
|
23
|
-
register :Create, "create", "shopify-cli/commands/create", true
|
|
24
22
|
register :Help, "help", "shopify-cli/commands/help", true
|
|
23
|
+
register :Login, "login", "shopify-cli/commands/login", true
|
|
25
24
|
register :Logout, "logout", "shopify-cli/commands/logout", true
|
|
25
|
+
register :Populate, "populate", "shopify-cli/commands/populate", true
|
|
26
|
+
register :Store, "store", "shopify-cli/commands/store", true
|
|
27
|
+
register :Switch, "switch", "shopify-cli/commands/switch", true
|
|
26
28
|
register :System, "system", "shopify-cli/commands/system", true
|
|
27
29
|
register :Version, "version", "shopify-cli/commands/version", true
|
|
30
|
+
register :Whoami, "whoami", "shopify-cli/commands/whoami", true
|
|
31
|
+
|
|
32
|
+
autoload :Connect, "shopify-cli/commands/connect"
|
|
28
33
|
end
|
|
29
34
|
end
|
|
@@ -23,17 +23,9 @@ module ShopifyCli
|
|
|
23
23
|
preamble = @ctx.message("core.help.preamble", ShopifyCli::TOOL_NAME)
|
|
24
24
|
@ctx.puts(preamble)
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
next if name == "help"
|
|
28
|
-
@ctx.puts("{{command:#{name}}}: #{klass.help}\n")
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
return unless inside_supported_project?
|
|
26
|
+
available_commands = resolved_commands.select { |_name, c| !c.hidden? }
|
|
32
27
|
|
|
33
|
-
|
|
34
|
-
@ctx.puts("{{bold:Available commands for #{project_type_name} projects:}}\n\n")
|
|
35
|
-
|
|
36
|
-
local_commands.each do |name, klass|
|
|
28
|
+
available_commands.each do |name, klass|
|
|
37
29
|
next if name == "help"
|
|
38
30
|
@ctx.puts("{{command:#{name}}}: #{klass.help}\n")
|
|
39
31
|
end
|
|
@@ -41,21 +33,6 @@ module ShopifyCli
|
|
|
41
33
|
|
|
42
34
|
private
|
|
43
35
|
|
|
44
|
-
def project_type_name
|
|
45
|
-
ProjectType.load_type(Project.current_project_type).project_name
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
def core_commands
|
|
49
|
-
resolved_commands
|
|
50
|
-
.select { |_name, c| !c.hidden? }
|
|
51
|
-
.select { |name, _c| Commands.core_command?(name) }
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def local_commands
|
|
55
|
-
resolved_commands
|
|
56
|
-
.reject { |name, _c| Commands.core_command?(name) }
|
|
57
|
-
end
|
|
58
|
-
|
|
59
36
|
def display_help(klass)
|
|
60
37
|
output = klass.help
|
|
61
38
|
if klass.respond_to?(:extended_help)
|
|
@@ -70,10 +47,6 @@ module ShopifyCli
|
|
|
70
47
|
.resolved_commands
|
|
71
48
|
.sort
|
|
72
49
|
end
|
|
73
|
-
|
|
74
|
-
def inside_supported_project?
|
|
75
|
-
Project.current_project_type && ProjectType.load_type(Project.current_project_type)
|
|
76
|
-
end
|
|
77
50
|
end
|
|
78
51
|
end
|
|
79
52
|
end
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
require "shopify_cli"
|
|
2
|
+
|
|
3
|
+
module ShopifyCli
|
|
4
|
+
module Commands
|
|
5
|
+
class Login < ShopifyCli::Command
|
|
6
|
+
PROTOCOL_REGEX = /^https?\:\/\//
|
|
7
|
+
PERMANENT_DOMAIN_SUFFIX = /\.myshopify\.(com|io)$/
|
|
8
|
+
|
|
9
|
+
options do |parser, flags|
|
|
10
|
+
parser.on("--store=STORE") { |url| flags[:shop] = url }
|
|
11
|
+
# backwards compatibility allow 'shop' for now
|
|
12
|
+
parser.on("--shop=SHOP") { |url| flags[:shop] = url }
|
|
13
|
+
parser.on("--password=PASSWORD") { |password| flags[:password] = password }
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def call(*)
|
|
17
|
+
shop = (options.flags[:shop] || @ctx.getenv("SHOPIFY_SHOP" || nil))
|
|
18
|
+
ShopifyCli::DB.set(shop: self.class.validate_shop(shop)) unless shop.nil?
|
|
19
|
+
|
|
20
|
+
if shop.nil? && Shopifolk.check
|
|
21
|
+
Shopifolk.reset
|
|
22
|
+
@ctx.puts(@ctx.message("core.tasks.select_org_and_shop.identified_as_shopify"))
|
|
23
|
+
message = @ctx.message("core.tasks.select_org_and_shop.first_party")
|
|
24
|
+
if CLI::UI::Prompt.confirm(message, default: false)
|
|
25
|
+
Shopifolk.act_as_shopify_organization
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# As password auth will soon be deprecated, we enable only in CI
|
|
30
|
+
if @ctx.ci? && (password = options.flags[:password] || @ctx.getenv("SHOPIFY_PASSWORD"))
|
|
31
|
+
ShopifyCli::DB.set(shopify_exchange_token: password)
|
|
32
|
+
else
|
|
33
|
+
IdentityAuth.new(ctx: @ctx).authenticate
|
|
34
|
+
org = select_organization
|
|
35
|
+
ShopifyCli::DB.set(organization_id: org["id"].to_i) unless org.nil?
|
|
36
|
+
Whoami.call([], "whoami")
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def self.help
|
|
41
|
+
ShopifyCli::Context.message("core.login.help", ShopifyCli::TOOL_NAME)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def self.validate_shop(shop)
|
|
45
|
+
permanent_domain = shop_to_permanent_domain(shop)
|
|
46
|
+
@ctx.abort(@ctx.message("core.login.invalid_shop", shop)) unless permanent_domain
|
|
47
|
+
permanent_domain
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.shop_to_permanent_domain(shop)
|
|
51
|
+
url = if PROTOCOL_REGEX =~ shop
|
|
52
|
+
shop
|
|
53
|
+
elsif shop.include?(".")
|
|
54
|
+
"https://#{shop}"
|
|
55
|
+
else
|
|
56
|
+
"https://#{shop}.myshopify.com"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Make a request to see if it exists or if we get redirected to the permanent domain one
|
|
60
|
+
uri = URI.parse(url)
|
|
61
|
+
Net::HTTP.start(uri.host, use_ssl: true) do |http|
|
|
62
|
+
response = http.request_head("/admin")
|
|
63
|
+
case response
|
|
64
|
+
when Net::HTTPSuccess, Net::HTTPSeeOther
|
|
65
|
+
uri.host
|
|
66
|
+
when Net::HTTPFound
|
|
67
|
+
domain = URI.parse(response["location"]).host
|
|
68
|
+
if PERMANENT_DOMAIN_SUFFIX =~ domain
|
|
69
|
+
domain
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def select_organization
|
|
78
|
+
organizations = ShopifyCli::PartnersAPI::Organizations.fetch_all(@ctx)
|
|
79
|
+
|
|
80
|
+
if organizations.count == 0
|
|
81
|
+
nil
|
|
82
|
+
elsif organizations.count == 1
|
|
83
|
+
organizations.first
|
|
84
|
+
else
|
|
85
|
+
org_id = CLI::UI::Prompt.ask(@ctx.message("core.tasks.select_org_and_shop.organization_select")) do |handler|
|
|
86
|
+
organizations.each do |o|
|
|
87
|
+
handler.option(@ctx.message("core.partners_api.org_name_and_id", o["businessName"], o["id"])) { o["id"] }
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
organizations.find { |o| o["id"] == org_id }
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|