diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 4ebf06fc..8f8f4b75 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -66,6 +66,51 @@ React Native sample apps can be run against local in-repo SDK sources with `dev rn ios --local` or `dev rn android --local`. The Web sample accepts a checkout URL directly and does not use the shared storefront credential files. +### Testing PR builds with Tophat + +[Tophat](https://github.com/Shopify/tophat) is a macOS menu-bar app that +installs testable builds onto a simulator, emulator, or connected device that +your Mac controls. Checkout Kit's E2E pipeline (Bitrise) produces the +installable artifacts, and Tophat downloads them. Because Tophat installs onto +the device your Mac controls, install links must be opened on that Mac — +scanning a QR code with a phone does not work with Tophat. + +**First-time setup.** `dev up` installs Tophat and seeds Quick Launch entries. +Tophat needs a Bitrise Personal Access Token to download build artifacts: + +1. Create a PAT at https://app.bitrise.io/me/account/security +2. Add it in Tophat -> Settings -> Extensions -> Bitrise + +There are three ways to install a build: + +1. **Quick Launch (latest `main`)** — `dev up` seeds a `Checkout Kit (React + Native)` entry (from `scripts/tophat/targets.json`) that installs the latest + successful `main` build. Select a device in Tophat's menu, then pick the + entry. +2. **Per-PR comment** — each PR gets a sticky comment with an `Install with + Tophat` link per SDK target for that PR's branch. Open Tophat, select your + target device, then click the link on the Mac running Tophat. +3. **`dev tophat` command** — installs a specific PR's build directly to a + device, targeting the device explicitly so you do not need to pre-select one + in Tophat: + + ```bash + dev tophat # pick a PR, then what to test, then a device + dev tophat 382 # PR 382 + dev tophat 382 react-native-ios # skip the "what to test" prompt + dev tophat https://github.com/Shopify/checkout-kit/pull/382 + ``` + + It resolves the PR's branch, asks what to test (e.g. React Native iOS / + Android), reuses a running device that matches or lets you pick one with + `fzf`, then installs the matching artifact. Set `TOPHAT_DRY_RUN=1` to print + the generated install config without installing. + +**Adding a new SDK target.** Add an entry to `scripts/tophat/targets.json` with +an `id`, `label`, and `recipes` (each a `platform`, `destination`, Bitrise +`workflow`, and `artifact_name`). It automatically flows into the Quick Launch +entries, the per-PR comment table, and `dev tophat`. + Sample app storefront configuration is generated from the repo-root `.env`. Shopify employees get this through `dev up`. External contributors can copy `.env.example` to `.env`, fill in local storefront values, then run diff --git a/dev.yml b/dev.yml index 4eb921f7..6e2621f6 100644 --- a/dev.yml +++ b/dev.yml @@ -6,6 +6,7 @@ up: - mint - xcbeautify - jq + - fzf - swiftlint - swiftformat - sccache @@ -32,6 +33,10 @@ up: name: Run Checkout Kit workspace setup met?: ./scripts/setup_dev_workspace --check --skip-optional-prompts meet: ./scripts/setup_dev_workspace --skip-optional-prompts + - tophat_mobile + - run_always: + name: Configure Tophat Quick Launch items + run: ruby scripts/tophat/configure_quick_launch open: "GitHub": "https://github.com/Shopify/checkout-kit" @@ -74,6 +79,11 @@ commands: desc: Copy the root .env into the current worktree so `dev up` can regenerate sample config run: ./scripts/copy_worktree_env + tophat: + desc: "Install a PR's build to a device via Tophat. Usage: dev tophat [] []" + syntax: "[] []" + run: ruby scripts/tophat/dev_tophat "$@" + format: desc: Auto-format and apply safe lint autocorrections across supported workspaces aliases: [fix] diff --git a/e2e/BITRISE.md b/e2e/BITRISE.md index 38184b55..8109bc69 100644 --- a/e2e/BITRISE.md +++ b/e2e/BITRISE.md @@ -46,7 +46,7 @@ bitrise validate -c e2e/bitrise.yml ## Duplicate PR build cancellation -Use Bitrise native Rolling builds instead of a repo-owned cancellation script. In Bitrise, open **Project settings > Builds > Build strategy**, enable **Abort builds triggered by pull requests**, and enable **Abort running builds** so duplicate in-progress PR pipelines are cancelled when a newer build starts. +Duplicate in-progress PR pipelines are cancelled by Bitrise native Rolling builds rather than a repo-owned cancellation script. Under **Project settings > Builds > Build strategy**, **Abort builds triggered by pull requests** and **Abort running builds** are enabled, so a newer PR build cancels the older one. ## Required app environment variables @@ -113,7 +113,7 @@ The `e2e-report` workflow creates commit statuses, Check Runs, and sticky PR com The Bitrise project has **Project settings > Repository > Extend GitHub App permissions to builds** enabled. Bitrise exposes the build-scoped GitHub App token as `GIT_HTTP_PASSWORD`; the report workflow maps it to `GITHUB_TOKEN` before running `e2e/scripts/report_e2e_results`. -Green runs update statuses and Check Runs without creating new PR comments. Failing runs update a sticky PR failure comment with direct BrowserStack evidence links. +Every run maintains a single sticky PR comment (create-or-update via a marker). The comment always includes an "Install with Tophat" link per SDK target and the E2E results table; failing runs add direct BrowserStack evidence links. The install links and Quick Launch entries are driven by `scripts/tophat/targets.json`; see the Tophat section in `.github/CONTRIBUTING.md`. ## Caching diff --git a/e2e/RUNBOOK.md b/e2e/RUNBOOK.md index 03c247f8..913ce1e3 100644 --- a/e2e/RUNBOOK.md +++ b/e2e/RUNBOOK.md @@ -11,9 +11,11 @@ requiring it never churns as applications, OS versions, or suites are added. The runner never hard-fails on test or infrastructure problems: every run writes a `result.json` and exits `0`, so the report workflow always has data to publish. The -report posts one **"Checkout Kit E2E"** Check Run and a sticky PR failure comment; it -does not post per-suite commit statuses. Failures are therefore loud (one red check -and a failure comment) but non-blocking. +report posts one **"Checkout Kit E2E"** Check Run and one sticky PR comment carrying the +Tophat install links and the run summary; it does not post per-suite commit statuses. The +comment is posted on every build, green or red, so the build is always installable from the +PR, and failures add a loud Failures section (alongside one red check) while staying +non-blocking. Failures land in `result.json` in one of two shapes: @@ -27,7 +29,7 @@ Failures land in `result.json` in one of two shapes: message, and full backtrace logged to the failing step's stderr. Setup failures that happen before a run plan row is read have no run metadata -(suite, target), so they appear in the Check Run summary and the failure comment +(suite, target), so they appear in the Check Run summary and the PR comment with an `error` status but reduced detail. This is separate from the Bitrise step's own required-env guards: a missing config variable (e.g. `BROWSERSTACK_ACCESS_KEY`) is validated by the step and fails fast before @@ -37,7 +39,7 @@ The report also enforces a **completeness check**: it compares the number of `result.json` files against `E2E_BROWSERSTACK_RUN_PLAN_COUNT` (the run plan row count, shared across the pipeline). If a run never reports — for example a whole execute workflow that failed to upload — the "Checkout Kit E2E" check is forced red -and the failure comment notes the shortfall, so a missing run can never silently +and the PR comment notes the shortfall, so a missing run can never silently pass. When the expected count is unavailable the completeness check is skipped rather than reporting a false failure. @@ -82,7 +84,7 @@ The app artifact environment variable for the run plan row must point at the `.a ## Failure triage -Use the GitHub Check Run or sticky PR failure comment first. Failure summaries should include Markdown links to: +Use the GitHub Check Run or sticky PR comment first. Failure summaries should include Markdown links to: - BrowserStack build - failed testcase @@ -95,4 +97,7 @@ Use the GitHub Check Run or sticky PR failure comment first. Failure summaries s BrowserStack artifact links require BrowserStack App Automate access. Sign in to [BrowserStack App Automate](https://app-automate.browserstack.com/dashboard/v2/builds) before opening evidence links. -Avoid posting additional PR comments for green runs. +The report keeps a single sticky PR comment, identified by a hidden marker, and updates it +in place on every build, so green runs never add a second comment. Because that comment +always carries the Tophat install links, it is posted even on a fully green run — a passing +build stays installable from the PR. diff --git a/e2e/lib/e2e_github_reporter.rb b/e2e/lib/e2e_github_reporter.rb index c3ccd609..94e8569c 100644 --- a/e2e/lib/e2e_github_reporter.rb +++ b/e2e/lib/e2e_github_reporter.rb @@ -1,25 +1,48 @@ # frozen_string_literal: true require "json" +require "uri" require_relative "json_http_client" -# Publishes normalized E2E run results back to GitHub as commit statuses, -# check runs, and a sticky pull request comment for failures. +# Publishes normalized E2E run results back to GitHub as a check run and a +# sticky pull request comment carrying the Tophat install link and run summary. class E2EGitHubReporter COMMENT_MARKER = "" + TOPHAT_INSTALL_BASE = "http://localhost:29070/install/bitrise-branch" - def initialize(results, repository:, sha:, pr_number:, token: nil, expected: nil) + def initialize(results, repository:, sha:, pr_number:, branch: nil, token: nil, app_slug: nil, targets: [], expected: nil) @results = results @repository = repository @sha = sha @pr_number = pr_number + @branch = branch @token = token + @app_slug = app_slug + @targets = targets @expected = expected end def publish! client.post_json("/repos/#{@repository}/check-runs", check_run_payload) - sync_failure_comment + sync_comment + end + + def tophat_install_url(target) + pairs = target.fetch("recipes").flat_map do |recipe| + [ + ["platform", recipe.fetch("platform")], + ["destination", recipe.fetch("destination")], + ["app_slug", @app_slug], + ["branch", @branch], + ["workflow", recipe.fetch("workflow")], + ["artifact_name", recipe.fetch("artifact_name")] + ] + end + "#{TOPHAT_INSTALL_BASE}?#{URI.encode_www_form(pairs)}" + end + + def comment_body + [COMMENT_MARKER, tophat_install_markdown, markdown_summary].join("\n\n") end def markdown_summary @@ -51,14 +74,22 @@ def markdown_summary lines.join("\n") end - def failure_comment_body - return nil if failed_results.empty? && complete? + private - [COMMENT_MARKER, markdown_summary].join("\n") + def tophat_install_markdown + lines = [] + lines << "## Install this build" + lines << "" + lines << "Open Tophat, select your target device, then click Install. Links open on the Mac running Tophat." + lines << "" + lines << "| SDK | Install |" + lines << "|---|---|" + @targets.each do |target| + lines << "| #{target.fetch("label")} | [Install with Tophat](#{tophat_install_url(target)}) |" + end + lines.join("\n") end - private - def check_run_payload conclusion = failed_results.empty? && complete? ? "success" : "failure" { @@ -83,21 +114,17 @@ def missing_count [@expected - @results.length, 0].max end - def sync_failure_comment - body = failure_comment_body - existing = existing_failure_comment - if body - if existing - client.patch_json("/repos/#{@repository}/issues/comments/#{existing.fetch("id")}", {body: body}) - else - client.post_json("/repos/#{@repository}/issues/#{@pr_number}/comments", {body: body}) - end - elsif existing - client.patch_json("/repos/#{@repository}/issues/comments/#{existing.fetch("id")}", {body: "#{COMMENT_MARKER}\n✅ Checkout Kit E2E failures resolved."}) + def sync_comment + body = comment_body + existing = existing_comment + if existing + client.patch_json("/repos/#{@repository}/issues/comments/#{existing.fetch("id")}", {body: body}) + else + client.post_json("/repos/#{@repository}/issues/#{@pr_number}/comments", {body: body}) end end - def existing_failure_comment + def existing_comment issue_comments.find do |comment| comment.fetch("body", "").include?(COMMENT_MARKER) end diff --git a/e2e/scripts/build_react_native_android b/e2e/scripts/build_react_native_android index 871e9f38..f3a4bedc 100755 --- a/e2e/scripts/build_react_native_android +++ b/e2e/scripts/build_react_native_android @@ -19,5 +19,9 @@ cd sample/android android_apk="$PWD/app/build/outputs/apk/e2e/app-e2e.apk" test -f "$android_apk" +e2e_dir="$(e2e_deploy_dir)" +cp "$android_apk" "$e2e_dir/app-e2e.apk" +android_apk="$e2e_dir/app-e2e.apk" + e2e_log "Publishing Android APK path" envman add --key E2E_REACT_NATIVE_ANDROID_APP_PATH --value "$android_apk" diff --git a/e2e/scripts/report_e2e_results b/e2e/scripts/report_e2e_results index d7260129..dc6a5a7e 100755 --- a/e2e/scripts/report_e2e_results +++ b/e2e/scripts/report_e2e_results @@ -11,6 +11,7 @@ options = { sha: ENV["BITRISE_GIT_COMMIT"], pr_number: ENV["BITRISE_PULL_REQUEST"], token: ENV["GITHUB_TOKEN"], + branch: ENV["BITRISE_GIT_BRANCH"], expected: ENV["E2E_BROWSERSTACK_RUN_PLAN_COUNT"] } @@ -19,6 +20,7 @@ OptionParser.new do |opts| opts.on("--repository REPOSITORY") { |repository| options[:repository] = repository } opts.on("--sha SHA") { |sha| options[:sha] = sha } opts.on("--pr NUMBER") { |number| options[:pr_number] = number } + opts.on("--branch BRANCH") { |branch| options[:branch] = branch } opts.on("--expected COUNT", Integer) { |count| options[:expected] = count } end.parse! @@ -47,12 +49,16 @@ if result_paths.empty? end results = result_paths.sort.map { |path| JSON.parse(File.read(path)) } +tophat_manifest = JSON.parse(File.read(File.expand_path("../../scripts/tophat/targets.json", __dir__))) reporter = E2EGitHubReporter.new( results, repository: options.fetch(:repository), sha: options.fetch(:sha), pr_number: options.fetch(:pr_number), token: options.fetch(:token), + branch: options.fetch(:branch), + app_slug: tophat_manifest.fetch("app_slug"), + targets: tophat_manifest.fetch("targets"), expected: expected ) reporter.publish! diff --git a/scripts/configure_tophat_quick_launch.sh b/scripts/configure_tophat_quick_launch.sh new file mode 100755 index 00000000..4457d7e0 --- /dev/null +++ b/scripts/configure_tophat_quick_launch.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +ruby "$SCRIPT_DIR/tophat/configure_quick_launch" "$@" diff --git a/scripts/tophat/common.rb b/scripts/tophat/common.rb new file mode 100644 index 00000000..9b269871 --- /dev/null +++ b/scripts/tophat/common.rb @@ -0,0 +1,97 @@ +# frozen_string_literal: true + +require "json" +require "open3" + +module CheckoutKitTophat + TOPHATCTL = "/Applications/Tophat.app/Contents/MacOS/tophatctl" + MANIFEST = File.expand_path("targets.json", __dir__) + BITRISE_PAT_URL = "https://app.bitrise.io/me/account/security" + PLATFORM_LABELS = {"ios" => "iOS", "android" => "Android"}.freeze + module_function + + def abort_with(message) + warn message + exit 1 + end + + def capture(*command) + stdout, status = Open3.capture2(*command) + abort_with("Command failed: #{command.join(" ")}") unless status.success? + stdout + end + + def load_manifest + JSON.parse(File.read(MANIFEST)) + end + + def require_tophat! + abort_with("Tophat is not installed at #{TOPHATCTL}. Run 'dev up' to install it.") unless File.executable?(TOPHATCTL) + end + + def install_options(manifest) + manifest.fetch("targets").flat_map do |target| + platforms = target.fetch("recipes").map { |recipe| recipe.fetch("platform") }.uniq + platforms.map do |platform| + { + "id" => "#{target.fetch("id")}-#{platform}", + "label" => "#{target.fetch("label")} (#{PLATFORM_LABELS.fetch(platform, platform)})", + "target" => target, + "platform" => platform + } + end + end + end + + def available_target_ids(manifest) + install_options(manifest).map { |option| option.fetch("id") } + end + + def artifact_provider_parameters(recipe, app_slug, branch) + { + "app_slug" => app_slug, + "branch" => branch, + "workflow" => recipe.fetch("workflow"), + "artifact_name" => recipe.fetch("artifact_name") + } + end + + def bitrise_branch_recipe(recipe, app_slug, branch) + { + "artifactProviderID" => "bitrise-branch", + "launchArguments" => [], + "artifactProviderParameters" => artifact_provider_parameters(recipe, app_slug, branch) + } + end + + def install_config(recipe, device, app_slug, branch) + [ + bitrise_branch_recipe(recipe, app_slug, branch).merge( + "device" => { + "name" => device.fetch("name"), + "platform" => device.fetch("platform"), + "runtimeVersion" => device.fetch("runtimeVersion") + } + ) + ] + end + + def quick_launch_entry(target, app_slug, branch) + { + "id" => "checkout-kit-#{target.fetch("id")}", + "name" => "Checkout Kit (#{target.fetch("label")})", + "recipes" => target.fetch("recipes").map do |recipe| + quick_launch_recipe(recipe, app_slug, branch) + end + } + end + + def quick_launch_recipe(recipe, app_slug, branch) + config = bitrise_branch_recipe(recipe, app_slug, branch).merge( + "platformHint" => recipe.fetch("platform") + ) + destination = recipe.fetch("destination") + config["destinationHint"] = destination unless destination == "any" + config + end +end diff --git a/scripts/tophat/configure_quick_launch b/scripts/tophat/configure_quick_launch new file mode 100755 index 00000000..a8e9561e --- /dev/null +++ b/scripts/tophat/configure_quick_launch @@ -0,0 +1,29 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "tempfile" +require_relative "common" + +def add_quick_launch_entry(entry) + Tempfile.create(["tophat-quick-launch", ".json"]) do |file| + file.write(JSON.generate(entry)) + file.flush + system(CheckoutKitTophat::TOPHATCTL, "apps", "add", file.path) || CheckoutKitTophat.abort_with("Failed to configure Tophat Quick Launch entry #{entry.fetch("id")}") + end +end + +CheckoutKitTophat.require_tophat! +manifest = CheckoutKitTophat.load_manifest + +manifest.fetch("targets").each do |target| + entry = CheckoutKitTophat.quick_launch_entry(target, manifest.fetch("app_slug"), manifest.fetch("default_branch")) + add_quick_launch_entry(entry) +end + +puts <<~REMINDER + + ==> Tophat Quick Launch items configured for Checkout Kit. + Installs need a Bitrise Personal Access Token: + 1. Create a PAT at #{CheckoutKitTophat::BITRISE_PAT_URL} + 2. Add it to Tophat -> Settings -> Extensions -> Bitrise +REMINDER diff --git a/scripts/tophat/dev_tophat b/scripts/tophat/dev_tophat new file mode 100755 index 00000000..f569c20a --- /dev/null +++ b/scripts/tophat/dev_tophat @@ -0,0 +1,201 @@ +#!/usr/bin/env ruby +# frozen_string_literal: true + +require "tempfile" +require_relative "common" + +def abort_with(message) + CheckoutKitTophat.abort_with(message) +end + +def capture(*command) + CheckoutKitTophat.capture(*command) +end + +def fzf_select(lines, prompt:, preview: nil) + args = ["fzf", "--prompt", "#{prompt} > ", "--height", "40%", "--reverse", "--delimiter", "\t", "--with-nth", "2.."] + args += ["--preview", preview, "--preview-window", "down,40%"] if preview + selection, status = Open3.capture2(*args, stdin_data: lines.join("\n")) + abort_with("Nothing selected.") unless status.success? && !selection.strip.empty? + selection.strip +end + +def resolve_pr_number(argument) + if argument + return Integer(argument[%r{/pull/(\d+)}, 1]) if argument.include?("/pull/") + abort_with("Invalid pull request '#{argument}'. Pass a PR number or pull request URL.") unless argument.match?(/\A\d+\z/) + return Integer(argument) + end + + prs = JSON.parse(capture("gh", "pr", "list", "--json", "number,title,author,headRefName", "--limit", "50")) + abort_with("No open pull requests found.") if prs.empty? + + lines = prs.map do |pr| + number = pr.fetch("number") + display = "##{number} #{pr.fetch("title")} @#{pr.dig("author", "login")} #{pr.fetch("headRefName")}" + "#{number}\t#{display}" + end + selection = fzf_select(lines, prompt: "Pull request", preview: "gh pr view {1}") + Integer(selection.split("\t", 2).first) +end + +def branch_for(pr_number) + capture("gh", "pr", "view", pr_number.to_s, "--json", "headRefName", "-q", ".headRefName").strip +end + +def select_option(options, requested_id) + if requested_id + option = options.find { |candidate| candidate.fetch("id") == requested_id } + abort_with("Unknown target '#{requested_id}'. Available: #{options.map { |o| o.fetch("id") }.join(", ")}") unless option + return option + end + return options.first if options.length == 1 + + lines = options.map { |option| "#{option.fetch("id")}\t#{option.fetch("label")}" } + selection = fzf_select(lines, prompt: "What to test") + selected_id = selection.split("\t", 2).first + options.find { |option| option.fetch("id") == selected_id } +end + +def select_device(platform) + devices = JSON.parse(capture(CheckoutKitTophat::TOPHATCTL, "list", "devices", "--json")) + matching = devices.select { |device| device.fetch("platform") == platform } + abort_with("No #{CheckoutKitTophat::PLATFORM_LABELS.fetch(platform, platform)} devices are available to Tophat.") if matching.empty? + + ready = matching.select { |device| device.fetch("state") == "ready" } + return ready.first if ready.length == 1 + + candidates = ready.empty? ? matching : ready + lines = candidates.each_with_index.map do |device, index| + "#{index}\t#{device.fetch("name")} #{device.fetch("runtimeVersion")} (#{device.fetch("type")}, #{device.fetch("state")})" + end + selection = fzf_select(lines, prompt: "Device") + candidates.fetch(Integer(selection.split("\t", 2).first)) +end + +def recipe_for(target, device) + recipe = target.fetch("recipes").find do |candidate| + candidate.fetch("platform") == device.fetch("platform") && + (candidate.fetch("destination") == device.fetch("type") || candidate.fetch("destination") == "any") + end + abort_with("No #{device.fetch("type")} recipe for #{target.fetch("label")} on #{device.fetch("platform")}.") unless recipe + recipe +end + +def bitrise_auth_error?(output) + normalized = output.downcase + normalized.include?("bitrise personal access token") || + normalized.include?("personal access token is required") || + normalized.include?("access token used to authenticate with bitrise is invalid") || + normalized.include?("authenticate with bitrise") || + (normalized.include?("unauthorized") && normalized.include?("bitrise")) || + normalized.match?(/\b(401|403)\b/) +end + +def prompt_for_bitrise_token_setup + puts <<~MESSAGE + + Tophat could not authenticate with Bitrise. + + Tophat needs a Bitrise Personal Access Token to download Checkout Kit build artifacts. + Create a token, then add it in Tophat -> Settings -> Extensions -> Bitrise. + + Press Enter to open the Bitrise token page, or Ctrl-C to cancel. + MESSAGE + STDIN.gets || abort_with("Cancelled.") + system("open", CheckoutKitTophat::BITRISE_PAT_URL) + puts "Add the token in Tophat -> Settings -> Extensions -> Bitrise, then press Enter to retry." + STDIN.gets || abort_with("Cancelled.") +end + +def run_tophat_install(config_path) + output = +"" + status = nil + + Open3.popen2e(CheckoutKitTophat::TOPHATCTL, "install", config_path) do |stdin, stdout_and_stderr, wait_thread| + stdin.close + stdout_and_stderr.each do |line| + output << line + print line + end + status = wait_thread.value + end + + [output, status] +end + +def install_with_tophat(config_path) + output, status = run_tophat_install(config_path) + return true if status.success? + + if bitrise_auth_error?(output) + prompt_for_bitrise_token_setup + _retry_output, retry_status = run_tophat_install(config_path) + return true if retry_status.success? + end + + false +end + +def help_requested? + ARGV.include?("--help") || ARGV.include?("-h") +end + +def print_help(manifest) + targets = CheckoutKitTophat.available_target_ids(manifest) + puts <<~HELP + Usage: dev tophat [] [] + + Install a pull request build to a device via Tophat. + + Arguments: + Optional pull request number or GitHub pull request URL. + If omitted, choose from open pull requests with fzf. + Optional target ID. If omitted, choose with fzf. + + Options: + -h, --help Show this help. + + Available targets: + #{targets.join("\n ")} + + Examples: + dev tophat + dev tophat 382 + dev tophat 382 react-native-ios + dev tophat https://github.com/Shopify/checkout-kit/pull/382 + + Environment: + TOPHAT_DRY_RUN=1 Print the generated Tophat install config without installing. + HELP +end + +manifest = CheckoutKitTophat.load_manifest +if help_requested? + print_help(manifest) + exit 0 +end + +CheckoutKitTophat.require_tophat! + +pr_number = resolve_pr_number(ARGV[0]) +branch = branch_for(pr_number) +option = select_option(CheckoutKitTophat.install_options(manifest), ARGV[1]) +device = select_device(option.fetch("platform")) +recipe = recipe_for(option.fetch("target"), device) +config = CheckoutKitTophat.install_config(recipe, device, manifest.fetch("app_slug"), branch) + +puts "==> Installing #{option.fetch("label")} from PR ##{pr_number} (#{branch})" +puts " Device: #{device.fetch("name")} #{device.fetch("runtimeVersion")} (#{device.fetch("type")})" +puts " Artifact: #{recipe.fetch("artifact_name")}" + +if ENV["TOPHAT_DRY_RUN"] == "1" + puts JSON.pretty_generate(config) + exit 0 +end + +Tempfile.create(["tophat-install", ".json"]) do |file| + file.write(JSON.generate(config)) + file.flush + install_with_tophat(file.path) || abort_with("Tophat install failed.") +end diff --git a/scripts/tophat/targets.json b/scripts/tophat/targets.json new file mode 100644 index 00000000..b63bc770 --- /dev/null +++ b/scripts/tophat/targets.json @@ -0,0 +1,30 @@ +{ + "app_slug": "f51f9054-053e-40f1-81e9-ae727567ae76", + "default_branch": "main", + "targets": [ + { + "id": "react-native", + "label": "React Native", + "recipes": [ + { + "platform": "ios", + "destination": "device", + "workflow": "e2e-build-react-native-ios", + "artifact_name": "CheckoutKitReactNativeDemo-Provisioned.ipa" + }, + { + "platform": "ios", + "destination": "simulator", + "workflow": "e2e-build-react-native-ios", + "artifact_name": "CheckoutKitReactNativeDemo-Simulator.zip" + }, + { + "platform": "android", + "destination": "any", + "workflow": "e2e-build-react-native-android", + "artifact_name": "app-e2e.apk" + } + ] + } + ] +}