From 0ac3546576b800f44ba4a54a30f17f02259a5ff2 Mon Sep 17 00:00:00 2001 From: Kevin O'Sullivan Date: Wed, 18 Mar 2020 16:50:00 -0400 Subject: [PATCH 1/7] Move functionality from Helpers::OS over to Context, refactor the remaining code accordingly, delete Helpers::OS and update test cases as needed. --- lib/shopify-cli/commands/deploy/heroku.rb | 185 +++++++ .../commands/deploy/heroku_test.rb | 477 ++++++++++++++++++ 2 files changed, 662 insertions(+) create mode 100644 lib/shopify-cli/commands/deploy/heroku.rb create mode 100644 test/shopify-cli/commands/deploy/heroku_test.rb diff --git a/lib/shopify-cli/commands/deploy/heroku.rb b/lib/shopify-cli/commands/deploy/heroku.rb new file mode 100644 index 0000000000..920ff5235d --- /dev/null +++ b/lib/shopify-cli/commands/deploy/heroku.rb @@ -0,0 +1,185 @@ +require 'shopify_cli' + +module ShopifyCli + module Commands + class Deploy + class Heroku < ShopifyCli::Task + + DOWNLOAD_URLS = { + linux: 'https://cli-assets.heroku.com/heroku-linux-x64.tar.gz', + mac: 'https://cli-assets.heroku.com/heroku-darwin-x64.tar.gz', + windows: 'https://cli-assets.heroku.com/heroku-win32-x64.tar.gz', + } + + def self.help + <<~HELP + Deploy the current app project to Heroku + Usage: {{command:#{ShopifyCli::TOOL_NAME} deploy heroku}} + HELP + end + + def call(ctx, _name = nil) + @ctx = ctx + + spin_group = CLI::UI::SpinGroup.new + git_service = ShopifyCli::Git.new(@ctx) + + spin_group.add('Downloading Heroku CLI…') do |spinner| + heroku_download + spinner.update_title('Downloaded Heroku CLI') + end + spin_group.wait + + spin_group.add('Installing Heroku CLI…') do |spinner| + heroku_install + spinner.update_title('Installed Heroku CLI') + end + spin_group.add('Checking git repo…') do |spinner| + git_service.init + spinner.update_title('Git repo initialized') + end + spin_group.wait + + if (account = heroku_whoami) + spin_group.add("Authenticated with Heroku as `#{account}`") { true } + spin_group.wait + else + CLI::UI::Frame.open("Authenticating with Heroku…", success_text: '{{v}} Authenticated with Heroku') do + heroku_authenticate + end + end + + if (app_name = heroku_app) + spin_group.add("Heroku app `#{app_name}` selected") { true } + spin_group.wait + else + app_type = CLI::UI::Prompt.ask('No existing Heroku app found. What would you like to do?') do |handler| + handler.option('Create a new Heroku app') { :new } + handler.option('Specify an existing Heroku app') { :existing } + end + + if app_type == :existing + app_name = CLI::UI::Prompt.ask('What is your Heroku app’s name?') + CLI::UI::Frame.open( + "Selecting Heroku app `#{app_name}`…", + success_text: "{{v}} Heroku app `#{app_name}` selected" + ) do + heroku_select_existing_app(app_name) + end + elsif app_type == :new + CLI::UI::Frame.open('Creating new Heroku app…', success_text: '{{v}} New Heroku app created') do + heroku_create_new_app + end + end + end + + branches = git_service.branches + if branches.length == 1 + branch_to_deploy = branches[0] + spin_group.add("Git branch `#{branch_to_deploy}` selected for deploy") { true } + spin_group.wait + else + branch_to_deploy = CLI::UI::Prompt.ask('What branch would you like to deploy?') do |handler| + branches.each do |branch| + handler.option(branch) { branch } + end + end + end + + CLI::UI::Frame.open('Deploying to Heroku…', success_text: '{{v}} Deployed to Heroku') do + heroku_deploy(branch_to_deploy) + end + end + + private + + def heroku_app + return nil if heroku_git_remote.nil? + app = heroku_git_remote + app = app.split('/').last + app = app.split('.').first + app + end + + def heroku_authenticate + result = @ctx.system(heroku_command, 'login') + @ctx.error("Could not authenticate with Heroku") unless result.success? + end + + def heroku_command + local_path = File.join(ShopifyCli::ROOT, 'heroku', 'bin', 'heroku').to_s + if File.exist?(local_path) + local_path + else + 'heroku' + end + end + + def heroku_create_new_app + output, status = @ctx.capture2e(heroku_command, 'create') + @ctx.error('Heroku app could not be created') unless status.success? + @ctx.puts(output) + + new_remote = output.split("\n").last.split("|").last.strip + result = @ctx.system('git', 'remote', 'add', 'heroku', new_remote) + msg = "Heroku app created, but couldn’t be set as a git remote" + @ctx.error(msg) unless result.success? + end + + def heroku_deploy(branch_to_deploy) + result = @ctx.system('git', 'push', '-u', 'heroku', "#{branch_to_deploy}:master") + @ctx.error("Could not deploy to Heroku") unless result.success? + end + + def heroku_download + return if heroku_installed? + + result = @ctx.system('curl', '-o', heroku_download_path, DOWNLOAD_URLS[@ctx.os], chdir: ShopifyCli::ROOT) + @ctx.error("Heroku CLI could not be downloaded") unless result.success? + @ctx.error("Heroku CLI could not be downloaded") unless File.exist?(heroku_download_path) + end + + def heroku_download_filename + URI.parse(DOWNLOAD_URLS[@ctx.os]).path.split('/').last + end + + def heroku_download_path + File.join(ShopifyCli::ROOT, heroku_download_filename) + end + + def heroku_git_remote + output, status = @ctx.capture2e('git', 'remote', 'get-url', 'heroku') + status.success? ? output : nil + end + + def heroku_install + return if heroku_installed? + + result = @ctx.system('tar', '-xf', heroku_download_path, chdir: ShopifyCli::ROOT) + @ctx.error("Could not install Heroku CLI") unless result.success? + + @ctx.rm(heroku_download_path) + end + + def heroku_installed? + _output, status = @ctx.capture2e(heroku_command, '--version') + status.success? + rescue + false + end + + def heroku_select_existing_app(app_name) + result = @ctx.system(heroku_command, 'git:remote', '-a', app_name) + msg = "Heroku app `#{app_name}` could not be selected" + @ctx.error(msg) unless result.success? + end + + def heroku_whoami + output, status = @ctx.capture2e(heroku_command, 'whoami') + return output.strip if status.success? + nil + end + end + end + end +end diff --git a/test/shopify-cli/commands/deploy/heroku_test.rb b/test/shopify-cli/commands/deploy/heroku_test.rb new file mode 100644 index 0000000000..99c56dd18b --- /dev/null +++ b/test/shopify-cli/commands/deploy/heroku_test.rb @@ -0,0 +1,477 @@ +require 'test_helper' + +module ShopifyCli + module Commands + class Deploy + class HerokuTest < MiniTest::Test + include TestHelpers::FakeUI + + def setup + super + + @download_filename = 'heroku-darwin-x64.tar.gz' + @download_path = File.join(ShopifyCli::ROOT, @download_filename) + @heroku_command = File.join(ShopifyCli::ROOT, 'heroku', 'bin', 'heroku').to_s + @heroku_remote = 'https://git.heroku.com/app-name.git' + + @status_mock = { + false: mock, + true: mock, + } + @status_mock[:false].stubs(:success?).returns(false) + @status_mock[:true].stubs(:success?).returns(true) + + File.stubs(:exist?) + + stub_successful_flow(os: :mac) + end + + def test_call_doesnt_download_heroku_cli_if_it_is_installed + @context.expects(:system) + .with('curl', '-o', @download_path, + Deploy::Heroku::DOWNLOAD_URLS[:mac], + chdir: ShopifyCli::ROOT) + .never + + run_cmd('deploy heroku') + end + + def test_call_downloads_heroku_cli_if_it_is_not_installed + stub_heroku_installed(status: false) + + @context.expects(:system) + .with('curl', '-o', @download_path, + Deploy::Heroku::DOWNLOAD_URLS[:mac], + chdir: ShopifyCli::ROOT) + .returns(@status_mock[:true]) + + run_cmd('deploy heroku') + end + + def test_call_raises_if_heroku_cli_download_fails + stub_heroku_installed(status: false) + + assert_raises ShopifyCli::Abort do + @context.expects(:system) + .with('curl', '-o', @download_path, + Deploy::Heroku::DOWNLOAD_URLS[:mac], + chdir: ShopifyCli::ROOT) + .returns(@status_mock[:false]) + + run_cmd('deploy heroku') + end + end + + def test_call_raises_if_heroku_cli_download_is_missing + stub_heroku_installed(status: false) + stub_heroku_download_exists(status: false) + + assert_raises ShopifyCli::Abort do + @context.expects(:system) + .with('curl', '-o', @download_path, + Deploy::Heroku::DOWNLOAD_URLS[:mac], + chdir: ShopifyCli::ROOT) + .returns(@status_mock[:true]) + + run_cmd('deploy heroku') + end + end + + def test_call_doesnt_install_heroku_cli_if_it_is_already_installed + @context.expects(:system) + .with('tar', '-xf', @download_path, chdir: ShopifyCli::ROOT) + .never + + run_cmd('deploy heroku') + end + + def test_call_installs_heroku_cli_if_it_is_downloaded + stub_heroku_installed(status: false) + + @context.expects(:system) + .with('tar', '-xf', @download_path, chdir: ShopifyCli::ROOT) + .returns(@status_mock[:true]) + + run_cmd('deploy heroku') + end + + def test_call_raises_if_heroku_cli_install_fails + stub_heroku_installed(status: false) + + assert_raises ShopifyCli::Abort do + @context.expects(:system) + .with('tar', '-xf', @download_path, chdir: ShopifyCli::ROOT) + .returns(@status_mock[:false]) + + run_cmd('deploy heroku') + end + end + + def test_call_raises_if_git_isnt_inited + stub_git_init(status: false, commits: false) + + assert_raises ShopifyCli::Abort do + run_cmd('deploy heroku') + end + end + + def test_call_raises_if_git_is_inited_but_there_are_no_commits + stub_git_init(status: true, commits: false) + + assert_raises ShopifyCli::Abort do + run_cmd('deploy heroku') + end + end + + def test_call_uses_existing_heroku_auth_if_available + @context.expects(:capture2e) + .with(@heroku_command, 'whoami') + .returns(['username', @status_mock[:true]]) + + CLI::UI::SpinGroup.any_instance.expects(:add).with( + 'Authenticated with Heroku as `username`' + ) + + capture_io do + run_cmd('deploy heroku') + end + end + + def test_call_attempts_to_authenticate_with_heroku_if_not_already_authed + stub_heroku_whoami(status: false) + + @context.expects(:system) + .with(@heroku_command, 'login') + .returns(@status_mock[:true]) + + run_cmd('deploy heroku') + end + + def test_call_raises_if_heroku_auth_fails + stub_heroku_whoami(status: false) + stub_heroku_login(status: false) + + assert_raises ShopifyCli::Abort do + run_cmd('deploy heroku') + end + end + + def test_call_uses_existing_heroku_app_if_available + @context.expects(:capture2e) + .with('git', 'remote', 'get-url', 'heroku') + .returns([@heroku_remote, @status_mock[:true]]) + + CLI::UI::SpinGroup.any_instance.expects(:add).with( + 'Heroku app `app-name` selected' + ) + + capture_io do + run_cmd('deploy heroku') + end + end + + def test_call_lets_you_choose_existing_heroku_app + stub_git_remote(status: false, remote: 'heroku') + + CLI::UI::Prompt.expects(:ask) + .with('No existing Heroku app found. What would you like to do?') + .returns(:existing) + + CLI::UI::Prompt.expects(:ask) + .with('What is your Heroku app’s name?') + .returns('app-name') + + @context.expects(:system) + .with(@heroku_command, 'git:remote', '-a', 'app-name') + .returns(@status_mock[:true]) + + run_cmd('deploy heroku') + end + + def test_call_raises_if_choosing_existing_heroku_app_fails + stub_git_remote(status: false, remote: 'heroku') + + CLI::UI::Prompt.expects(:ask) + .with('No existing Heroku app found. What would you like to do?') + .returns(:existing) + + CLI::UI::Prompt.expects(:ask) + .with('What is your Heroku app’s name?') + .returns('app-name') + + @context.expects(:system) + .with(@heroku_command, 'git:remote', '-a', 'app-name') + .returns(@status_mock[:false]) + + assert_raises ShopifyCli::Abort do + run_cmd('deploy heroku') + end + end + + def test_call_lets_you_create_new_heroku_app + stub_git_remote(status: false, remote: 'heroku') + + CLI::UI::Prompt.expects(:ask) + .with('No existing Heroku app found. What would you like to do?') + .returns(:new) + + output = <<~EOS + Creating app... done, ⬢ app-name + https://app-name.herokuapp.com/ | #{@heroku_remote} + EOS + + @context.expects(:capture2e) + .with(@heroku_command, 'create') + .returns([output, @status_mock[:true]]) + + @context.expects(:system) + .with('git', 'remote', 'add', 'heroku', @heroku_remote) + .returns(@status_mock[:true]) + + run_cmd('deploy heroku') + end + + def test_call_raises_if_creating_new_heroku_app_fails + stub_git_remote(status: false, remote: 'heroku') + + CLI::UI::Prompt.expects(:ask) + .with('No existing Heroku app found. What would you like to do?') + .returns(:new) + + @context.expects(:capture2e) + .with(@heroku_command, 'create') + .returns(['', @status_mock[:false]]) + + @context.expects(:system) + .with('git', 'remote', 'add', 'heroku', @heroku_remote) + .never + + assert_raises ShopifyCli::Abort do + run_cmd('deploy heroku') + end + end + + def test_call_raises_if_setting_remote_heroku_fails + stub_git_remote(status: false, remote: 'heroku') + + CLI::UI::Prompt.expects(:ask) + .with('No existing Heroku app found. What would you like to do?') + .returns(:new) + + output = <<~EOS + Creating app... done, ⬢ app-name + https://app-name.herokuapp.com/ | #{@heroku_remote} + EOS + + @context.expects(:capture2e) + .with(@heroku_command, 'create') + .returns([output, @status_mock[:true]]) + + @context.expects(:system) + .with('git', 'remote', 'add', 'heroku', @heroku_remote) + .returns(@status_mock[:false]) + + assert_raises ShopifyCli::Abort do + run_cmd('deploy heroku') + end + end + + def test_call_doesnt_prompt_if_only_one_branch_exists + @context.expects(:capture2e) + .with('git', 'branch', '--list', '--format=%(refname:short)') + .returns(["master\n", @status_mock[:true]]) + + CLI::UI::SpinGroup.any_instance.expects(:add).with( + 'Git branch `master` selected for deploy' + ) + + capture_io do + run_cmd('deploy heroku') + end + end + + def test_call_lets_you_specify_a_branch_if_multiple_exist + stub_git_branches(multiple: true) + + CLI::UI::Prompt.expects(:ask) + .with('What branch would you like to deploy?') + .returns('other_branch') + + @context.expects(:system) + .with('git', 'push', '-u', 'heroku', "other_branch:master") + .returns(@status_mock[:true]) + + run_cmd('deploy heroku') + end + + def test_call_raises_if_finding_branches_fails + @context.stubs(:capture2e) + .with('git', 'branch', '--list', '--format=%(refname:short)') + .returns(['', @status_mock[:false]]) + + assert_raises ShopifyCli::Abort do + run_cmd('deploy heroku') + end + end + + def test_call_tries_to_deploy_to_heroku + @context.expects(:system) + .with('git', 'push', '-u', 'heroku', "master:master") + .returns(@status_mock[:true]) + + run_cmd('deploy heroku') + end + + def test_call_raises_if_deploy_fails + @context.expects(:system) + .with('git', 'push', '-u', 'heroku', "master:master") + .returns(@status_mock[:false]) + + assert_raises ShopifyCli::Abort do + run_cmd('deploy heroku') + end + end + + private + + def stub_successful_flow(os:) + stub_git_init(status: true, commits: true) + stub_os(os: os) + stub_heroku_downloaded(status: true) + stub_heroku_download_exists(status: true) + stub_tar(status: true) + stub_heroku_installed(status: true) + stub_heroku_whoami(status: true) + stub_heroku_login(status: true) + stub_heroku_select_app(status: true) + stub_git_remote(status: true, remote: 'heroku') + stub_git_remote(status: true, remote: 'origin') + stub_git_branches(multiple: false) + stub_heroku_deploy(status: true) + end + + def stub_git_branches(multiple:) + output = "master\n" + output << "other_branch\n" if multiple + + @context.stubs(:capture2e) + .with('git', 'branch', '--list', '--format=%(refname:short)') + .returns([output, @status_mock[:true]]) + end + + def stub_git_init(status:, commits:) + output = if commits == true + <<~EOS + On branch master + Your branch is up to date with 'heroku/master'. + + nothing to commit, working tree clean + EOS + else + <<~EOS + On branch master + + No commits yet + EOS + end + + @context.stubs(:capture2e) + .with('git', 'status') + .returns([output, @status_mock[:"#{status}"]]) + end + + def stub_git_remote(status:, remote:) + output = if status == true + @heroku_remote + else + "fatal: No such remote '#{remote}'" + end + + @context.stubs(:capture2e) + .with('git', 'remote', 'get-url', remote) + .returns([output, @status_mock[:"#{status}"]]) + end + + def stub_heroku_deploy(status:) + @context.stubs(:system) + .with('git', 'push', '-u', 'heroku', "master:master") + .returns(@status_mock[:"#{status}"]) + end + + def stub_heroku_download_exists(status:) + File.stubs(:exist?) + .with(@download_path) + .returns(status) + end + + def stub_heroku_downloaded(status:) + @context.stubs(:system) + .with('curl', '-o', @download_path, + Deploy::Heroku::DOWNLOAD_URLS[:mac], + chdir: ShopifyCli::ROOT) + .returns(@status_mock[:"#{status}"]) + end + + def stub_heroku_installed(status:) + File.stubs(:exist?) + .with(@heroku_command) + .returns(status) + + @context.stubs(:capture2e) + .with(@heroku_command, '--version') + .returns(['', @status_mock[:"#{status}"]]) + + @context.stubs(:capture2e) + .with('heroku', '--version') + .returns(['', @status_mock[:"#{status}"]]) + end + + def stub_heroku_login(status:) + @context.stubs(:system) + .with(@heroku_command, 'login') + .returns(@status_mock[:"#{status}"]) + + @context.stubs(:system) + .with('heroku', 'login') + .returns(@status_mock[:"#{status}"]) + end + + def stub_heroku_select_app(status:) + @context.stubs(:capture2e) + .with(@heroku_command, 'git:remote', '-a', 'app-name') + .returns(['', @status_mock[:"#{status}"]]) + + @context.stubs(:capture2e) + .with(@heroku_command, 'git:remote', '-a', 'app-name') + .returns(['', @status_mock[:"#{status}"]]) + end + + def stub_heroku_whoami(status:) + output = status ? 'username' : nil + + @context.stubs(:capture2e) + .with(@heroku_command, 'whoami') + .returns([output, @status_mock[:"#{status}"]]) + + @context.stubs(:capture2e) + .with('heroku', 'whoami') + .returns([output, @status_mock[:"#{status}"]]) + end + + def stub_tar(status:) + @context.stubs(:system) + .with('tar', '-xf', @download_path, chdir: ShopifyCli::ROOT) + .returns(@status_mock[:"#{status}"]) + + FileUtils.stubs(:rm) + .with(@download_path) + .returns(status) + end + + def stub_os(os:) + ShopifyCli::Context.any_instance.stubs(:os).returns(os) + end + end + end + end +end From 40da8cd3e78b5a97b1ef828718a1a3cc74526821 Mon Sep 17 00:00:00 2001 From: Kevin O'Sullivan Date: Wed, 8 Apr 2020 20:17:55 -0400 Subject: [PATCH 2/7] Moving ShopifyCli::Helpers::PartnersAPI to ShopifyCli::PartnersAPI - moving lib/shopify-cli/helpers/partners_api.rb to lib/shopify-cli/partners_api.rb - moving test/shopify-cli/helpers/partners_api_test.rb to test/shopify-cli/partners_api_test.rb - changing references of Helpers::PartnersAPI to ShopifyCli::PartnersAPI in impacted helpers and tasks - changing autoload from lib/shopify-cli/helpers.rb to lib/shopify_cli.rb --- lib/shopify-cli/helpers.rb | 1 - lib/shopify-cli/helpers/organizations.rb | 6 +- lib/shopify-cli/helpers/partners_api.rb | 64 ------------- lib/shopify-cli/partners_api.rb | 62 ++++++++++++ .../tasks/authenticate_identity.rb | 6 +- lib/shopify-cli/tasks/create_api_client.rb | 2 +- lib/shopify-cli/tasks/ensure_loopback_url.rb | 4 +- lib/shopify-cli/tasks/ensure_test_shop.rb | 2 +- .../tasks/update_dashboard_urls.rb | 4 +- lib/shopify_cli.rb | 1 + test/shopify-cli/helpers/partners_api_test.rb | 94 ------------------- test/shopify-cli/partners_api_test.rb | 89 ++++++++++++++++++ test/task/authenticate_identity_test.rb | 4 +- 13 files changed, 166 insertions(+), 173 deletions(-) delete mode 100644 lib/shopify-cli/helpers/partners_api.rb create mode 100644 lib/shopify-cli/partners_api.rb delete mode 100644 test/shopify-cli/helpers/partners_api_test.rb create mode 100644 test/shopify-cli/partners_api_test.rb diff --git a/lib/shopify-cli/helpers.rb b/lib/shopify-cli/helpers.rb index 74364f5048..024f78b9e6 100644 --- a/lib/shopify-cli/helpers.rb +++ b/lib/shopify-cli/helpers.rb @@ -8,7 +8,6 @@ module Helpers autoload :Haikunator, 'shopify-cli/helpers/haikunator' autoload :Node, 'shopify-cli/helpers/node' autoload :Organizations, 'shopify-cli/helpers/organizations' - autoload :PartnersAPI, 'shopify-cli/helpers/partners_api' autoload :PkceToken, 'shopify-cli/helpers/pkce_token' autoload :Store, 'shopify-cli/helpers/store' autoload :String, 'shopify-cli/helpers/string' diff --git a/lib/shopify-cli/helpers/organizations.rb b/lib/shopify-cli/helpers/organizations.rb index e568a73c74..e942c2d477 100644 --- a/lib/shopify-cli/helpers/organizations.rb +++ b/lib/shopify-cli/helpers/organizations.rb @@ -3,7 +3,7 @@ module Helpers class Organizations class << self def fetch_all(ctx) - resp = Helpers::PartnersAPI.query(ctx, 'all_organizations') + resp = ShopifyCli::PartnersAPI.query(ctx, 'all_organizations') resp['data']['organizations']['nodes'].map do |org| org['stores'] = org['stores']['nodes'] org @@ -11,7 +11,7 @@ def fetch_all(ctx) end def fetch(ctx, id:) - resp = Helpers::PartnersAPI.query(ctx, 'find_organization', id: id) + resp = ShopifyCli::PartnersAPI.query(ctx, 'find_organization', id: id) org = resp['data']['organizations']['nodes'].first return nil if org.nil? org['stores'] = org['stores']['nodes'] @@ -19,7 +19,7 @@ def fetch(ctx, id:) end def fetch_with_app(ctx) - resp = Helpers::PartnersAPI.query(ctx, 'all_orgs_with_apps') + resp = ShopifyCli::PartnersAPI.query(ctx, 'all_orgs_with_apps') resp['data']['organizations']['nodes'].map do |org| org['stores'] = org['stores']['nodes'] org['apps'] = org['apps']['nodes'] diff --git a/lib/shopify-cli/helpers/partners_api.rb b/lib/shopify-cli/helpers/partners_api.rb deleted file mode 100644 index dd408dedfc..0000000000 --- a/lib/shopify-cli/helpers/partners_api.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'shopify_cli' - -module ShopifyCli - module Helpers - class PartnersAPI < API - ENV_VAR = 'SHOPIFY_APP_CLI_LOCAL_PARTNERS' - AUTH_PROD_URI = 'https://accounts.shopify.com' - AUTH_DEV_URI = 'https://identity.myshopify.io' - PROD_URI = 'https://partners.shopify.com' - DEV_URI = 'https://partners.myshopify.io/' - PROD_ID = '271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6' - DEV_ID = 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978' - PROD_CLI_ID = 'fbdb2649-e327-4907-8f67-908d24cfd7e3' - DEV_CLI_ID = 'e5380e02-312a-7408-5718-e07017e9cf52' - - class << self - def id - ENV[ENV_VAR].nil? ? PROD_ID : DEV_ID - end - - def cli_id - ENV[ENV_VAR].nil? ? PROD_CLI_ID : DEV_CLI_ID - end - - def auth_endpoint - ENV[ENV_VAR].nil? ? AUTH_PROD_URI : AUTH_DEV_URI - end - - def endpoint - ENV[ENV_VAR].nil? ? PROD_URI : DEV_URI - end - - def query(ctx, body, **variables) - authenticated_req(ctx) do - api_client(ctx).query(body, variables: variables) - end - end - - private - - def authenticated_req(ctx) - yield - rescue API::APIRequestUnauthorizedError - Tasks::AuthenticateIdentity.call(ctx) - retry - rescue API::APIRequestNotFoundError - ctx.abort("error: Your account was not found. Please sign up at https://partners.shopify.com/signup") - end - - def api_client(ctx) - new( - ctx: ctx, - token: Helpers::PkceToken.read(ctx), - url: "#{endpoint}/api/cli/graphql", - ) - end - end - - def auth_headers(token) - { Authorization: "Bearer #{token}" } - end - end - end -end diff --git a/lib/shopify-cli/partners_api.rb b/lib/shopify-cli/partners_api.rb new file mode 100644 index 0000000000..089db78747 --- /dev/null +++ b/lib/shopify-cli/partners_api.rb @@ -0,0 +1,62 @@ +require 'shopify_cli' + +module ShopifyCli + class PartnersAPI < API + ENV_VAR = 'SHOPIFY_APP_CLI_LOCAL_PARTNERS' + AUTH_PROD_URI = 'https://accounts.shopify.com' + AUTH_DEV_URI = 'https://identity.myshopify.io' + PROD_URI = 'https://partners.shopify.com' + DEV_URI = 'https://partners.myshopify.io/' + PROD_ID = '271e16d403dfa18082ffb3d197bd2b5f4479c3fc32736d69296829cbb28d41a6' + DEV_ID = 'df89d73339ac3c6c5f0a98d9ca93260763e384d51d6038da129889c308973978' + PROD_CLI_ID = 'fbdb2649-e327-4907-8f67-908d24cfd7e3' + DEV_CLI_ID = 'e5380e02-312a-7408-5718-e07017e9cf52' + + class << self + def id + ENV[ENV_VAR].nil? ? PROD_ID : DEV_ID + end + + def cli_id + ENV[ENV_VAR].nil? ? PROD_CLI_ID : DEV_CLI_ID + end + + def auth_endpoint + ENV[ENV_VAR].nil? ? AUTH_PROD_URI : AUTH_DEV_URI + end + + def endpoint + ENV[ENV_VAR].nil? ? PROD_URI : DEV_URI + end + + def query(ctx, body, **variables) + authenticated_req(ctx) do + api_client(ctx).query(body, variables: variables) + end + end + + private + + def authenticated_req(ctx) + yield + rescue API::APIRequestUnauthorizedError + Tasks::AuthenticateIdentity.call(ctx) + retry + rescue API::APIRequestNotFoundError + ctx.puts("{{x}} error: Your account was not found. Please sign up at https://partners.shopify.com/signup") + end + + def api_client(ctx) + new( + ctx: ctx, + token: Helpers::PkceToken.read(ctx), + url: "#{endpoint}/api/cli/graphql", + ) + end + end + + def auth_headers(token) + { Authorization: "Bearer #{token}" } + end + end +end diff --git a/lib/shopify-cli/tasks/authenticate_identity.rb b/lib/shopify-cli/tasks/authenticate_identity.rb index 751a6d49de..220e88b641 100644 --- a/lib/shopify-cli/tasks/authenticate_identity.rb +++ b/lib/shopify-cli/tasks/authenticate_identity.rb @@ -9,10 +9,10 @@ def call(ctx) OAuth.new( ctx: ctx, service: 'identity', - client_id: Helpers::PartnersAPI.cli_id, + client_id: ShopifyCli::PartnersAPI.cli_id, scopes: SCOPES, - request_exchange: Helpers::PartnersAPI.id, - ).authenticate("#{Helpers::PartnersAPI.auth_endpoint}/oauth") + request_exchange: ShopifyCli::PartnersAPI.id, + ).authenticate("#{ShopifyCli::PartnersAPI.auth_endpoint}/oauth") end end end diff --git a/lib/shopify-cli/tasks/create_api_client.rb b/lib/shopify-cli/tasks/create_api_client.rb index 177fc8a7c8..7a5cfe3821 100644 --- a/lib/shopify-cli/tasks/create_api_client.rb +++ b/lib/shopify-cli/tasks/create_api_client.rb @@ -6,7 +6,7 @@ class CreateApiClient < ShopifyCli::Task VALID_APP_TYPES = %w(public custom) def call(ctx, org_id:, title:, app_url:, type:) - resp = Helpers::PartnersAPI.query( + resp = ShopifyCli::PartnersAPI.query( ctx, 'create_app', org: org_id.to_i, diff --git a/lib/shopify-cli/tasks/ensure_loopback_url.rb b/lib/shopify-cli/tasks/ensure_loopback_url.rb index 3d212cf5c9..a9d1fd40cb 100644 --- a/lib/shopify-cli/tasks/ensure_loopback_url.rb +++ b/lib/shopify-cli/tasks/ensure_loopback_url.rb @@ -4,13 +4,13 @@ class EnsureLoopbackURL < ShopifyCli::Task def call(ctx) @ctx = ctx api_key = Project.current.env.api_key - result = Helpers::PartnersAPI.query(ctx, 'get_app_urls', apiKey: api_key) + result = ShopifyCli::PartnersAPI.query(ctx, 'get_app_urls', apiKey: api_key) loopback = OAuth::REDIRECT_HOST app = result['data']['app'] urls = app['redirectUrlWhitelist'] if urls.grep(/#{loopback}/).empty? with_loopback = urls.push(loopback) - ShopifyCli::Helpers::PartnersAPI.query(@ctx, 'update_dashboard_urls', input: { + ShopifyCli::PartnersAPI.query(@ctx, 'update_dashboard_urls', input: { redirectUrlWhitelist: with_loopback, apiKey: api_key }) end diff --git a/lib/shopify-cli/tasks/ensure_test_shop.rb b/lib/shopify-cli/tasks/ensure_test_shop.rb index 0a9130860c..19aaddfd95 100644 --- a/lib/shopify-cli/tasks/ensure_test_shop.rb +++ b/lib/shopify-cli/tasks/ensure_test_shop.rb @@ -9,7 +9,7 @@ def call(ctx) return if shop['transferDisabled'] == true return unless CLI::UI::Prompt.confirm("Do you want to convert #{project.env.shop} to a test shop."\ " This will enable you to install your app on this store.") - ShopifyCli::Helpers::PartnersAPI.query(ctx, 'convert_dev_to_test_store', input: { + ShopifyCli::PartnersAPI.query(ctx, 'convert_dev_to_test_store', input: { organizationID: shop['orgID'].to_i, shopId: shop['shopId'], }) diff --git a/lib/shopify-cli/tasks/update_dashboard_urls.rb b/lib/shopify-cli/tasks/update_dashboard_urls.rb index 71fd41a4f0..4544aa8ff1 100644 --- a/lib/shopify-cli/tasks/update_dashboard_urls.rb +++ b/lib/shopify-cli/tasks/update_dashboard_urls.rb @@ -7,12 +7,12 @@ def call(ctx, url:, callback_url:) @ctx = ctx project = ShopifyCli::Project.current api_key = project.env.api_key - result = Helpers::PartnersAPI.query(ctx, 'get_app_urls', apiKey: api_key) + result = ShopifyCli::PartnersAPI.query(ctx, 'get_app_urls', apiKey: api_key) app = result['data']['app'] consent = check_application_url(app['applicationUrl'], url) constructed_urls = construct_redirect_urls(app['redirectUrlWhitelist'], url, callback_url) return if url == app['applicationUrl'] - ShopifyCli::Helpers::PartnersAPI.query(@ctx, 'update_dashboard_urls', input: { + ShopifyCli::PartnersAPI.query(@ctx, 'update_dashboard_urls', input: { applicationUrl: consent ? url : app['applicationUrl'], redirectUrlWhitelist: constructed_urls, apiKey: api_key }) diff --git a/lib/shopify_cli.rb b/lib/shopify_cli.rb index d19b3f63c1..91db9bc81b 100644 --- a/lib/shopify_cli.rb +++ b/lib/shopify_cli.rb @@ -109,6 +109,7 @@ module ShopifyCli autoload :OAuth, 'shopify-cli/oauth' autoload :Options, 'shopify-cli/options' autoload :ProcessSupervision, 'shopify-cli/process_supervision' + autoload :PartnersAPI, 'shopify-cli/partners_api' autoload :Project, 'shopify-cli/project' autoload :ProjectType, 'shopify-cli/project_type' autoload :SubCommand, 'shopify-cli/sub_command' diff --git a/test/shopify-cli/helpers/partners_api_test.rb b/test/shopify-cli/helpers/partners_api_test.rb deleted file mode 100644 index e85defaaaa..0000000000 --- a/test/shopify-cli/helpers/partners_api_test.rb +++ /dev/null @@ -1,94 +0,0 @@ -require 'test_helper' - -module ShopifyCli - module Helpers - class PartnersAPITest < MiniTest::Test - include TestHelpers::Project - - def setup - super - Helpers::PkceToken.stubs(:read).returns('token123') - end - - def test_id - ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' - assert_equal PartnersAPI::DEV_ID, PartnersAPI.id - ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') - assert_equal PartnersAPI::PROD_ID, PartnersAPI.id - end - - def test_cli_id - ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' - assert_equal PartnersAPI::DEV_CLI_ID, PartnersAPI.cli_id - ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') - assert_equal PartnersAPI::PROD_CLI_ID, PartnersAPI.cli_id - end - - def test_auth_endpoint - ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' - assert_equal PartnersAPI::AUTH_DEV_URI, PartnersAPI.auth_endpoint - ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') - assert_equal PartnersAPI::AUTH_PROD_URI, PartnersAPI.auth_endpoint - end - - def test_endpoint - ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' - assert_equal PartnersAPI::DEV_URI, PartnersAPI.endpoint - ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') - assert_equal PartnersAPI::PROD_URI, PartnersAPI.endpoint - end - - def test_query_calls_partners_api - api_stub = Object.new - PartnersAPI.expects(:new).with( - ctx: @context, - token: 'token123', - url: "#{PartnersAPI.endpoint}/api/cli/graphql", - ).returns(api_stub) - api_stub.expects(:query).with('query', variables: {}).returns('response') - assert_equal 'response', PartnersAPI.query(@context, 'query') - end - - def test_query_can_reauth - api_stub = Object.new - PartnersAPI.expects(:new).with( - ctx: @context, - token: 'token123', - url: "#{PartnersAPI.endpoint}/api/cli/graphql", - ).returns(api_stub).twice - api_stub.expects(:query).with('query', variables: {}).returns('response') - api_stub.expects(:query).raises(API::APIRequestUnauthorizedError) - Tasks::AuthenticateIdentity.expects(:call) - PartnersAPI.query(@context, 'query') - end - - def test_query_aborts_without_partners_account - api_stub = Object.new - PartnersAPI.expects(:new).with( - ctx: @context, - token: 'token123', - url: "#{PartnersAPI.endpoint}/api/cli/graphql", - ).returns(api_stub) - api_stub.expects(:query).raises(API::APIRequestNotFoundError) - e = assert_raises ShopifyCli::Abort do - PartnersAPI.query(@context, 'query') - end - assert_match( - 'error: Your account was not found. Please sign up at https://partners.shopify.com/signup', - e.message - ) - end - - def test_query - api_stub = Object.new - PartnersAPI.expects(:new).with( - ctx: @context, - token: 'token123', - url: "#{PartnersAPI.endpoint}/api/cli/graphql", - ).returns(api_stub) - api_stub.expects(:query).with('query', variables: {}).returns('response') - assert_equal 'response', PartnersAPI.query(@context, 'query') - end - end - end -end diff --git a/test/shopify-cli/partners_api_test.rb b/test/shopify-cli/partners_api_test.rb new file mode 100644 index 0000000000..d7009b19dd --- /dev/null +++ b/test/shopify-cli/partners_api_test.rb @@ -0,0 +1,89 @@ +require 'test_helper' + +module ShopifyCli + class PartnersAPITest < MiniTest::Test + include TestHelpers::Project + + def setup + super + Helpers::PkceToken.stubs(:read).returns('token123') + end + + def test_id + ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' + assert_equal PartnersAPI::DEV_ID, PartnersAPI.id + ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') + assert_equal PartnersAPI::PROD_ID, PartnersAPI.id + end + + def test_cli_id + ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' + assert_equal PartnersAPI::DEV_CLI_ID, PartnersAPI.cli_id + ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') + assert_equal PartnersAPI::PROD_CLI_ID, PartnersAPI.cli_id + end + + def test_auth_endpoint + ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' + assert_equal PartnersAPI::AUTH_DEV_URI, PartnersAPI.auth_endpoint + ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') + assert_equal PartnersAPI::AUTH_PROD_URI, PartnersAPI.auth_endpoint + end + + def test_endpoint + ENV['SHOPIFY_APP_CLI_LOCAL_PARTNERS'] = '1' + assert_equal PartnersAPI::DEV_URI, PartnersAPI.endpoint + ENV.delete('SHOPIFY_APP_CLI_LOCAL_PARTNERS') + assert_equal PartnersAPI::PROD_URI, PartnersAPI.endpoint + end + + def test_query_calls_partners_api + api_stub = Object.new + PartnersAPI.expects(:new).with( + ctx: @context, + token: 'token123', + url: "#{PartnersAPI.endpoint}/api/cli/graphql", + ).returns(api_stub) + api_stub.expects(:query).with('query', variables: {}).returns('response') + assert_equal 'response', PartnersAPI.query(@context, 'query') + end + + def test_query_can_reauth + api_stub = Object.new + PartnersAPI.expects(:new).with( + ctx: @context, + token: 'token123', + url: "#{PartnersAPI.endpoint}/api/cli/graphql", + ).returns(api_stub).twice + api_stub.expects(:query).with('query', variables: {}).returns('response') + api_stub.expects(:query).raises(API::APIRequestUnauthorizedError) + Tasks::AuthenticateIdentity.expects(:call) + PartnersAPI.query(@context, 'query') + end + + def test_query_fails_gracefully_without_partners_account + api_stub = Object.new + PartnersAPI.expects(:new).with( + ctx: @context, + token: 'token123', + url: "#{PartnersAPI.endpoint}/api/cli/graphql", + ).returns(api_stub) + api_stub.expects(:query).raises(API::APIRequestNotFoundError) + @context.expects(:puts).with( + "{{x}} error: Your account was not found. Please sign up at https://partners.shopify.com/signup", + ) + PartnersAPI.query(@context, 'query') + end + + def test_query + api_stub = Object.new + PartnersAPI.expects(:new).with( + ctx: @context, + token: 'token123', + url: "#{PartnersAPI.endpoint}/api/cli/graphql", + ).returns(api_stub) + api_stub.expects(:query).with('query', variables: {}).returns('response') + assert_equal 'response', PartnersAPI.query(@context, 'query') + end + end +end diff --git a/test/task/authenticate_identity_test.rb b/test/task/authenticate_identity_test.rb index e30826a748..61989dad11 100644 --- a/test/task/authenticate_identity_test.rb +++ b/test/task/authenticate_identity_test.rb @@ -12,9 +12,9 @@ def test_negotiate_oauth_and_store_token .with( ctx: @context, service: 'identity', - client_id: Helpers::PartnersAPI.cli_id, + client_id: PartnersAPI.cli_id, scopes: AuthenticateIdentity::SCOPES, - request_exchange: Helpers::PartnersAPI.id, + request_exchange: PartnersAPI.id, ).returns(@oauth_client) @oauth_client .expects(:authenticate) From 1c2a84814364725298158ff0816f94d244f6e73a Mon Sep 17 00:00:00 2001 From: Kevin O'Sullivan Date: Wed, 8 Apr 2020 20:17:55 -0400 Subject: [PATCH 3/7] Moving ShopifyCli::Helpers::PartnersAPI to ShopifyCli::PartnersAPI - moving lib/shopify-cli/helpers/partners_api.rb to lib/shopify-cli/partners_api.rb - moving test/shopify-cli/helpers/partners_api_test.rb to test/shopify-cli/partners_api_test.rb - changing references of Helpers::PartnersAPI to ShopifyCli::PartnersAPI in impacted helpers and tasks - changing autoload from lib/shopify-cli/helpers.rb to lib/shopify_cli.rb --- lib/shopify_cli.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/shopify_cli.rb b/lib/shopify_cli.rb index 91db9bc81b..d9166a92f6 100644 --- a/lib/shopify_cli.rb +++ b/lib/shopify_cli.rb @@ -108,8 +108,8 @@ module ShopifyCli autoload :Log, 'shopify-cli/log' autoload :OAuth, 'shopify-cli/oauth' autoload :Options, 'shopify-cli/options' - autoload :ProcessSupervision, 'shopify-cli/process_supervision' autoload :PartnersAPI, 'shopify-cli/partners_api' + autoload :ProcessSupervision, 'shopify-cli/process_supervision' autoload :Project, 'shopify-cli/project' autoload :ProjectType, 'shopify-cli/project_type' autoload :SubCommand, 'shopify-cli/sub_command' From 95bdbafab1e43211a8457b4a86e5dc48dd54ace2 Mon Sep 17 00:00:00 2001 From: Kevin O'Sullivan Date: Mon, 13 Apr 2020 11:50:51 -0400 Subject: [PATCH 4/7] Corrected output strings (spelling, grammar) --- lib/shopify-cli/commands/connect.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/shopify-cli/commands/connect.rb b/lib/shopify-cli/commands/connect.rb index 9c53f0e5de..b00a783b81 100644 --- a/lib/shopify-cli/commands/connect.rb +++ b/lib/shopify-cli/commands/connect.rb @@ -21,7 +21,7 @@ def fetch_org org_id = if orgs.count == 1 orgs.first["id"] else - CLI::UI::Prompt.ask('Which organization does this project belong to?') do |handler| + CLI::UI::Prompt.ask('To which organization does this project belong?') do |handler| orgs.each { |org| handler.option(org["businessName"]) { org["id"] } } end end @@ -33,7 +33,7 @@ def get_app(apps) app_id = if apps.count == 1 apps.first["id"] else - CLI::UI::Prompt.ask('Which app does this project belong to?') do |handler| + CLI::UI::Prompt.ask('To which app does this project belong?') do |handler| apps.each { |app| handler.option(app["title"]) { app["id"] } } end end @@ -44,7 +44,7 @@ def get_shop(shops, id) if shops.count == 1 shops.first elsif shops.count == 0 - @ctx.puts('No developement shops available.') + @ctx.puts('No development stores available.') @ctx.puts("Visit {{underline:https://partners.shopify.com/#{id}/stores}} to create one") else shop = CLI::UI::Prompt.ask('Which development store would you like to use?') do |handler| From 5d6dd3f9bdbcce1c8c730bf39e0ebc7b23e768a4 Mon Sep 17 00:00:00 2001 From: Kevin O'Sullivan Date: Mon, 13 Apr 2020 16:00:41 -0400 Subject: [PATCH 5/7] Updating test case related to last commit --- test/shopify-cli/commands/connect_test.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/shopify-cli/commands/connect_test.rb b/test/shopify-cli/commands/connect_test.rb index baaf1b06bc..9bf30c8772 100644 --- a/test/shopify-cli/commands/connect_test.rb +++ b/test/shopify-cli/commands/connect_test.rb @@ -35,7 +35,7 @@ def test_run }], }] ShopifyCli::Helpers::Organizations.stubs(:fetch_with_app).returns(response) - CLI::UI::Prompt.expects(:ask).with('Which organization does this project belong to?').returns(422) + CLI::UI::Prompt.expects(:ask).with('To which organization does this project belong?').returns(422) CLI::UI::Prompt.expects(:ask).with( 'Which development store would you like to use?' ).returns('store.myshopify.com') From b9369b6be211ca1ac789d9ebf9a6f9fe9150e0fa Mon Sep 17 00:00:00 2001 From: Kevin O'Sullivan Date: Mon, 13 Apr 2020 17:56:27 -0400 Subject: [PATCH 6/7] Missed one file deletion during rebase --- .../commands/deploy/heroku_test.rb | 477 ------------------ 1 file changed, 477 deletions(-) delete mode 100644 test/shopify-cli/commands/deploy/heroku_test.rb diff --git a/test/shopify-cli/commands/deploy/heroku_test.rb b/test/shopify-cli/commands/deploy/heroku_test.rb deleted file mode 100644 index 99c56dd18b..0000000000 --- a/test/shopify-cli/commands/deploy/heroku_test.rb +++ /dev/null @@ -1,477 +0,0 @@ -require 'test_helper' - -module ShopifyCli - module Commands - class Deploy - class HerokuTest < MiniTest::Test - include TestHelpers::FakeUI - - def setup - super - - @download_filename = 'heroku-darwin-x64.tar.gz' - @download_path = File.join(ShopifyCli::ROOT, @download_filename) - @heroku_command = File.join(ShopifyCli::ROOT, 'heroku', 'bin', 'heroku').to_s - @heroku_remote = 'https://git.heroku.com/app-name.git' - - @status_mock = { - false: mock, - true: mock, - } - @status_mock[:false].stubs(:success?).returns(false) - @status_mock[:true].stubs(:success?).returns(true) - - File.stubs(:exist?) - - stub_successful_flow(os: :mac) - end - - def test_call_doesnt_download_heroku_cli_if_it_is_installed - @context.expects(:system) - .with('curl', '-o', @download_path, - Deploy::Heroku::DOWNLOAD_URLS[:mac], - chdir: ShopifyCli::ROOT) - .never - - run_cmd('deploy heroku') - end - - def test_call_downloads_heroku_cli_if_it_is_not_installed - stub_heroku_installed(status: false) - - @context.expects(:system) - .with('curl', '-o', @download_path, - Deploy::Heroku::DOWNLOAD_URLS[:mac], - chdir: ShopifyCli::ROOT) - .returns(@status_mock[:true]) - - run_cmd('deploy heroku') - end - - def test_call_raises_if_heroku_cli_download_fails - stub_heroku_installed(status: false) - - assert_raises ShopifyCli::Abort do - @context.expects(:system) - .with('curl', '-o', @download_path, - Deploy::Heroku::DOWNLOAD_URLS[:mac], - chdir: ShopifyCli::ROOT) - .returns(@status_mock[:false]) - - run_cmd('deploy heroku') - end - end - - def test_call_raises_if_heroku_cli_download_is_missing - stub_heroku_installed(status: false) - stub_heroku_download_exists(status: false) - - assert_raises ShopifyCli::Abort do - @context.expects(:system) - .with('curl', '-o', @download_path, - Deploy::Heroku::DOWNLOAD_URLS[:mac], - chdir: ShopifyCli::ROOT) - .returns(@status_mock[:true]) - - run_cmd('deploy heroku') - end - end - - def test_call_doesnt_install_heroku_cli_if_it_is_already_installed - @context.expects(:system) - .with('tar', '-xf', @download_path, chdir: ShopifyCli::ROOT) - .never - - run_cmd('deploy heroku') - end - - def test_call_installs_heroku_cli_if_it_is_downloaded - stub_heroku_installed(status: false) - - @context.expects(:system) - .with('tar', '-xf', @download_path, chdir: ShopifyCli::ROOT) - .returns(@status_mock[:true]) - - run_cmd('deploy heroku') - end - - def test_call_raises_if_heroku_cli_install_fails - stub_heroku_installed(status: false) - - assert_raises ShopifyCli::Abort do - @context.expects(:system) - .with('tar', '-xf', @download_path, chdir: ShopifyCli::ROOT) - .returns(@status_mock[:false]) - - run_cmd('deploy heroku') - end - end - - def test_call_raises_if_git_isnt_inited - stub_git_init(status: false, commits: false) - - assert_raises ShopifyCli::Abort do - run_cmd('deploy heroku') - end - end - - def test_call_raises_if_git_is_inited_but_there_are_no_commits - stub_git_init(status: true, commits: false) - - assert_raises ShopifyCli::Abort do - run_cmd('deploy heroku') - end - end - - def test_call_uses_existing_heroku_auth_if_available - @context.expects(:capture2e) - .with(@heroku_command, 'whoami') - .returns(['username', @status_mock[:true]]) - - CLI::UI::SpinGroup.any_instance.expects(:add).with( - 'Authenticated with Heroku as `username`' - ) - - capture_io do - run_cmd('deploy heroku') - end - end - - def test_call_attempts_to_authenticate_with_heroku_if_not_already_authed - stub_heroku_whoami(status: false) - - @context.expects(:system) - .with(@heroku_command, 'login') - .returns(@status_mock[:true]) - - run_cmd('deploy heroku') - end - - def test_call_raises_if_heroku_auth_fails - stub_heroku_whoami(status: false) - stub_heroku_login(status: false) - - assert_raises ShopifyCli::Abort do - run_cmd('deploy heroku') - end - end - - def test_call_uses_existing_heroku_app_if_available - @context.expects(:capture2e) - .with('git', 'remote', 'get-url', 'heroku') - .returns([@heroku_remote, @status_mock[:true]]) - - CLI::UI::SpinGroup.any_instance.expects(:add).with( - 'Heroku app `app-name` selected' - ) - - capture_io do - run_cmd('deploy heroku') - end - end - - def test_call_lets_you_choose_existing_heroku_app - stub_git_remote(status: false, remote: 'heroku') - - CLI::UI::Prompt.expects(:ask) - .with('No existing Heroku app found. What would you like to do?') - .returns(:existing) - - CLI::UI::Prompt.expects(:ask) - .with('What is your Heroku app’s name?') - .returns('app-name') - - @context.expects(:system) - .with(@heroku_command, 'git:remote', '-a', 'app-name') - .returns(@status_mock[:true]) - - run_cmd('deploy heroku') - end - - def test_call_raises_if_choosing_existing_heroku_app_fails - stub_git_remote(status: false, remote: 'heroku') - - CLI::UI::Prompt.expects(:ask) - .with('No existing Heroku app found. What would you like to do?') - .returns(:existing) - - CLI::UI::Prompt.expects(:ask) - .with('What is your Heroku app’s name?') - .returns('app-name') - - @context.expects(:system) - .with(@heroku_command, 'git:remote', '-a', 'app-name') - .returns(@status_mock[:false]) - - assert_raises ShopifyCli::Abort do - run_cmd('deploy heroku') - end - end - - def test_call_lets_you_create_new_heroku_app - stub_git_remote(status: false, remote: 'heroku') - - CLI::UI::Prompt.expects(:ask) - .with('No existing Heroku app found. What would you like to do?') - .returns(:new) - - output = <<~EOS - Creating app... done, ⬢ app-name - https://app-name.herokuapp.com/ | #{@heroku_remote} - EOS - - @context.expects(:capture2e) - .with(@heroku_command, 'create') - .returns([output, @status_mock[:true]]) - - @context.expects(:system) - .with('git', 'remote', 'add', 'heroku', @heroku_remote) - .returns(@status_mock[:true]) - - run_cmd('deploy heroku') - end - - def test_call_raises_if_creating_new_heroku_app_fails - stub_git_remote(status: false, remote: 'heroku') - - CLI::UI::Prompt.expects(:ask) - .with('No existing Heroku app found. What would you like to do?') - .returns(:new) - - @context.expects(:capture2e) - .with(@heroku_command, 'create') - .returns(['', @status_mock[:false]]) - - @context.expects(:system) - .with('git', 'remote', 'add', 'heroku', @heroku_remote) - .never - - assert_raises ShopifyCli::Abort do - run_cmd('deploy heroku') - end - end - - def test_call_raises_if_setting_remote_heroku_fails - stub_git_remote(status: false, remote: 'heroku') - - CLI::UI::Prompt.expects(:ask) - .with('No existing Heroku app found. What would you like to do?') - .returns(:new) - - output = <<~EOS - Creating app... done, ⬢ app-name - https://app-name.herokuapp.com/ | #{@heroku_remote} - EOS - - @context.expects(:capture2e) - .with(@heroku_command, 'create') - .returns([output, @status_mock[:true]]) - - @context.expects(:system) - .with('git', 'remote', 'add', 'heroku', @heroku_remote) - .returns(@status_mock[:false]) - - assert_raises ShopifyCli::Abort do - run_cmd('deploy heroku') - end - end - - def test_call_doesnt_prompt_if_only_one_branch_exists - @context.expects(:capture2e) - .with('git', 'branch', '--list', '--format=%(refname:short)') - .returns(["master\n", @status_mock[:true]]) - - CLI::UI::SpinGroup.any_instance.expects(:add).with( - 'Git branch `master` selected for deploy' - ) - - capture_io do - run_cmd('deploy heroku') - end - end - - def test_call_lets_you_specify_a_branch_if_multiple_exist - stub_git_branches(multiple: true) - - CLI::UI::Prompt.expects(:ask) - .with('What branch would you like to deploy?') - .returns('other_branch') - - @context.expects(:system) - .with('git', 'push', '-u', 'heroku', "other_branch:master") - .returns(@status_mock[:true]) - - run_cmd('deploy heroku') - end - - def test_call_raises_if_finding_branches_fails - @context.stubs(:capture2e) - .with('git', 'branch', '--list', '--format=%(refname:short)') - .returns(['', @status_mock[:false]]) - - assert_raises ShopifyCli::Abort do - run_cmd('deploy heroku') - end - end - - def test_call_tries_to_deploy_to_heroku - @context.expects(:system) - .with('git', 'push', '-u', 'heroku', "master:master") - .returns(@status_mock[:true]) - - run_cmd('deploy heroku') - end - - def test_call_raises_if_deploy_fails - @context.expects(:system) - .with('git', 'push', '-u', 'heroku', "master:master") - .returns(@status_mock[:false]) - - assert_raises ShopifyCli::Abort do - run_cmd('deploy heroku') - end - end - - private - - def stub_successful_flow(os:) - stub_git_init(status: true, commits: true) - stub_os(os: os) - stub_heroku_downloaded(status: true) - stub_heroku_download_exists(status: true) - stub_tar(status: true) - stub_heroku_installed(status: true) - stub_heroku_whoami(status: true) - stub_heroku_login(status: true) - stub_heroku_select_app(status: true) - stub_git_remote(status: true, remote: 'heroku') - stub_git_remote(status: true, remote: 'origin') - stub_git_branches(multiple: false) - stub_heroku_deploy(status: true) - end - - def stub_git_branches(multiple:) - output = "master\n" - output << "other_branch\n" if multiple - - @context.stubs(:capture2e) - .with('git', 'branch', '--list', '--format=%(refname:short)') - .returns([output, @status_mock[:true]]) - end - - def stub_git_init(status:, commits:) - output = if commits == true - <<~EOS - On branch master - Your branch is up to date with 'heroku/master'. - - nothing to commit, working tree clean - EOS - else - <<~EOS - On branch master - - No commits yet - EOS - end - - @context.stubs(:capture2e) - .with('git', 'status') - .returns([output, @status_mock[:"#{status}"]]) - end - - def stub_git_remote(status:, remote:) - output = if status == true - @heroku_remote - else - "fatal: No such remote '#{remote}'" - end - - @context.stubs(:capture2e) - .with('git', 'remote', 'get-url', remote) - .returns([output, @status_mock[:"#{status}"]]) - end - - def stub_heroku_deploy(status:) - @context.stubs(:system) - .with('git', 'push', '-u', 'heroku', "master:master") - .returns(@status_mock[:"#{status}"]) - end - - def stub_heroku_download_exists(status:) - File.stubs(:exist?) - .with(@download_path) - .returns(status) - end - - def stub_heroku_downloaded(status:) - @context.stubs(:system) - .with('curl', '-o', @download_path, - Deploy::Heroku::DOWNLOAD_URLS[:mac], - chdir: ShopifyCli::ROOT) - .returns(@status_mock[:"#{status}"]) - end - - def stub_heroku_installed(status:) - File.stubs(:exist?) - .with(@heroku_command) - .returns(status) - - @context.stubs(:capture2e) - .with(@heroku_command, '--version') - .returns(['', @status_mock[:"#{status}"]]) - - @context.stubs(:capture2e) - .with('heroku', '--version') - .returns(['', @status_mock[:"#{status}"]]) - end - - def stub_heroku_login(status:) - @context.stubs(:system) - .with(@heroku_command, 'login') - .returns(@status_mock[:"#{status}"]) - - @context.stubs(:system) - .with('heroku', 'login') - .returns(@status_mock[:"#{status}"]) - end - - def stub_heroku_select_app(status:) - @context.stubs(:capture2e) - .with(@heroku_command, 'git:remote', '-a', 'app-name') - .returns(['', @status_mock[:"#{status}"]]) - - @context.stubs(:capture2e) - .with(@heroku_command, 'git:remote', '-a', 'app-name') - .returns(['', @status_mock[:"#{status}"]]) - end - - def stub_heroku_whoami(status:) - output = status ? 'username' : nil - - @context.stubs(:capture2e) - .with(@heroku_command, 'whoami') - .returns([output, @status_mock[:"#{status}"]]) - - @context.stubs(:capture2e) - .with('heroku', 'whoami') - .returns([output, @status_mock[:"#{status}"]]) - end - - def stub_tar(status:) - @context.stubs(:system) - .with('tar', '-xf', @download_path, chdir: ShopifyCli::ROOT) - .returns(@status_mock[:"#{status}"]) - - FileUtils.stubs(:rm) - .with(@download_path) - .returns(status) - end - - def stub_os(os:) - ShopifyCli::Context.any_instance.stubs(:os).returns(os) - end - end - end - end -end From 0c44592e59a9ac2eabadb56dde6bdfa196b60806 Mon Sep 17 00:00:00 2001 From: Kevin O'Sullivan Date: Mon, 13 Apr 2020 18:09:52 -0400 Subject: [PATCH 7/7] Missed another file deletion during rebase --- lib/shopify-cli/commands/deploy/heroku.rb | 185 ---------------------- 1 file changed, 185 deletions(-) delete mode 100644 lib/shopify-cli/commands/deploy/heroku.rb diff --git a/lib/shopify-cli/commands/deploy/heroku.rb b/lib/shopify-cli/commands/deploy/heroku.rb deleted file mode 100644 index 920ff5235d..0000000000 --- a/lib/shopify-cli/commands/deploy/heroku.rb +++ /dev/null @@ -1,185 +0,0 @@ -require 'shopify_cli' - -module ShopifyCli - module Commands - class Deploy - class Heroku < ShopifyCli::Task - - DOWNLOAD_URLS = { - linux: 'https://cli-assets.heroku.com/heroku-linux-x64.tar.gz', - mac: 'https://cli-assets.heroku.com/heroku-darwin-x64.tar.gz', - windows: 'https://cli-assets.heroku.com/heroku-win32-x64.tar.gz', - } - - def self.help - <<~HELP - Deploy the current app project to Heroku - Usage: {{command:#{ShopifyCli::TOOL_NAME} deploy heroku}} - HELP - end - - def call(ctx, _name = nil) - @ctx = ctx - - spin_group = CLI::UI::SpinGroup.new - git_service = ShopifyCli::Git.new(@ctx) - - spin_group.add('Downloading Heroku CLI…') do |spinner| - heroku_download - spinner.update_title('Downloaded Heroku CLI') - end - spin_group.wait - - spin_group.add('Installing Heroku CLI…') do |spinner| - heroku_install - spinner.update_title('Installed Heroku CLI') - end - spin_group.add('Checking git repo…') do |spinner| - git_service.init - spinner.update_title('Git repo initialized') - end - spin_group.wait - - if (account = heroku_whoami) - spin_group.add("Authenticated with Heroku as `#{account}`") { true } - spin_group.wait - else - CLI::UI::Frame.open("Authenticating with Heroku…", success_text: '{{v}} Authenticated with Heroku') do - heroku_authenticate - end - end - - if (app_name = heroku_app) - spin_group.add("Heroku app `#{app_name}` selected") { true } - spin_group.wait - else - app_type = CLI::UI::Prompt.ask('No existing Heroku app found. What would you like to do?') do |handler| - handler.option('Create a new Heroku app') { :new } - handler.option('Specify an existing Heroku app') { :existing } - end - - if app_type == :existing - app_name = CLI::UI::Prompt.ask('What is your Heroku app’s name?') - CLI::UI::Frame.open( - "Selecting Heroku app `#{app_name}`…", - success_text: "{{v}} Heroku app `#{app_name}` selected" - ) do - heroku_select_existing_app(app_name) - end - elsif app_type == :new - CLI::UI::Frame.open('Creating new Heroku app…', success_text: '{{v}} New Heroku app created') do - heroku_create_new_app - end - end - end - - branches = git_service.branches - if branches.length == 1 - branch_to_deploy = branches[0] - spin_group.add("Git branch `#{branch_to_deploy}` selected for deploy") { true } - spin_group.wait - else - branch_to_deploy = CLI::UI::Prompt.ask('What branch would you like to deploy?') do |handler| - branches.each do |branch| - handler.option(branch) { branch } - end - end - end - - CLI::UI::Frame.open('Deploying to Heroku…', success_text: '{{v}} Deployed to Heroku') do - heroku_deploy(branch_to_deploy) - end - end - - private - - def heroku_app - return nil if heroku_git_remote.nil? - app = heroku_git_remote - app = app.split('/').last - app = app.split('.').first - app - end - - def heroku_authenticate - result = @ctx.system(heroku_command, 'login') - @ctx.error("Could not authenticate with Heroku") unless result.success? - end - - def heroku_command - local_path = File.join(ShopifyCli::ROOT, 'heroku', 'bin', 'heroku').to_s - if File.exist?(local_path) - local_path - else - 'heroku' - end - end - - def heroku_create_new_app - output, status = @ctx.capture2e(heroku_command, 'create') - @ctx.error('Heroku app could not be created') unless status.success? - @ctx.puts(output) - - new_remote = output.split("\n").last.split("|").last.strip - result = @ctx.system('git', 'remote', 'add', 'heroku', new_remote) - msg = "Heroku app created, but couldn’t be set as a git remote" - @ctx.error(msg) unless result.success? - end - - def heroku_deploy(branch_to_deploy) - result = @ctx.system('git', 'push', '-u', 'heroku', "#{branch_to_deploy}:master") - @ctx.error("Could not deploy to Heroku") unless result.success? - end - - def heroku_download - return if heroku_installed? - - result = @ctx.system('curl', '-o', heroku_download_path, DOWNLOAD_URLS[@ctx.os], chdir: ShopifyCli::ROOT) - @ctx.error("Heroku CLI could not be downloaded") unless result.success? - @ctx.error("Heroku CLI could not be downloaded") unless File.exist?(heroku_download_path) - end - - def heroku_download_filename - URI.parse(DOWNLOAD_URLS[@ctx.os]).path.split('/').last - end - - def heroku_download_path - File.join(ShopifyCli::ROOT, heroku_download_filename) - end - - def heroku_git_remote - output, status = @ctx.capture2e('git', 'remote', 'get-url', 'heroku') - status.success? ? output : nil - end - - def heroku_install - return if heroku_installed? - - result = @ctx.system('tar', '-xf', heroku_download_path, chdir: ShopifyCli::ROOT) - @ctx.error("Could not install Heroku CLI") unless result.success? - - @ctx.rm(heroku_download_path) - end - - def heroku_installed? - _output, status = @ctx.capture2e(heroku_command, '--version') - status.success? - rescue - false - end - - def heroku_select_existing_app(app_name) - result = @ctx.system(heroku_command, 'git:remote', '-a', app_name) - msg = "Heroku app `#{app_name}` could not be selected" - @ctx.error(msg) unless result.success? - end - - def heroku_whoami - output, status = @ctx.capture2e(heroku_command, 'whoami') - return output.strip if status.success? - nil - end - end - end - end -end