From 41648cfdc9b1a37c07887080d8e6a73445e62e08 Mon Sep 17 00:00:00 2001 From: Herve Labas Date: Thu, 2 Jul 2026 17:04:20 +0200 Subject: [PATCH 1/5] Add GitHub report metadata --- README.md | 7 +++++ scripts/run.sh | 76 +++++++++++++++++++++++++++++++++++++++++++++++++ scripts/test.sh | 34 ++++++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/README.md b/README.md index 26c427c..89c660e 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,13 @@ The action always starts detached runs. The Checkly CLI prints a test session ID and session URL; this action exposes them as outputs and writes them to the GitHub Actions step summary. +When `github-report` is enabled, the action also passes GitHub Actions metadata +to the Checkly CLI. If the Checkly GitHub App is connected to the repository, +Checkly can use that metadata to post a GitHub Check that links back to the test +session and updates when the session finishes. If the app is not connected, the +run still succeeds or fails normally and the session link in the workflow output +remains the fallback. + ## Inputs | Input | Description | diff --git a/scripts/run.sh b/scripts/run.sh index 2330155..c42c57f 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -94,6 +94,70 @@ append_summary() { fi } +clear_github_report_env() { + unset CHECKLY_GITHUB_REPORT + unset CHECKLY_GITHUB_REPOSITORY + unset CHECKLY_GITHUB_SHA + unset CHECKLY_GITHUB_RUN_ID + unset CHECKLY_GITHUB_RUN_ATTEMPT + unset CHECKLY_GITHUB_WORKFLOW + unset CHECKLY_GITHUB_JOB + unset CHECKLY_GITHUB_EVENT_NAME + unset CHECKLY_GITHUB_REF + unset CHECKLY_GITHUB_HEAD_REF + unset CHECKLY_GITHUB_BASE_REF + unset CHECKLY_GITHUB_SERVER_URL +} + +configure_generic_repo_env() { + if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then + export CHECKLY_REPO_URL="${CHECKLY_REPO_URL:-${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}}" + fi + + if [[ -n "${GITHUB_SHA:-}" ]]; then + export CHECKLY_REPO_SHA="${CHECKLY_REPO_SHA:-$GITHUB_SHA}" + fi + + local branch_name="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}" + if [[ -n "$branch_name" ]]; then + export CHECKLY_REPO_BRANCH="${CHECKLY_REPO_BRANCH:-$branch_name}" + fi +} + +configure_github_report() { + local value="${INPUT_GITHUB_REPORT:-true}" + if falsey "$value"; then + clear_github_report_env + return + fi + if ! truthy "$value"; then + echo "::error::Expected boolean input for github-report, got '${value}'." >&2 + exit 1 + fi + + export CHECKLY_GITHUB_REPORT=true + + if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then + export CHECKLY_GITHUB_REPOSITORY="$GITHUB_REPOSITORY" + fi + + if [[ -n "${GITHUB_SHA:-}" ]]; then + export CHECKLY_GITHUB_SHA="$GITHUB_SHA" + fi + + [[ -n "${GITHUB_RUN_ID:-}" ]] && export CHECKLY_GITHUB_RUN_ID="$GITHUB_RUN_ID" + [[ -n "${GITHUB_RUN_ATTEMPT:-}" ]] && export CHECKLY_GITHUB_RUN_ATTEMPT="$GITHUB_RUN_ATTEMPT" + [[ -n "${GITHUB_WORKFLOW:-}" ]] && export CHECKLY_GITHUB_WORKFLOW="$GITHUB_WORKFLOW" + [[ -n "${GITHUB_JOB:-}" ]] && export CHECKLY_GITHUB_JOB="$GITHUB_JOB" + [[ -n "${GITHUB_EVENT_NAME:-}" ]] && export CHECKLY_GITHUB_EVENT_NAME="$GITHUB_EVENT_NAME" + [[ -n "${GITHUB_REF:-}" ]] && export CHECKLY_GITHUB_REF="$GITHUB_REF" + [[ -n "${GITHUB_HEAD_REF:-}" ]] && export CHECKLY_GITHUB_HEAD_REF="$GITHUB_HEAD_REF" + [[ -n "${GITHUB_BASE_REF:-}" ]] && export CHECKLY_GITHUB_BASE_REF="$GITHUB_BASE_REF" + [[ -n "${GITHUB_SERVER_URL:-}" ]] && export CHECKLY_GITHUB_SERVER_URL="$GITHUB_SERVER_URL" + + return 0 +} + command_name="$(trim "${INPUT_COMMAND:-test}")" cli_version="$(trim "${INPUT_CLI_VERSION:-latest}")" working_directory="$(trim "${INPUT_WORKING_DIRECTORY:-.}")" @@ -161,6 +225,9 @@ else add_optional_boolean_flag "--fail-on-no-matching" "--no-fail-on-no-matching" "${INPUT_FAIL_ON_NO_MATCHING:-}" fi +configure_generic_repo_env +configure_github_report + if [[ "${CHECKLY_ACTION_DRY_RUN:-}" == "1" || "${CHECKLY_ACTION_DRY_RUN:-}" == "true" ]]; then if [[ -n "$install_command" ]]; then printf 'Install command: %s\n' "$install_command" @@ -168,6 +235,15 @@ if [[ "${CHECKLY_ACTION_DRY_RUN:-}" == "1" || "${CHECKLY_ACTION_DRY_RUN:-}" == " printf 'Command: ' printf '%q ' "${checkly_command[@]}" printf '\n' + if [[ "${CHECKLY_GITHUB_REPORT:-}" == "true" ]]; then + printf 'GitHub report: enabled' + if [[ -n "${CHECKLY_GITHUB_REPOSITORY:-}" && -n "${CHECKLY_GITHUB_SHA:-}" ]]; then + printf ' for %s@%s' "$CHECKLY_GITHUB_REPOSITORY" "$CHECKLY_GITHUB_SHA" + fi + printf '\n' + else + printf 'GitHub report: disabled\n' + fi exit 0 fi diff --git a/scripts/test.sh b/scripts/test.sh index 0ca610d..a10d88b 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -37,6 +37,7 @@ assert_contains "$test_command_output" "--tags production\\,webapp --tags produc assert_contains "$test_command_output" "--grep checkout" assert_contains "$test_command_output" "--update-snapshots" assert_contains "$test_command_output" "checks/\\*\\*/\\*.check.ts smoke.check.ts" +assert_contains "$test_command_output" "GitHub report: enabled" trigger_command_output="$( INPUT_COMMAND=trigger \ @@ -52,4 +53,37 @@ assert_contains "$trigger_command_output" "--tags production" assert_contains "$trigger_command_output" "--check-id abc\\,def" assert_contains "$trigger_command_output" "--no-fail-on-no-matching" +github_report_output="$( + INPUT_COMMAND=test \ + INPUT_GITHUB_REPORT=true \ + GITHUB_REPOSITORY=checkly/playwright-reporter-demo \ + GITHUB_SHA=abc123def456 \ + GITHUB_RUN_ID=123456 \ + GITHUB_RUN_ATTEMPT=2 \ + GITHUB_WORKFLOW=Checkly \ + GITHUB_JOB=validate \ + GITHUB_EVENT_NAME=pull_request \ + GITHUB_REF=refs/pull/4/merge \ + GITHUB_REF_NAME=4/merge \ + GITHUB_HEAD_REF=herve/test-checkly-action \ + GITHUB_BASE_REF=main \ + GITHUB_SERVER_URL=https://github.com \ + run_dry +)" + +assert_contains "$github_report_output" "GitHub report: enabled for checkly/playwright-reporter-demo@abc123def456" + +github_report_disabled_output="$( + INPUT_COMMAND=test \ + INPUT_GITHUB_REPORT=false \ + CHECKLY_GITHUB_REPORT=true \ + CHECKLY_GITHUB_REPOSITORY=spoofed/repository \ + CHECKLY_GITHUB_SHA=spoofed-sha \ + GITHUB_REPOSITORY=checkly/playwright-reporter-demo \ + GITHUB_SHA=abc123def456 \ + run_dry +)" + +assert_contains "$github_report_disabled_output" "GitHub report: disabled" + echo "All local action tests passed." From 0708fd3655140fb5c51f64669e1397c145182a65 Mon Sep 17 00:00:00 2001 From: Herve Labas Date: Thu, 2 Jul 2026 17:35:08 +0200 Subject: [PATCH 2/5] Report PR head metadata from action --- scripts/run.sh | 80 ++++++++++++++++++++++++++++++++++++++++++++----- scripts/test.sh | 20 +++++++++++-- 2 files changed, 90 insertions(+), 10 deletions(-) diff --git a/scripts/run.sh b/scripts/run.sh index c42c57f..350e475 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -94,6 +94,62 @@ append_summary() { fi } +github_event_value() { + local path="$1" + local event_path="${GITHUB_EVENT_PATH:-}" + if [[ -z "$event_path" || ! -f "$event_path" ]]; then + return 0 + fi + if ! command -v node >/dev/null 2>&1; then + return 0 + fi + + node - "$path" <<'NODE' +const fs = require('fs') + +const eventPath = process.env.GITHUB_EVENT_PATH +const path = process.argv[2].split('.') + +try { + let value = JSON.parse(fs.readFileSync(eventPath, 'utf8')) + for (const segment of path) { + if (!value || typeof value !== 'object' || !(segment in value)) { + process.exit(0) + } + value = value[segment] + } + if (typeof value === 'string' && value.trim() !== '') { + process.stdout.write(value) + } +} catch (_) { + process.exit(0) +} +NODE +} + +is_pull_request_event() { + case "${GITHUB_EVENT_NAME:-}" in + pull_request|pull_request_target) return 0 ;; + *) return 1 ;; + esac +} + +resolve_github_repository() { + local repository="" + if is_pull_request_event; then + repository="$(github_event_value "pull_request.head.repo.full_name")" + fi + printf '%s' "${repository:-${GITHUB_REPOSITORY:-}}" +} + +resolve_github_sha() { + local sha="" + if is_pull_request_event; then + sha="$(github_event_value "pull_request.head.sha")" + fi + printf '%s' "${sha:-${GITHUB_SHA:-}}" +} + clear_github_report_env() { unset CHECKLY_GITHUB_REPORT unset CHECKLY_GITHUB_REPOSITORY @@ -110,12 +166,16 @@ clear_github_report_env() { } configure_generic_repo_env() { - if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then - export CHECKLY_REPO_URL="${CHECKLY_REPO_URL:-${GITHUB_SERVER_URL:-https://github.com}/${GITHUB_REPOSITORY}}" + local repository + repository="$(resolve_github_repository)" + if [[ -n "$repository" ]]; then + export CHECKLY_REPO_URL="${CHECKLY_REPO_URL:-${GITHUB_SERVER_URL:-https://github.com}/${repository}}" fi - if [[ -n "${GITHUB_SHA:-}" ]]; then - export CHECKLY_REPO_SHA="${CHECKLY_REPO_SHA:-$GITHUB_SHA}" + local sha + sha="$(resolve_github_sha)" + if [[ -n "$sha" ]]; then + export CHECKLY_REPO_SHA="${CHECKLY_REPO_SHA:-$sha}" fi local branch_name="${GITHUB_HEAD_REF:-${GITHUB_REF_NAME:-}}" @@ -137,12 +197,16 @@ configure_github_report() { export CHECKLY_GITHUB_REPORT=true - if [[ -n "${GITHUB_REPOSITORY:-}" ]]; then - export CHECKLY_GITHUB_REPOSITORY="$GITHUB_REPOSITORY" + local repository + repository="$(resolve_github_repository)" + if [[ -n "$repository" ]]; then + export CHECKLY_GITHUB_REPOSITORY="$repository" fi - if [[ -n "${GITHUB_SHA:-}" ]]; then - export CHECKLY_GITHUB_SHA="$GITHUB_SHA" + local sha + sha="$(resolve_github_sha)" + if [[ -n "$sha" ]]; then + export CHECKLY_GITHUB_SHA="$sha" fi [[ -n "${GITHUB_RUN_ID:-}" ]] && export CHECKLY_GITHUB_RUN_ID="$GITHUB_RUN_ID" diff --git a/scripts/test.sh b/scripts/test.sh index a10d88b..0eaf3a8 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -53,16 +53,32 @@ assert_contains "$trigger_command_output" "--tags production" assert_contains "$trigger_command_output" "--check-id abc\\,def" assert_contains "$trigger_command_output" "--no-fail-on-no-matching" +github_event_path="$(mktemp)" +trap 'rm -f "$github_event_path"' EXIT +cat > "$github_event_path" <<'JSON' +{ + "pull_request": { + "head": { + "sha": "head123def456", + "repo": { + "full_name": "checkly/playwright-reporter-demo" + } + } + } +} +JSON + github_report_output="$( INPUT_COMMAND=test \ INPUT_GITHUB_REPORT=true \ GITHUB_REPOSITORY=checkly/playwright-reporter-demo \ - GITHUB_SHA=abc123def456 \ + GITHUB_SHA=merge123def456 \ GITHUB_RUN_ID=123456 \ GITHUB_RUN_ATTEMPT=2 \ GITHUB_WORKFLOW=Checkly \ GITHUB_JOB=validate \ GITHUB_EVENT_NAME=pull_request \ + GITHUB_EVENT_PATH="$github_event_path" \ GITHUB_REF=refs/pull/4/merge \ GITHUB_REF_NAME=4/merge \ GITHUB_HEAD_REF=herve/test-checkly-action \ @@ -71,7 +87,7 @@ github_report_output="$( run_dry )" -assert_contains "$github_report_output" "GitHub report: enabled for checkly/playwright-reporter-demo@abc123def456" +assert_contains "$github_report_output" "GitHub report: enabled for checkly/playwright-reporter-demo@head123def456" github_report_disabled_output="$( INPUT_COMMAND=test \ From c8da22954a7a1d9fb13e3cb67fdbc722580e9e1a Mon Sep 17 00:00:00 2001 From: Herve Labas Date: Fri, 3 Jul 2026 14:34:07 +0200 Subject: [PATCH 3/5] feat: preflight GitHub check writeback --- README.md | 30 +++++---- action.yml | 2 +- scripts/run.sh | 168 +++++++++++++++++++++++++++++++++++++++++++++--- scripts/test.sh | 40 +++++++++++- 4 files changed, 216 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 89c660e..dfd4788 100644 --- a/README.md +++ b/README.md @@ -2,9 +2,9 @@ Run Checkly checks from GitHub Actions with the Checkly CLI. -This action is a small wrapper around `npx checkly@ test --detach` and -`npx checkly@ trigger --detach`. It keeps the Checkly CLI as the source -of truth while making the common GitHub Actions setup easier to discover. +This action is a small wrapper around `npx checkly@ test` and +`npx checkly@ trigger`. It keeps the Checkly CLI as the source of truth +while making the common GitHub Actions setup easier to discover. ## Usage @@ -57,16 +57,20 @@ with: production,backend ``` -The action always starts detached runs. The Checkly CLI prints a test session ID -and session URL; this action exposes them as outputs and writes them to the -GitHub Actions step summary. +When `github-report` is enabled, the action asks Checkly whether the Checkly +GitHub App is connected to the repository and can report GitHub Checks. If it is, +the action runs the CLI with `--detach`, passes GitHub Actions metadata to the +CLI, and Checkly posts a GitHub Check that updates when the session finishes. -When `github-report` is enabled, the action also passes GitHub Actions metadata -to the Checkly CLI. If the Checkly GitHub App is connected to the repository, -Checkly can use that metadata to post a GitHub Check that links back to the test -session and updates when the session finishes. If the app is not connected, the -run still succeeds or fails normally and the session link in the workflow output -remains the fallback. +If GitHub Check reporting is unavailable, the action runs without `--detach` and +waits for the test session to finish so the GitHub Actions job reports the +result. It also uses the CLI GitHub reporter and writes the Checkly summary to +the GitHub Actions step summary. + +For `deployment_status` workflows, the action exposes +`github.event.deployment_status.environment_url` as `ENVIRONMENT_URL` when that +environment variable is not already set. For pull request preview URLs, pass the +target URL explicitly through `env` or the workflow `env` block. ## Inputs @@ -93,7 +97,7 @@ remains the fallback. | `verify-runtime-dependencies` | `test` only. Set to `false` to pass `--no-verify-runtime-dependencies`. | | `fail-on-no-matching` | `trigger` only. Set to `false` to pass `--no-fail-on-no-matching`. | | `verbose` | Set to `true` or `false` to pass `--verbose` or `--no-verbose`. | -| `github-report` | Best-effort GitHub Check reporting when the Checkly GitHub App is connected. Defaults to `true`. | +| `github-report` | Use detached Checkly runs with GitHub Check writeback when the Checkly GitHub App is connected. Falls back to waiting in the Action when unavailable. Defaults to `true`. | ## Outputs diff --git a/action.yml b/action.yml index 21ca9a1..434198f 100644 --- a/action.yml +++ b/action.yml @@ -76,7 +76,7 @@ inputs: description: Set to true or false to pass --verbose or --no-verbose. required: false github-report: - description: Best-effort GitHub Check reporting when the Checkly GitHub App is connected. + description: Use detached Checkly runs with GitHub Check writeback when the Checkly GitHub App is connected. Falls back to waiting in the Action when unavailable. required: false default: "true" diff --git a/scripts/run.sh b/scripts/run.sh index 350e475..27227ca 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -184,6 +184,14 @@ configure_generic_repo_env() { fi } +configure_deployment_environment_url() { + local deployment_url + deployment_url="$(github_event_value "deployment_status.environment_url")" + if [[ -n "$deployment_url" && -z "${ENVIRONMENT_URL:-}" ]]; then + export ENVIRONMENT_URL="$deployment_url" + fi +} + configure_github_report() { local value="${INPUT_GITHUB_REPORT:-true}" if falsey "$value"; then @@ -222,6 +230,104 @@ configure_github_report() { return 0 } +resolve_checkly_api_url() { + if [[ -n "${CHECKLY_API_URL:-}" ]]; then + printf '%s' "${CHECKLY_API_URL%/}" + return + fi + + case "${CHECKLY_ENV:-production}" in + local) printf '%s' "http://127.0.0.1:3000" ;; + development) printf '%s' "https://api-dev.checklyhq.com" ;; + staging) printf '%s' "https://api-test.checklyhq.com" ;; + *) printf '%s' "https://api.checklyhq.com" ;; + esac +} + +github_report_preflight() { + if [[ -n "${CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE:-}" ]]; then + if truthy "${CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE}"; then + printf 'true\tavailable\n' + else + printf 'false\t%s\n' "${CHECKLY_ACTION_GITHUB_REPORT_REASON:-unavailable}" + fi + return + fi + + if [[ "${CHECKLY_ACTION_DRY_RUN:-}" == "1" || "${CHECKLY_ACTION_DRY_RUN:-}" == "true" ]]; then + printf 'false\tdry_run\n' + return + fi + + if [[ -z "${CHECKLY_API_KEY:-}" || -z "${CHECKLY_ACCOUNT_ID:-}" ]]; then + printf 'false\tmissing_credentials\n' + return + fi + + if [[ -z "${CHECKLY_GITHUB_REPOSITORY:-}" || -z "${CHECKLY_GITHUB_SHA:-}" ]]; then + printf 'false\tmissing_metadata\n' + return + fi + + if ! command -v node >/dev/null 2>&1; then + printf 'false\tnode_unavailable\n' + return + fi + + CHECKLY_PREFLIGHT_API_URL="$(resolve_checkly_api_url)" \ + CHECKLY_PREFLIGHT_CLI_VERSION="$cli_version" \ + node <<'NODE' +const apiUrl = process.env.CHECKLY_PREFLIGHT_API_URL +const accountId = process.env.CHECKLY_ACCOUNT_ID +const apiKey = process.env.CHECKLY_API_KEY + +const payload = { + repository: process.env.CHECKLY_GITHUB_REPOSITORY, + sha: process.env.CHECKLY_GITHUB_SHA, + runId: process.env.CHECKLY_GITHUB_RUN_ID, + runAttempt: process.env.CHECKLY_GITHUB_RUN_ATTEMPT, + workflow: process.env.CHECKLY_GITHUB_WORKFLOW, + job: process.env.CHECKLY_GITHUB_JOB, + eventName: process.env.CHECKLY_GITHUB_EVENT_NAME, + ref: process.env.CHECKLY_GITHUB_REF, + headRef: process.env.CHECKLY_GITHUB_HEAD_REF, + baseRef: process.env.CHECKLY_GITHUB_BASE_REF, + serverUrl: process.env.CHECKLY_GITHUB_SERVER_URL, +} + +async function main() { + try { + const response = await fetch(`${apiUrl}/next/test-sessions/github-checks/preflight`, { + method: 'POST', + headers: { + authorization: `Bearer ${apiKey}`, + 'content-type': 'application/json', + 'user-agent': 'checkly-action', + 'x-checkly-account': accountId, + 'x-checkly-source': 'CLI', + 'x-checkly-operator': 'github-actions', + 'x-checkly-ci-name': 'GitHub Actions', + 'x-checkly-cli-version': process.env.CHECKLY_PREFLIGHT_CLI_VERSION ?? '', + }, + body: JSON.stringify(payload), + }) + + if (!response.ok) { + process.stdout.write(`false\tpreflight_http_${response.status}\n`) + return + } + + const result = await response.json() + process.stdout.write(`${result.available ? 'true' : 'false'}\t${result.reason || 'unavailable'}\n`) + } catch (_) { + process.stdout.write('false\tpreflight_failed\n') + } +} + +main() +NODE +} + command_name="$(trim "${INPUT_COMMAND:-test}")" cli_version="$(trim "${INPUT_CLI_VERSION:-latest}")" working_directory="$(trim "${INPUT_WORKING_DIRECTORY:-.}")" @@ -265,7 +371,42 @@ if [[ "$command_name" == "test" && -n "$(trim "${INPUT_FAIL_ON_NO_MATCHING:-}")" exit 1 fi -checkly_command=(npx --yes "checkly@${cli_version}" "$command_name" --detach) +configure_generic_repo_env +configure_deployment_environment_url + +github_report_requested=false +github_report_available=false +github_report_reason="disabled" +detach_run=false +github_reporter_run=true + +if falsey "${INPUT_GITHUB_REPORT:-true}"; then + clear_github_report_env +elif truthy "${INPUT_GITHUB_REPORT:-true}"; then + github_report_requested=true + configure_github_report + preflight_result="$(github_report_preflight)" + IFS=$'\t' read -r github_report_available github_report_reason <<< "$preflight_result" + if [[ "$github_report_available" == "true" ]]; then + detach_run=true + github_reporter_run=false + else + clear_github_report_env + if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then + echo "::warning::Checkly GitHub App reporting is unavailable (${github_report_reason}). Running without --detach so this GitHub Actions job waits for the Checkly test session result. Install the Checkly GitHub App on this repository to run detached and receive a Checkly GitHub Check instead." + fi + fi +else + echo "::error::Expected boolean input for github-report, got '${INPUT_GITHUB_REPORT}'." >&2 + exit 1 +fi + +checkly_command=(npx --yes "checkly@${cli_version}" "$command_name") +if [[ "$detach_run" == "true" ]]; then + checkly_command+=("--detach") +elif [[ "$github_reporter_run" == "true" ]]; then + checkly_command+=("--reporter=github") +fi add_repeated_flag_from_lines "--tags" "${INPUT_TAGS:-}" add_flag_value "--config" "${INPUT_CONFIG:-}" @@ -289,22 +430,24 @@ else add_optional_boolean_flag "--fail-on-no-matching" "--no-fail-on-no-matching" "${INPUT_FAIL_ON_NO_MATCHING:-}" fi -configure_generic_repo_env -configure_github_report - if [[ "${CHECKLY_ACTION_DRY_RUN:-}" == "1" || "${CHECKLY_ACTION_DRY_RUN:-}" == "true" ]]; then if [[ -n "$install_command" ]]; then printf 'Install command: %s\n' "$install_command" fi + if [[ -n "${ENVIRONMENT_URL:-}" ]]; then + printf 'Environment URL: %s\n' "$ENVIRONMENT_URL" + fi printf 'Command: ' printf '%q ' "${checkly_command[@]}" printf '\n' - if [[ "${CHECKLY_GITHUB_REPORT:-}" == "true" ]]; then - printf 'GitHub report: enabled' + if [[ "$github_report_requested" == "true" && "$github_report_available" == "true" ]]; then + printf 'GitHub report: detached writeback enabled' if [[ -n "${CHECKLY_GITHUB_REPOSITORY:-}" && -n "${CHECKLY_GITHUB_SHA:-}" ]]; then printf ' for %s@%s' "$CHECKLY_GITHUB_REPOSITORY" "$CHECKLY_GITHUB_SHA" fi printf '\n' + elif [[ "$github_report_requested" == "true" ]]; then + printf 'GitHub report: unavailable (%s), waiting for CLI result\n' "$github_report_reason" else printf 'GitHub report: disabled\n' fi @@ -338,6 +481,12 @@ fi append_summary "## Checkly" append_summary "" + +if [[ -f checkly-github-report.md && -n "${GITHUB_STEP_SUMMARY:-}" ]]; then + cat checkly-github-report.md >> "$GITHUB_STEP_SUMMARY" + append_summary "" +fi + if [[ -n "$test_session_id" ]]; then append_summary "- Test session ID: \`${test_session_id}\`" fi @@ -348,9 +497,12 @@ if [[ -z "$test_session_id" && -z "$test_session_url" ]]; then append_summary "- The Checkly CLI did not print a detached test session reference." fi -if truthy "${INPUT_GITHUB_REPORT:-true}"; then +if [[ "$github_report_requested" == "true" && "$github_report_available" == "true" ]]; then + append_summary "" + append_summary "GitHub Check reporting is enabled for this run." +elif [[ "$github_report_requested" == "true" ]]; then append_summary "" - append_summary "GitHub Check reporting is best-effort. If the Checkly GitHub App is not connected to this repository, use the test session link above." + append_summary "GitHub Check reporting was unavailable (${github_report_reason}). This job waited for the Checkly run to finish. Install the Checkly GitHub App on this repository to run detached and receive a Checkly GitHub Check." fi rm -f "$output_file" diff --git a/scripts/test.sh b/scripts/test.sh index 0eaf3a8..0321d8f 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -21,6 +21,7 @@ assert_contains() { test_command_output="$( INPUT_COMMAND=test \ INPUT_CLI_VERSION=1.2.3 \ + CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=true \ INPUT_TAGS=$'production,webapp\nproduction,backend' \ INPUT_GREP='checkout' \ INPUT_FILES=$'checks/**/*.check.ts\nsmoke.check.ts' \ @@ -37,11 +38,12 @@ assert_contains "$test_command_output" "--tags production\\,webapp --tags produc assert_contains "$test_command_output" "--grep checkout" assert_contains "$test_command_output" "--update-snapshots" assert_contains "$test_command_output" "checks/\\*\\*/\\*.check.ts smoke.check.ts" -assert_contains "$test_command_output" "GitHub report: enabled" +assert_contains "$test_command_output" "GitHub report: detached writeback enabled" trigger_command_output="$( INPUT_COMMAND=trigger \ INPUT_CLI_VERSION=latest \ + CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=true \ INPUT_TAGS='production' \ INPUT_CHECK_ID='abc,def' \ INPUT_FAIL_ON_NO_MATCHING=false \ @@ -53,6 +55,18 @@ assert_contains "$trigger_command_output" "--tags production" assert_contains "$trigger_command_output" "--check-id abc\\,def" assert_contains "$trigger_command_output" "--no-fail-on-no-matching" +fallback_command_output="$( + INPUT_COMMAND=test \ + INPUT_CLI_VERSION=1.2.3 \ + CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=false \ + CHECKLY_ACTION_GITHUB_REPORT_REASON=github_app_not_connected \ + GITHUB_ACTIONS=true \ + run_dry +)" + +assert_contains "$fallback_command_output" "checkly@1.2.3 test --reporter=github" +assert_contains "$fallback_command_output" "GitHub report: unavailable (github_app_not_connected), waiting for CLI result" + github_event_path="$(mktemp)" trap 'rm -f "$github_event_path"' EXIT cat > "$github_event_path" <<'JSON' @@ -71,6 +85,7 @@ JSON github_report_output="$( INPUT_COMMAND=test \ INPUT_GITHUB_REPORT=true \ + CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=true \ GITHUB_REPOSITORY=checkly/playwright-reporter-demo \ GITHUB_SHA=merge123def456 \ GITHUB_RUN_ID=123456 \ @@ -87,7 +102,7 @@ github_report_output="$( run_dry )" -assert_contains "$github_report_output" "GitHub report: enabled for checkly/playwright-reporter-demo@head123def456" +assert_contains "$github_report_output" "GitHub report: detached writeback enabled for checkly/playwright-reporter-demo@head123def456" github_report_disabled_output="$( INPUT_COMMAND=test \ @@ -101,5 +116,26 @@ github_report_disabled_output="$( )" assert_contains "$github_report_disabled_output" "GitHub report: disabled" +assert_contains "$github_report_disabled_output" "checkly@latest test --reporter=github" + +deployment_event_path="$(mktemp)" +trap 'rm -f "$github_event_path" "$deployment_event_path"' EXIT +cat > "$deployment_event_path" <<'JSON' +{ + "deployment_status": { + "environment_url": "https://preview.example.com" + } +} +JSON + +deployment_url_output="$( + INPUT_COMMAND=test \ + INPUT_GITHUB_REPORT=false \ + GITHUB_EVENT_NAME=deployment_status \ + GITHUB_EVENT_PATH="$deployment_event_path" \ + run_dry +)" + +assert_contains "$deployment_url_output" "Environment URL: https://preview.example.com" echo "All local action tests passed." From 1dd0644ab7ae0654317a73c78816809deb95a706 Mon Sep 17 00:00:00 2001 From: Herve Labas Date: Fri, 3 Jul 2026 14:43:37 +0200 Subject: [PATCH 4/5] fix: require compatible CLI for GitHub report --- README.md | 2 +- action.yml | 2 +- scripts/run.sh | 27 +++++++++++++++++++++++++++ scripts/test.sh | 46 +++++++++++++++++++++++++++++++++++++++++----- 4 files changed, 70 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index dfd4788..f923dce 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ target URL explicitly through `env` or the workflow `env` block. | Input | Description | | --- | --- | | `command` | `test` for local constructs or `trigger` for deployed checks. Defaults to `test`. | -| `cli-version` | Checkly CLI npm version. Defaults to `latest`. | +| `cli-version` | Checkly CLI npm version. Defaults to `latest`. When `github-report` is enabled, pinned stable versions must be `8.12.0` or newer. Dist-tags, canaries, and prereleases are allowed. | | `working-directory` | Directory where the CLI command should run. Defaults to `.`. | | `install-command` | Optional command to run before the Checkly CLI command, inside `working-directory`. | | `tags` | One `--tags` filter per line. Each line can contain comma-separated tags. | diff --git a/action.yml b/action.yml index 434198f..0b2dca1 100644 --- a/action.yml +++ b/action.yml @@ -12,7 +12,7 @@ inputs: required: false default: test cli-version: - description: Checkly CLI npm version to run. + description: Checkly CLI npm version to run. When github-report is enabled, pinned stable versions must be 8.12.0 or newer. required: false default: latest working-directory: diff --git a/scripts/run.sh b/scripts/run.sh index 27227ca..2708932 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -79,6 +79,32 @@ add_positional_from_lines() { done <<< "$values" } +validate_cli_version_for_github_report() { + local version + version="$(trim "${1:-}")" + if [[ -z "$version" ]]; then + return + fi + + # Only reject exact pinned stable semver below 8.12.0. Dist-tags, ranges, + # canaries, and prereleases are allowed because they may point at compatible + # builds before a stable release exists. + if [[ "$version" =~ ^v?([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + local major="${BASH_REMATCH[1]}" + local minor="${BASH_REMATCH[2]}" + local patch="${BASH_REMATCH[3]}" + + if (( major < 8 || (major == 8 && minor < 12) )); then + echo "::error::github-report requires Checkly CLI 8.12.0 or newer when cli-version is pinned. Use cli-version: latest, a canary/prerelease, or a version >= 8.12.0. Got '${version}'." >&2 + exit 1 + fi + + # Keep shellcheck/linters happy that patch is intentionally parsed as part + # of the exact semver guard even though the minimum is major/minor aligned. + : "$patch" + fi +} + write_output() { local name="$1" local value="$2" @@ -384,6 +410,7 @@ if falsey "${INPUT_GITHUB_REPORT:-true}"; then clear_github_report_env elif truthy "${INPUT_GITHUB_REPORT:-true}"; then github_report_requested=true + validate_cli_version_for_github_report "$cli_version" configure_github_report preflight_result="$(github_report_preflight)" IFS=$'\t' read -r github_report_available github_report_reason <<< "$preflight_result" diff --git a/scripts/test.sh b/scripts/test.sh index 0321d8f..83cea45 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -18,9 +18,26 @@ assert_contains() { fi } +assert_fails_with() { + local expected="$1" + shift + local output + set +e + output="$("$@" 2>&1)" + local status="$?" + set -e + if [[ "$status" -eq 0 ]]; then + echo "Expected command to fail." >&2 + echo "Actual output:" >&2 + echo "$output" >&2 + exit 1 + fi + assert_contains "$output" "$expected" +} + test_command_output="$( INPUT_COMMAND=test \ - INPUT_CLI_VERSION=1.2.3 \ + INPUT_CLI_VERSION=8.12.0 \ CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=true \ INPUT_TAGS=$'production,webapp\nproduction,backend' \ INPUT_GREP='checkout' \ @@ -33,7 +50,7 @@ test_command_output="$( )" assert_contains "$test_command_output" "Install command: npm ci" -assert_contains "$test_command_output" "checkly@1.2.3 test --detach" +assert_contains "$test_command_output" "checkly@8.12.0 test --detach" assert_contains "$test_command_output" "--tags production\\,webapp --tags production\\,backend" assert_contains "$test_command_output" "--grep checkout" assert_contains "$test_command_output" "--update-snapshots" @@ -57,16 +74,34 @@ assert_contains "$trigger_command_output" "--no-fail-on-no-matching" fallback_command_output="$( INPUT_COMMAND=test \ - INPUT_CLI_VERSION=1.2.3 \ + INPUT_CLI_VERSION=8.12.0 \ CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=false \ CHECKLY_ACTION_GITHUB_REPORT_REASON=github_app_not_connected \ GITHUB_ACTIONS=true \ run_dry )" -assert_contains "$fallback_command_output" "checkly@1.2.3 test --reporter=github" +assert_contains "$fallback_command_output" "checkly@8.12.0 test --reporter=github" assert_contains "$fallback_command_output" "GitHub report: unavailable (github_app_not_connected), waiting for CLI result" +assert_fails_with "github-report requires Checkly CLI 8.12.0 or newer" env \ + INPUT_COMMAND=test \ + INPUT_CLI_VERSION=8.11.9 \ + INPUT_GITHUB_REPORT=true \ + CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=true \ + CHECKLY_ACTION_DRY_RUN=1 \ + "$ROOT_DIR/scripts/run.sh" + +prerelease_version_output="$( + INPUT_COMMAND=test \ + INPUT_CLI_VERSION=0.0.0-canary.58c867e \ + INPUT_GITHUB_REPORT=true \ + CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=true \ + run_dry +)" + +assert_contains "$prerelease_version_output" "checkly@0.0.0-canary.58c867e test --detach" + github_event_path="$(mktemp)" trap 'rm -f "$github_event_path"' EXIT cat > "$github_event_path" <<'JSON' @@ -106,6 +141,7 @@ assert_contains "$github_report_output" "GitHub report: detached writeback enabl github_report_disabled_output="$( INPUT_COMMAND=test \ + INPUT_CLI_VERSION=8.11.9 \ INPUT_GITHUB_REPORT=false \ CHECKLY_GITHUB_REPORT=true \ CHECKLY_GITHUB_REPOSITORY=spoofed/repository \ @@ -116,7 +152,7 @@ github_report_disabled_output="$( )" assert_contains "$github_report_disabled_output" "GitHub report: disabled" -assert_contains "$github_report_disabled_output" "checkly@latest test --reporter=github" +assert_contains "$github_report_disabled_output" "checkly@8.11.9 test --reporter=github" deployment_event_path="$(mktemp)" trap 'rm -f "$github_event_path" "$deployment_event_path"' EXIT From 345bbc3cb766b6a67d56cb1e3b835bfcc0e14376 Mon Sep 17 00:00:00 2001 From: Herve Labas Date: Sat, 4 Jul 2026 10:13:19 +0200 Subject: [PATCH 5/5] fix: harden the github-report path for zero-config marketplace use - An old pinned CLI no longer hard-fails the job: cli-version < 8.12.0 now falls back to the non-detached mode with reason cli_version_too_old, skipping the preflight entirely. - The preflight fetch gets a 10s AbortSignal timeout so a slow backend can never stall the customer's job before tests run. - The preflight command substitution tolerates a non-zero node exit (OOM/kill) instead of aborting the whole action under set -e. - The preflight-sourced reason is sanitized to [a-z0-9_] before being interpolated into ::warning:: workflow commands and the step summary, closing a workflow-command injection surface. - resolve_github_repository/sha run once and feed both env configs, dropping four redundant node forks per run; configure_github_report loses its unreachable duplicate boolean validation. - README/action.yml copy updated; tests cover the old-CLI fallback and the invalid-boolean error. Co-Authored-By: Claude Fable 5 --- README.md | 2 +- action.yml | 2 +- scripts/run.sh | 91 ++++++++++++++++++++++++++----------------------- scripts/test.sh | 14 +++++++- 4 files changed, 64 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index f923dce..09ea5ec 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ target URL explicitly through `env` or the workflow `env` block. | Input | Description | | --- | --- | | `command` | `test` for local constructs or `trigger` for deployed checks. Defaults to `test`. | -| `cli-version` | Checkly CLI npm version. Defaults to `latest`. When `github-report` is enabled, pinned stable versions must be `8.12.0` or newer. Dist-tags, canaries, and prereleases are allowed. | +| `cli-version` | Checkly CLI npm version. Defaults to `latest`. GitHub Check writeback needs `8.12.0` or newer; older pinned versions fall back to waiting in the Action. Dist-tags, canaries, and prereleases are assumed compatible. | | `working-directory` | Directory where the CLI command should run. Defaults to `.`. | | `install-command` | Optional command to run before the Checkly CLI command, inside `working-directory`. | | `tags` | One `--tags` filter per line. Each line can contain comma-separated tags. | diff --git a/action.yml b/action.yml index 0b2dca1..0ba7fd0 100644 --- a/action.yml +++ b/action.yml @@ -12,7 +12,7 @@ inputs: required: false default: test cli-version: - description: Checkly CLI npm version to run. When github-report is enabled, pinned stable versions must be 8.12.0 or newer. + description: Checkly CLI npm version to run. GitHub Check writeback needs 8.12.0 or newer; older pinned versions fall back to waiting in the Action. required: false default: latest working-directory: diff --git a/scripts/run.sh b/scripts/run.sh index 2708932..4bd5e94 100755 --- a/scripts/run.sh +++ b/scripts/run.sh @@ -79,30 +79,32 @@ add_positional_from_lines() { done <<< "$values" } -validate_cli_version_for_github_report() { +cli_version_supports_github_report() { local version version="$(trim "${1:-}")" - if [[ -z "$version" ]]; then - return - fi - # Only reject exact pinned stable semver below 8.12.0. Dist-tags, ranges, - # canaries, and prereleases are allowed because they may point at compatible - # builds before a stable release exists. - if [[ "$version" =~ ^v?([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then + # Only exact pinned stable semver is comparable against the 8.12.0 floor. + # Dist-tags, ranges, canaries, and prereleases pass because they may point + # at compatible builds before a stable release exists. + if [[ "$version" =~ ^v?([0-9]+)\.([0-9]+)\.[0-9]+$ ]]; then local major="${BASH_REMATCH[1]}" local minor="${BASH_REMATCH[2]}" - local patch="${BASH_REMATCH[3]}" if (( major < 8 || (major == 8 && minor < 12) )); then - echo "::error::github-report requires Checkly CLI 8.12.0 or newer when cli-version is pinned. Use cli-version: latest, a canary/prerelease, or a version >= 8.12.0. Got '${version}'." >&2 - exit 1 + return 1 fi - - # Keep shellcheck/linters happy that patch is intentionally parsed as part - # of the exact semver guard even though the minimum is major/minor aligned. - : "$patch" fi + + return 0 +} + +# Reasons are interpolated into ::warning:: workflow commands and the step +# summary; constrain them to a safe charset so a malformed or hostile +# preflight response can never inject workflow commands or extra lines. +sanitize_reason() { + local value + value="$(printf '%s' "${1:-}" | tr -cd 'a-z0-9_')" + printf '%s' "${value:-unavailable}" } write_output() { @@ -192,14 +194,13 @@ clear_github_report_env() { } configure_generic_repo_env() { - local repository - repository="$(resolve_github_repository)" + local repository="$1" + local sha="$2" + if [[ -n "$repository" ]]; then export CHECKLY_REPO_URL="${CHECKLY_REPO_URL:-${GITHUB_SERVER_URL:-https://github.com}/${repository}}" fi - local sha - sha="$(resolve_github_sha)" if [[ -n "$sha" ]]; then export CHECKLY_REPO_SHA="${CHECKLY_REPO_SHA:-$sha}" fi @@ -219,26 +220,15 @@ configure_deployment_environment_url() { } configure_github_report() { - local value="${INPUT_GITHUB_REPORT:-true}" - if falsey "$value"; then - clear_github_report_env - return - fi - if ! truthy "$value"; then - echo "::error::Expected boolean input for github-report, got '${value}'." >&2 - exit 1 - fi + local repository="$1" + local sha="$2" export CHECKLY_GITHUB_REPORT=true - local repository - repository="$(resolve_github_repository)" if [[ -n "$repository" ]]; then export CHECKLY_GITHUB_REPOSITORY="$repository" fi - local sha - sha="$(resolve_github_sha)" if [[ -n "$sha" ]]; then export CHECKLY_GITHUB_SHA="$sha" fi @@ -336,6 +326,8 @@ async function main() { 'x-checkly-cli-version': process.env.CHECKLY_PREFLIGHT_CLI_VERSION ?? '', }, body: JSON.stringify(payload), + // The preflight is advisory: never let a slow backend hold up the run. + signal: AbortSignal.timeout(10_000), }) if (!response.ok) { @@ -397,7 +389,12 @@ if [[ "$command_name" == "test" && -n "$(trim "${INPUT_FAIL_ON_NO_MATCHING:-}")" exit 1 fi -configure_generic_repo_env +# Resolve the repository/sha once; both the generic repo env and the GitHub +# report env derive from the same values. +github_repository="$(resolve_github_repository)" +github_sha="$(resolve_github_sha)" + +configure_generic_repo_env "$github_repository" "$github_sha" configure_deployment_environment_url github_report_requested=false @@ -410,17 +407,27 @@ if falsey "${INPUT_GITHUB_REPORT:-true}"; then clear_github_report_env elif truthy "${INPUT_GITHUB_REPORT:-true}"; then github_report_requested=true - validate_cli_version_for_github_report "$cli_version" - configure_github_report - preflight_result="$(github_report_preflight)" - IFS=$'\t' read -r github_report_available github_report_reason <<< "$preflight_result" - if [[ "$github_report_available" == "true" ]]; then - detach_run=true - github_reporter_run=false - else + if ! cli_version_supports_github_report "$cli_version"; then + # Writeback needs CLI >= 8.12.0, but an older pinned CLI is not an error: + # fall back to the non-detached mode like any other unavailable reason. + github_report_reason="cli_version_too_old" clear_github_report_env if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then - echo "::warning::Checkly GitHub App reporting is unavailable (${github_report_reason}). Running without --detach so this GitHub Actions job waits for the Checkly test session result. Install the Checkly GitHub App on this repository to run detached and receive a Checkly GitHub Check instead." + echo "::warning::GitHub Check writeback needs Checkly CLI 8.12.0 or newer (cli-version is '${cli_version}'). Running without --detach so this GitHub Actions job waits for the Checkly test session result. Use cli-version: latest or >= 8.12.0 to enable detached GitHub Check reporting." + fi + else + configure_github_report "$github_repository" "$github_sha" + preflight_result="$(github_report_preflight)" || preflight_result=$'false\tpreflight_crashed' + IFS=$'\t' read -r github_report_available github_report_reason <<< "$preflight_result" + github_report_reason="$(sanitize_reason "$github_report_reason")" + if [[ "$github_report_available" == "true" ]]; then + detach_run=true + github_reporter_run=false + else + clear_github_report_env + if [[ "${GITHUB_ACTIONS:-}" == "true" ]]; then + echo "::warning::Checkly GitHub App reporting is unavailable (${github_report_reason}). Running without --detach so this GitHub Actions job waits for the Checkly test session result. Install the Checkly GitHub App on this repository to run detached and receive a Checkly GitHub Check instead." + fi fi fi else diff --git a/scripts/test.sh b/scripts/test.sh index 83cea45..fd243c7 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -84,11 +84,23 @@ fallback_command_output="$( assert_contains "$fallback_command_output" "checkly@8.12.0 test --reporter=github" assert_contains "$fallback_command_output" "GitHub report: unavailable (github_app_not_connected), waiting for CLI result" -assert_fails_with "github-report requires Checkly CLI 8.12.0 or newer" env \ +# An old pinned CLI must fall back (skipping the preflight entirely), not fail +# the job: CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=true would force the detached +# path if the preflight were still consulted. +old_cli_fallback_output="$( INPUT_COMMAND=test \ INPUT_CLI_VERSION=8.11.9 \ INPUT_GITHUB_REPORT=true \ CHECKLY_ACTION_GITHUB_REPORT_AVAILABLE=true \ + run_dry +)" + +assert_contains "$old_cli_fallback_output" "checkly@8.11.9 test --reporter=github" +assert_contains "$old_cli_fallback_output" "GitHub report: unavailable (cli_version_too_old), waiting for CLI result" + +assert_fails_with "Expected boolean input for github-report" env \ + INPUT_COMMAND=test \ + INPUT_GITHUB_REPORT=banana \ CHECKLY_ACTION_DRY_RUN=1 \ "$ROOT_DIR/scripts/run.sh"