From 1031db8c143cc4e1778b381e9671b73f76e09dc4 Mon Sep 17 00:00:00 2001 From: Dylan Frankland Date: Fri, 2 Oct 2026 20:30:05 +0000 Subject: [PATCH 1/5] ci: publish releases to trunk.io/releases/analytics-cli as well as GitHub Each release's assets are also uploaded to the trunk-releases bucket under releases/analytics-cli/prod//, with a manifest of sha256s written last and a channel.json pointer that only the new Promote Release workflow moves. Mirror Releases to S3 backfills releases cut before this lane existed. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/scripts/publish-release-to-s3.sh | 97 ++++++++++++++++++++++ .github/scripts/set-latest-in-s3.sh | 30 +++++++ .github/workflows/mirror_release_to_s3.yml | 57 +++++++++++++ .github/workflows/promote_release.yml | 51 ++++++++++++ .github/workflows/release.yml | 5 ++ .github/workflows/release_cli.yml | 15 ++++ CONTRIBUTING.md | 16 ++++ 7 files changed, 271 insertions(+) create mode 100755 .github/scripts/publish-release-to-s3.sh create mode 100755 .github/scripts/set-latest-in-s3.sh create mode 100644 .github/workflows/mirror_release_to_s3.yml create mode 100644 .github/workflows/promote_release.yml diff --git a/.github/scripts/publish-release-to-s3.sh b/.github/scripts/publish-release-to-s3.sh new file mode 100755 index 00000000..0d8024f2 --- /dev/null +++ b/.github/scripts/publish-release-to-s3.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash +# Publish one release's assets to releases/analytics-cli/prod// in the trunk-releases +# bucket (served at https://trunk.io/releases/analytics-cli/prod/). Never moves channel.json: +# a published version is a prerelease until promote_release.yml points latest at it. +# +# Usage: publish-release-to-s3.sh [--skip-complete] +# +# Every *.tar.gz / *.zip in is uploaded under its own filename. Objects are +# immutable: an asset already in the bucket is skipped if its recorded sha256 matches and is an +# error if it differs. manifest.json goes up last, so its presence means the version is +# complete. With --skip-complete (backfills), a version whose manifest already exists is a +# no-op; without it, that is an error. +set -euo pipefail + +bucket="${BUCKET:-trunk-releases}" +prefix="${PREFIX:-releases/analytics-cli/prod}" + +skip_complete=false +if [[ ${1-} == "--skip-complete" ]]; then + skip_complete=true + shift +fi +version="${1:?usage: publish-release-to-s3.sh [--skip-complete] }" +asset_dir="${2:?usage: publish-release-to-s3.sh [--skip-complete] }" + +if [[ ! ${version} =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "::error::'${version}' is not a semver version" + exit 1 +fi + +# Prints the object's recorded sha256, or nothing when the key does not exist. Any failure other +# than a 404 aborts: without s3:ListBucket a missing key reads as 403, which must not be taken +# for "absent". +recorded_sha256() { + local key="$1" output + if output="$(aws s3api head-object --bucket "${bucket}" --key "${key}" \ + --query 'Metadata.sha256' --output text 2>&1)"; then + echo "${output}" + elif [[ ${output} != *"Not Found"* && ${output} != *"404"* ]]; then + echo "::error::head-object s3://${bucket}/${key} failed: ${output}" >&2 + return 1 + fi +} + +version_prefix="${prefix}/${version}" +manifest_key="${version_prefix}/manifest.json" + +manifest_sha="$(recorded_sha256 "${manifest_key}")" +if [[ -n ${manifest_sha} ]]; then + if [[ ${skip_complete} == true ]]; then + echo "${version} is already published (s3://${bucket}/${manifest_key}); nothing to do" + exit 0 + fi + echo "::error::${version} is already published (s3://${bucket}/${manifest_key}); released versions are immutable" + exit 1 +fi + +shopt -s nullglob +assets=("${asset_dir}"/*.tar.gz "${asset_dir}"/*.zip) +if [[ ${#assets[@]} -eq 0 ]]; then + echo "::error::no *.tar.gz or *.zip assets in ${asset_dir}" + exit 1 +fi + +manifest="$(jq -n --arg v "${version}" '{version: $v, artifacts: {}}')" +for asset in "${assets[@]}"; do + name="$(basename "${asset}")" + key="${version_prefix}/${name}" + sha="$(sha256sum "${asset}" | cut -d' ' -f1)" + case "${name}" in + *.zip) content_type="application/zip" ;; + *) content_type="application/gzip" ;; + esac + + existing="$(recorded_sha256 "${key}")" + if [[ -z ${existing} ]]; then + aws s3 cp "${asset}" "s3://${bucket}/${key}" --only-show-errors \ + --content-type "${content_type}" \ + --metadata "sha256=${sha},release-version=${version}" + echo "uploaded ${key}" + elif [[ ${existing} == "${sha}" ]]; then + echo "already uploaded ${key}" + else + echo "::error::s3://${bucket}/${key} already exists with sha256 ${existing}, not ${sha}; released artifacts are immutable" + exit 1 + fi + + manifest="$(jq --arg n "${name}" --arg s "${sha}" '.artifacts[$n] = {sha256: $s}' <<<"${manifest}")" +done + +manifest_file="$(mktemp)" +jq . <<<"${manifest}" >"${manifest_file}" +aws s3 cp "${manifest_file}" "s3://${bucket}/${manifest_key}" --only-show-errors \ + --content-type "application/json" \ + --metadata "sha256=$(sha256sum "${manifest_file}" | cut -d' ' -f1),release-version=${version}" +rm -f "${manifest_file}" +echo "published ${version} to https://trunk.io/${version_prefix}/ (${#assets[@]} artifacts)" diff --git a/.github/scripts/set-latest-in-s3.sh b/.github/scripts/set-latest-in-s3.sh new file mode 100755 index 00000000..2447f2e2 --- /dev/null +++ b/.github/scripts/set-latest-in-s3.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# Point releases/analytics-cli/prod/channel.json at an already-published version. This is the +# only mutable object in the prefix; consumers read `latest` from it. +# +# Usage: set-latest-in-s3.sh +set -euo pipefail + +bucket="${BUCKET:-trunk-releases}" +prefix="${PREFIX:-releases/analytics-cli/prod}" +version="${1:?usage: set-latest-in-s3.sh }" + +if [[ ! ${version} =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then + echo "::error::'${version}' is not a semver version" + exit 1 +fi + +manifest_key="${prefix}/${version}/manifest.json" +if ! output="$(aws s3api head-object --bucket "${bucket}" --key "${manifest_key}" 2>&1)"; then + if [[ ${output} == *"Not Found"* || ${output} == *"404"* ]]; then + echo "::error::no s3://${bucket}/${manifest_key}; ${version} was never published (or only partially). Publish it first (mirror_release_to_s3.yml)." + else + echo "::error::head-object s3://${bucket}/${manifest_key} failed: ${output}" + fi + exit 1 +fi + +jq -nc --arg v "${version}" '{latest: $v}' | + aws s3 cp - "s3://${bucket}/${prefix}/channel.json" --only-show-errors \ + --content-type "application/json" --cache-control "max-age=60" +echo "https://trunk.io/${prefix}/channel.json now points at ${version}" diff --git a/.github/workflows/mirror_release_to_s3.yml b/.github/workflows/mirror_release_to_s3.yml new file mode 100644 index 00000000..6b74e89c --- /dev/null +++ b/.github/workflows/mirror_release_to_s3.yml @@ -0,0 +1,57 @@ +name: Mirror Releases to S3 +run-name: Mirror ${{ inputs.tags }} to S3 +on: + workflow_dispatch: + inputs: + # trunk-ignore(checkov/CKV_GHA_7) + tags: + type: string + description: Space-separated release tags to copy from GitHub to trunk.io/releases/analytics-cli/prod/, or `all` + required: true + set-latest-to-github-latest: + type: boolean + description: Afterwards, point channel.json at GitHub's current latest release + default: false +concurrency: ${{ github.workflow }} + +permissions: + contents: read + +jobs: + mirror: + name: Mirror ${{ inputs.tags }} + runs-on: ubuntu-latest + timeout-minutes: 120 + permissions: + contents: read + id-token: write + env: + TAGS: ${{ inputs.tags }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ vars.TRUNK_RELEASES_PUBLISH_ROLE_ARN }} + role-skip-session-tagging: true + aws-region: us-west-2 + + # --skip-complete makes a re-run cheap: versions already published are skipped, and a + # version interrupted mid-upload is finished. + - name: Mirror + run: | + if [[ ${TAGS} == all ]]; then + TAGS="$(gh release list --limit 1000 --exclude-drafts --json tagName --jq '.[].tagName' | tr '\n' ' ')" + fi + for tag in ${TAGS}; do + dir="$(mktemp -d)" + gh release download "${tag}" --dir "${dir}" --pattern '*.tar.gz' --pattern '*.zip' + .github/scripts/publish-release-to-s3.sh --skip-complete "${tag}" "${dir}" + rm -rf "${dir}" + done + + - name: Point channel.json at GitHub's latest release + if: inputs.set-latest-to-github-latest + run: .github/scripts/set-latest-in-s3.sh "$(gh release view --json tagName --jq .tagName)" diff --git a/.github/workflows/promote_release.yml b/.github/workflows/promote_release.yml new file mode 100644 index 00000000..f58e73fa --- /dev/null +++ b/.github/workflows/promote_release.yml @@ -0,0 +1,51 @@ +name: Promote Release +run-name: Promote ${{ inputs.version }} to latest +on: + workflow_dispatch: + inputs: + # trunk-ignore(checkov/CKV_GHA_7) + version: + type: string + description: Already-released version to make latest (on GitHub and at trunk.io/releases/analytics-cli/prod/channel.json) + required: true +concurrency: ${{ github.workflow }} + +permissions: + contents: read + +jobs: + promote: + name: Promote ${{ inputs.version }} + runs-on: ubuntu-latest + permissions: + contents: write + id-token: write + env: + VERSION: ${{ inputs.version }} + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - uses: actions/checkout@v4 + + - name: Check the GitHub release + run: | + if [[ ${VERSION} == *-* ]]; then + echo "::error::${VERSION} is a prerelease version; only a stable version can be latest" + exit 1 + fi + gh release view "${VERSION}" --json tagName,isDraft --jq 'if .isDraft then error("release is a draft") else .tagName end' + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ vars.TRUNK_RELEASES_PUBLISH_ROLE_ARN }} + role-skip-session-tagging: true + aws-region: us-west-2 + + # S3 moves first: its preconditions (the role, a complete manifest) are the ones that fail in + # practice, and when they do GitHub has not moved either. Both steps are idempotent, so if the + # GitHub edit fails afterwards, re-running this workflow converges the two. + - name: Point channel.json at ${{ inputs.version }} + run: .github/scripts/set-latest-in-s3.sh "${VERSION}" + + - name: Mark ${{ inputs.version }} latest on GitHub + run: gh release edit "${VERSION}" --prerelease=false --latest diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a5fa0128..248ace27 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,6 +11,11 @@ jobs: release_cli: name: Trigger CLI Release uses: ./.github/workflows/release_cli.yml + # A called workflow's jobs can hold no more than its caller grants; publishing to S3 needs + # the OIDC token. + permissions: + contents: write + id-token: write secrets: inherit with: release_tag: ${{ inputs.version }} diff --git a/.github/workflows/release_cli.yml b/.github/workflows/release_cli.yml index 68a6a2dd..bef23c7c 100644 --- a/.github/workflows/release_cli.yml +++ b/.github/workflows/release_cli.yml @@ -241,6 +241,9 @@ jobs: name: Tag and Release [ ${{ github.event.inputs.release_tag || inputs.release_tag }} ] runs-on: ubuntu-latest needs: [build_release, perform_smoke_test] + permissions: + contents: write + id-token: write # only empty during testing if: ${{ needs.perform_smoke_test.result == 'success' && (github.event.inputs.release_tag != '' || inputs.release_tag != '') }} steps: @@ -308,6 +311,18 @@ jobs: --generate-notes ${{ github.event.inputs.release_tag || inputs.release_tag }} \ ./build/*.tar.gz ./build/*.zip + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v4 + with: + role-to-assume: ${{ vars.TRUNK_RELEASES_PUBLISH_ROLE_ARN }} + role-skip-session-tagging: true + aws-region: us-west-2 + + # Uploads to releases/analytics-cli/prod// but leaves channel.json alone: like the + # GitHub release above, the version stays a prerelease until promote_release.yml. + - name: Publish release to S3 + run: .github/scripts/publish-release-to-s3.sh "${{ github.event.inputs.release_tag || inputs.release_tag }}" ./build + - name: Create Sentry release uses: getsentry/action-release@v1 env: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9be7cc33..d13b8010 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -116,3 +116,19 @@ The pattern is: - Use `tracing` for observability and debugging (Sentry + optional verbose output) - Use `superconsole` for user-facing communication - Errors that block execution should use both: `tracing` for logging and `superconsole` for user display + +## Releasing + +Releases are published in two places: GitHub releases, and `https://trunk.io/releases/analytics-cli/prod/` (the `trunk-releases` S3 bucket). Trunk's own consumers (the `analytics-uploader` action and the `trunk` CLI's `flakytests` command) read the S3 copy. GitHub releases stay, because the frozen legacy `trunk` launcher and customers' own scripts download from them. + +1. **Cut**: run the `Release` workflow with the version. It builds, smoke-tests, creates a GitHub **prerelease**, and uploads the same assets to `releases/analytics-cli/prod//`. A version is immutable once published. +2. **Promote**: run the `Promote Release` workflow with the version. It points `releases/analytics-cli/prod/channel.json` at it and marks it latest on GitHub. Do **not** mark a release latest in the GitHub UI, since that leaves `channel.json` behind. +3. **Backfill**: run `Mirror Releases to S3` to copy releases that predate the S3 lane (or that a failed run left incomplete). Pass space-separated tags or `all`. Re-running it is safe because complete versions are skipped. + +Layout under `releases/analytics-cli/prod/`: + +- `channel.json`: `{"latest": ""}`, the only mutable object (`Cache-Control: max-age=60`). +- `/`: each GitHub release asset under the same filename, e.g. `trunk-analytics-cli-x86_64-unknown-linux.tar.gz`. +- `/manifest.json`: `{"version": "", "artifacts": {"": {"sha256": ""}}}`, uploaded last, so its presence means the version is complete. + +One-time prerequisite: the publishing workflows assume the IAM role `trunk-analytics-cli-release-role` in AWS account 443897627245 over GitHub OIDC, through the repository variable `TRUNK_RELEASES_PUBLISH_ROLE_ARN`. The role is granted only the `releases/analytics-cli/*` prefix, plus `s3:ListBucket` on it so a missing key reads as 404 rather than 403. Because this is a public repository, its trust should be restricted to `repo:trunk-io/analytics-cli:ref:refs/heads/main`, not `repo:trunk-io/analytics-cli:*`. See trunk2's `docs/runbooks/trunk-releases-publish-roles.md` for how these roles are created. From 16f0c0da4afe40ea348398be86cd5c6ac85ad5df Mon Sep 17 00:00:00 2001 From: Dylan Frankland Date: Fri, 2 Oct 2026 20:52:45 +0000 Subject: [PATCH 2/5] ci: check mirrored assets against GitHub's recorded sha256 digests Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/scripts/verify-github-digests.sh | 33 ++++++++++++++++++++++ .github/workflows/mirror_release_to_s3.yml | 1 + CONTRIBUTING.md | 2 +- 3 files changed, 35 insertions(+), 1 deletion(-) create mode 100755 .github/scripts/verify-github-digests.sh diff --git a/.github/scripts/verify-github-digests.sh b/.github/scripts/verify-github-digests.sh new file mode 100755 index 00000000..6e071621 --- /dev/null +++ b/.github/scripts/verify-github-digests.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# GitHub records asset digests only since June 2025 (0.10.0-beta.0 on); older assets pass unchecked. +# Usage: verify-github-digests.sh +set -euo pipefail + +tag="${1:?usage: verify-github-digests.sh }" +asset_dir="${2:?usage: verify-github-digests.sh }" + +declare -A digests +while IFS=$'\t' read -r name digest; do + digests["${name}"]="${digest}" +done < <(gh api "repos/{owner}/{repo}/releases/tags/${tag}" \ + --jq '.assets[] | [.name, (.digest // "")] | @tsv') + +verified=0 +unverified=0 +shopt -s nullglob +for asset in "${asset_dir}"/*.tar.gz "${asset_dir}"/*.zip; do + name="$(basename "${asset}")" + expected="${digests[${name}]-}" + if [[ -z ${expected} ]]; then + unverified=$((unverified + 1)) + continue + fi + actual="sha256:$(sha256sum "${asset}" | cut -d' ' -f1)" + if [[ ${actual} != "${expected}" ]]; then + echo "::error::${tag}/${name}: downloaded ${actual}, GitHub recorded ${expected}" + exit 1 + fi + verified=$((verified + 1)) +done + +echo "${tag}: ${verified} asset(s) match GitHub's digest, ${unverified} have no GitHub digest" diff --git a/.github/workflows/mirror_release_to_s3.yml b/.github/workflows/mirror_release_to_s3.yml index 6b74e89c..5825d98d 100644 --- a/.github/workflows/mirror_release_to_s3.yml +++ b/.github/workflows/mirror_release_to_s3.yml @@ -48,6 +48,7 @@ jobs: for tag in ${TAGS}; do dir="$(mktemp -d)" gh release download "${tag}" --dir "${dir}" --pattern '*.tar.gz' --pattern '*.zip' + .github/scripts/verify-github-digests.sh "${tag}" "${dir}" .github/scripts/publish-release-to-s3.sh --skip-complete "${tag}" "${dir}" rm -rf "${dir}" done diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d13b8010..5289cf7c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,7 +123,7 @@ Releases are published in two places: GitHub releases, and `https://trunk.io/rel 1. **Cut**: run the `Release` workflow with the version. It builds, smoke-tests, creates a GitHub **prerelease**, and uploads the same assets to `releases/analytics-cli/prod//`. A version is immutable once published. 2. **Promote**: run the `Promote Release` workflow with the version. It points `releases/analytics-cli/prod/channel.json` at it and marks it latest on GitHub. Do **not** mark a release latest in the GitHub UI, since that leaves `channel.json` behind. -3. **Backfill**: run `Mirror Releases to S3` to copy releases that predate the S3 lane (or that a failed run left incomplete). Pass space-separated tags or `all`. Re-running it is safe because complete versions are skipped. +3. **Backfill**: run `Mirror Releases to S3` to copy releases that predate the S3 lane (or that a failed run left incomplete). Pass space-separated tags or `all`. Re-running it is safe because complete versions are skipped. Each download is first checked against the sha256 digest GitHub recorded for it; releases from before June 2025 have no digest and are copied unchecked. Layout under `releases/analytics-cli/prod/`: From c6706bfdb2768b468c53beb7a1dd35047f8e9a15 Mon Sep 17 00:00:00 2001 From: Dylan Frankland Date: Mon, 5 Oct 2026 17:43:17 +0000 Subject: [PATCH 3/5] ci: default Release workflow permissions to read-only Fixes checkov CKV2_GHA_1. release_ruby_gem now gets its id-token explicitly instead of from the repository default. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/release.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 248ace27..4d40a999 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,6 +7,9 @@ on: description: Version/tag to release required: true +permissions: + contents: read + jobs: release_cli: name: Trigger CLI Release @@ -24,6 +27,9 @@ jobs: name: Trigger Ruby Gem Release needs: [release_cli] uses: ./.github/workflows/release_ruby_gem.yml + permissions: + contents: read + id-token: write secrets: inherit with: release_tag: ${{ inputs.version }} From f0ae2eb78343e65d9aba7503747584786c8a5556 Mon Sep 17 00:00:00 2001 From: Dylan Frankland Date: Mon, 5 Oct 2026 17:43:18 +0000 Subject: [PATCH 4/5] ci: check the trunk launcher can run a release before promoting it The launcher only installs GitHub's latest release, so run the published launcher with its download URL pinned to the version being promoted and require `trunk flakytests --version` to report it. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/promote_release.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/promote_release.yml b/.github/workflows/promote_release.yml index f58e73fa..b6965607 100644 --- a/.github/workflows/promote_release.yml +++ b/.github/workflows/promote_release.yml @@ -34,6 +34,24 @@ jobs: fi gh release view "${VERSION}" --json tagName,isDraft --jq 'if .isDraft then error("release is a draft") else .tagName end' + # The published trunk launcher only installs GitHub's latest release, which is still the + # previous version, so pin its one download URL to make it fetch and run this one instead. + # TODO: use `FT_VERSION=${VERSION} trunk flakytests --version` once the launcher supports it. + - name: Run ${{ inputs.version }} through the trunk launcher + run: | + dir="$(mktemp -d)" + curl -fsSL https://trunk.io/releases/trunk -o "${dir}/trunk" + sed -i "s|/releases/latest/download/|/releases/download/${VERSION}/|" "${dir}/trunk" + chmod +x "${dir}/trunk" + if ! actual="$(TRUNK_CACHE="${dir}/cache" "${dir}/trunk" flakytests --version | tail -n1 | awk '{print $NF}')"; then + echo "::error::the trunk launcher could not download or run ${VERSION} from GitHub" + exit 1 + fi + if [[ ${actual} != "${VERSION}" ]]; then + echo "::error::the trunk launcher ran ${actual}, not ${VERSION}; has its download URL changed?" + exit 1 + fi + - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: From 352ccd27ecada940a42ee79e9d17d71fd6877a48 Mon Sep 17 00:00:00 2001 From: Dylan Frankland Date: Mon, 5 Oct 2026 18:08:52 +0000 Subject: [PATCH 5/5] Revert "ci: check the trunk launcher can run a release before promoting it" This reverts commit f0ae2eb7. The precondition moves to its own PR, which pins the version with TRUNK_ANALYTICS_CLI_VERSION instead of rewriting the launcher's download URL. Co-Authored-By: Claude Opus 5.5 (1M context) --- .github/workflows/promote_release.yml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/.github/workflows/promote_release.yml b/.github/workflows/promote_release.yml index b6965607..f58e73fa 100644 --- a/.github/workflows/promote_release.yml +++ b/.github/workflows/promote_release.yml @@ -34,24 +34,6 @@ jobs: fi gh release view "${VERSION}" --json tagName,isDraft --jq 'if .isDraft then error("release is a draft") else .tagName end' - # The published trunk launcher only installs GitHub's latest release, which is still the - # previous version, so pin its one download URL to make it fetch and run this one instead. - # TODO: use `FT_VERSION=${VERSION} trunk flakytests --version` once the launcher supports it. - - name: Run ${{ inputs.version }} through the trunk launcher - run: | - dir="$(mktemp -d)" - curl -fsSL https://trunk.io/releases/trunk -o "${dir}/trunk" - sed -i "s|/releases/latest/download/|/releases/download/${VERSION}/|" "${dir}/trunk" - chmod +x "${dir}/trunk" - if ! actual="$(TRUNK_CACHE="${dir}/cache" "${dir}/trunk" flakytests --version | tail -n1 | awk '{print $NF}')"; then - echo "::error::the trunk launcher could not download or run ${VERSION} from GitHub" - exit 1 - fi - if [[ ${actual} != "${VERSION}" ]]; then - echo "::error::the trunk launcher ran ${actual}, not ${VERSION}; has its download URL changed?" - exit 1 - fi - - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: