Skip to content

Commit f4d8ea7

Browse files
committed
feat: install mergify-cli from prebuilt release binaries
Replace the Python + uv install path (`uv tool install mergify-cli`) with a direct download of the prebuilt `mergify` binary from the mergify-cli GitHub release, verified against the release `SHA256SUMS`. Why direct asset download over the official `curl|sh` installer: - Reproducible, pinned CI installs without piping an unpinned `main` HEAD script into a shell. - The official `install.sh` only supports Linux/Darwin (it dies on Windows) and resolves `latest` via `api.github.com`, which is rate-limited on shared CI IPs. We support all five shipped targets and resolve `latest` via a plain github.com redirect (no API, no token). Details: - Map RUNNER_OS/RUNNER_ARCH to the Rust target triple, download the `mergify-<version>-<target>.{tar.gz,zip}` asset, fail-closed checksum verify (exact-match + malformed-line guard, mirroring install.sh), extract, install to $RUNNER_TEMP and add to $GITHUB_PATH. - The `mergify_cli_version` output is read back from `mergify --version` (the binary now reports its real version), accurate for both pinned and `latest`. - Default bumped to 2026.6.16.1: the first release using the canonical versioned asset naming enforced by mergify-cli's release workflow. The transitional 2026.6.15.1 used a non-versioned name and is not pinnable. - Renovate datasource moved pypi -> github-releases (Mergifyio/mergify-cli, pep440). - CI test-install is now a 5-OS matrix, one runner per target triple. BREAKING CHANGE: the `python_version` input is removed (no Python is used) and the action no longer provisions Python/uv on PATH as a side effect. Fixes MRGFY-7660 Change-Id: If70ad03ddf01ec196f051fef6489b68904d02b20
1 parent 2440cd0 commit f4d8ea7

4 files changed

Lines changed: 126 additions & 45 deletions

File tree

.github/workflows/ci.yaml

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,21 +61,39 @@ jobs:
6161
6262
test-install:
6363
timeout-minutes: 5
64-
runs-on: ubuntu-24.04
64+
# One runner per prebuilt target triple, so every asset/extraction path is
65+
# exercised: linux+macos on both x86_64 and aarch64, windows x86_64.
66+
strategy:
67+
fail-fast: false
68+
matrix:
69+
os:
70+
- ubuntu-24.04 # x86_64-unknown-linux-gnu
71+
- ubuntu-24.04-arm # aarch64-unknown-linux-gnu
72+
- macos-15-intel # x86_64-apple-darwin
73+
- macos-15 # aarch64-apple-darwin
74+
- windows-2025 # x86_64-pc-windows-msvc
75+
runs-on: ${{ matrix.os }}
76+
defaults:
77+
run:
78+
shell: bash
6579
steps:
6680
- name: Checkout 🛎️
6781
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
6882

6983
- name: Install pinned mergify-cli
7084
id: pinned
7185
uses: ./
86+
with:
87+
mergify_cli_version: 2026.6.16.1
7288

7389
- name: Assert pinned install
7490
env:
7591
VERSION: ${{ steps.pinned.outputs.mergify_cli_version }}
7692
run: |
77-
test -n "$VERSION"
78-
mergify --version
93+
# Compare against the literal requested version (not the action's own
94+
# readback), so a regression that installs the wrong version fails CI.
95+
test "$VERSION" = "2026.6.16.1"
96+
test "$(mergify --version | awk '{print $NF}')" = "2026.6.16.1"
7997
8098
- name: Install latest mergify-cli
8199
id: latest
@@ -88,7 +106,7 @@ jobs:
88106
VERSION: ${{ steps.latest.outputs.mergify_cli_version }}
89107
run: |
90108
test -n "$VERSION"
91-
mergify --version
109+
test "$(mergify --version | awk '{print $NF}')" = "$VERSION"
92110
93111
all-greens:
94112
if: ${{ !cancelled() }}

README.md

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# setup-cli
22

3-
GitHub Action to install the [Mergify CLI](https://pypi.org/project/mergify-cli/)
3+
GitHub Action to install the [Mergify CLI](https://github.com/Mergifyio/mergify-cli)
44
(`mergify-cli`) with version pinning and Renovate autoupdate.
55

6-
It sets up Python, installs `uv`, then installs `mergify-cli` (pinned by default,
7-
`latest` supported) and exposes the resolved version as an output.
6+
It downloads the prebuilt `mergify` binary from the mergify-cli GitHub release
7+
(pinned by default, `latest` supported), verifies it against the release
8+
`SHA256SUMS`, puts it on `PATH`, and exposes the installed version as an output.
9+
No Python or toolchain is required.
810

911
More information on https://mergify.com
1012

@@ -35,13 +37,12 @@ Pin a specific `mergify-cli` version, or install the latest one:
3537

3638
| Input | Type | Required | Default | Description |
3739
| --- | --- | --- | --- | --- |
38-
| `mergify_cli_version` | string | false | `2026.6.8.1` | Version of mergify-cli to install. Use `latest` to install the latest released version without pinning. |
39-
| `python_version` | string | false | `3.14` | Python version to set up for the install (passed to actions/setup-python). |
40+
| `mergify_cli_version` | string | false | `2026.6.16.1` | Version of mergify-cli to install. Use `latest` to install the latest released version without pinning. |
4041

4142
<!-- AUTO-DOC-INPUT:END -->
4243

4344
## Outputs
4445

4546
| Output | Description |
4647
| --- | --- |
47-
| `mergify_cli_version` | The `mergify-cli` version that was installed. Resolved from the installed package metadata, so it reflects the real version even when `latest` or an empty input was requested. |
48+
| `mergify_cli_version` | The `mergify-cli` version that was installed. Read back from the installed binary, so it reflects the real version even when `latest` or an empty input was requested. |

action.yml

Lines changed: 92 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: Setup Mergify CLI
2-
description: Install the Mergify CLI (mergify-cli) with version pinning.
2+
description: Install the Mergify CLI (mergify-cli) prebuilt binary with version pinning.
33
author: Mergify
44
branding:
55
icon: terminal
@@ -9,48 +9,109 @@ inputs:
99
description: |
1010
Version of mergify-cli to install. Use `latest` to install the latest
1111
released version without pinning.
12-
# renovate: datasource=pypi depName=mergify-cli
13-
default: 2026.6.8.1
14-
python_version:
15-
description: Python version to set up for the install (passed to actions/setup-python).
16-
default: "3.14"
12+
# renovate: datasource=github-releases depName=Mergifyio/mergify-cli versioning=pep440
13+
default: 2026.6.16.1
1714
outputs:
1815
mergify_cli_version:
1916
description: |
20-
The mergify-cli version that was installed. Resolved from the installed
21-
package metadata, so it reflects the real version even when `latest` or an
22-
empty input was requested.
17+
The mergify-cli version that was installed. Read back from the installed
18+
binary, so it reflects the real version even when `latest` or an empty
19+
input was requested.
2320
value: ${{ steps.install.outputs.mergify_cli_version }}
2421
runs:
2522
using: composite
2623
steps:
27-
- name: Setup Python 🔧
28-
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
29-
with:
30-
python-version: ${{ inputs.python_version }}
31-
32-
- name: Install uv
33-
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
34-
with:
35-
# mergify is too small to benefit + there is no version lock (so no cache key either)
36-
enable-cache: false
37-
3824
- name: Install mergify-cli
3925
id: install
4026
shell: bash
4127
env:
4228
MERGIFY_CLI_VERSION: ${{ inputs.mergify_cli_version }}
4329
run: |
44-
if [ -z "$MERGIFY_CLI_VERSION" ] || [ "$MERGIFY_CLI_VERSION" = "latest" ]; then
45-
# --upgrade implies --refresh, so uv re-resolves against PyPI and
46-
# installs the newest release even on persistent self-hosted runners
47-
# where an older mergify-cli is already cached.
48-
uv tool install --upgrade mergify-cli
30+
set -euo pipefail
31+
32+
REPO="Mergifyio/mergify-cli"
33+
34+
die() { echo "::error::$1" >&2; exit 1; }
35+
36+
# Map the runner's OS/arch to the Rust target triple the release
37+
# workflow tags its assets with. RUNNER_OS/RUNNER_ARCH are set by the
38+
# runner and are more reliable than `uname` (notably on the Windows
39+
# Git-bash shell, where `uname -s` reports MINGW64_NT…).
40+
case "${RUNNER_OS}-${RUNNER_ARCH}" in
41+
Linux-X64) target="x86_64-unknown-linux-gnu"; ext="tar.gz"; bin="mergify" ;;
42+
Linux-ARM64) target="aarch64-unknown-linux-gnu"; ext="tar.gz"; bin="mergify" ;;
43+
macOS-X64) target="x86_64-apple-darwin"; ext="tar.gz"; bin="mergify" ;;
44+
macOS-ARM64) target="aarch64-apple-darwin"; ext="tar.gz"; bin="mergify" ;;
45+
Windows-X64) target="x86_64-pc-windows-msvc"; ext="zip"; bin="mergify.exe" ;;
46+
*) die "no prebuilt mergify-cli binary for ${RUNNER_OS}/${RUNNER_ARCH} — see https://github.com/${REPO}/releases" ;;
47+
esac
48+
49+
# Resolve `latest`/empty to a concrete tag. The asset filename embeds
50+
# the version, so we cannot use the `/releases/latest/download/` path:
51+
# we follow the `/releases/latest` redirect (plain github.com, no API
52+
# token, no api.github.com rate limit) and read the tag out of the
53+
# final URL.
54+
if [ -z "${MERGIFY_CLI_VERSION}" ] || [ "${MERGIFY_CLI_VERSION}" = "latest" ]; then
55+
effective=$(curl -fsSL -o /dev/null -w '%{url_effective}' "https://github.com/${REPO}/releases/latest") \
56+
|| die "could not reach github.com to resolve the latest mergify-cli release"
57+
version=${effective##*/tag/}
58+
[ -n "${version}" ] && [ "${version}" != "${effective}" ] || die "could not resolve latest mergify-cli release from ${effective}"
4959
else
50-
uv tool install "mergify-cli==$MERGIFY_CLI_VERSION"
60+
version=${MERGIFY_CLI_VERSION}
61+
fi
62+
63+
asset="mergify-${version}-${target}.${ext}"
64+
base="https://github.com/${REPO}/releases/download/${version}"
65+
66+
tmp=$(mktemp -d)
67+
trap 'rm -rf "${tmp}"' EXIT
68+
69+
echo "Downloading ${base}/${asset}"
70+
curl -fsSL "${base}/${asset}" -o "${tmp}/${asset}" || die "failed to download ${asset} — does ${version} ship a prebuilt binary?"
71+
curl -fsSL "${base}/SHA256SUMS" -o "${tmp}/SHA256SUMS" || die "failed to download SHA256SUMS for ${version}"
72+
73+
# Verify against SHA256SUMS, fail-closed. Pull our line by exact
74+
# second-field match (literal, unlike a `grep` whose `.`s would match
75+
# any char), then assert the canonical `64 hex + two spaces` shape
76+
# before handing it to the checker: GNU `sha256sum -c` only *warns* on
77+
# a malformed entry and still exits 0, which would let a corrupted
78+
# SHA256SUMS slip through.
79+
echo "Verifying checksum"
80+
( cd "${tmp}"
81+
awk -v a="${asset}" '$2==a{print;found=1} END{exit !found}' SHA256SUMS > scoped.sums \
82+
|| die "no checksum entry for ${asset} in SHA256SUMS"
83+
grep -qE '^[0-9a-fA-F]{64} ' scoped.sums || die "malformed checksum entry for ${asset}"
84+
if command -v sha256sum >/dev/null 2>&1; then
85+
sha256sum -c scoped.sums >/dev/null || die "checksum verification failed for ${asset}"
86+
elif command -v shasum >/dev/null 2>&1; then
87+
shasum -a 256 -c scoped.sums >/dev/null || die "checksum verification failed for ${asset}"
88+
else
89+
die "neither sha256sum nor shasum found"
90+
fi
91+
)
92+
93+
case "${ext}" in
94+
tar.gz) tar -xzf "${tmp}/${asset}" -C "${tmp}" ;;
95+
zip) unzip -oq "${tmp}/${asset}" -d "${tmp}" ;;
96+
esac
97+
98+
# On the Windows Git-bash shell RUNNER_TEMP holds a native path with
99+
# backslashes (D:\a\_temp), which bash file ops choke on. Keep a unix
100+
# form for our own mkdir/install/exec and a native form for GITHUB_PATH
101+
# so both bash and pwsh consumer steps resolve `mergify` afterwards.
102+
bindir="${RUNNER_TEMP}/mergify-cli-bin"
103+
bindir_unix="${bindir}"
104+
if command -v cygpath >/dev/null 2>&1; then
105+
bindir_unix=$(cygpath -u "${bindir}")
106+
bindir=$(cygpath -w "${bindir}")
51107
fi
52-
mergify --version
53-
# `mergify --version` may print a placeholder while versioning becomes
54-
# Rust-native, so read the resolved version from the package metadata.
55-
installed=$(uv tool list | awk '/^mergify-cli /{print $2}' | sed 's/^v//')
56-
echo "mergify_cli_version=$installed" >> "$GITHUB_OUTPUT"
108+
mkdir -p "${bindir_unix}"
109+
install -m 0755 "${tmp}/${bin}" "${bindir_unix}/${bin}"
110+
111+
# Expose the binary to later steps and read the version back from it
112+
# (the binary reports its real version) for the action output.
113+
echo "${bindir}" >> "${GITHUB_PATH}"
114+
installed=$("${bindir_unix}/${bin}" --version | awk '{print $NF}')
115+
[ -n "${installed}" ] || die "could not read installed mergify-cli version"
116+
echo "mergify-cli ${installed} installed to ${bindir}/${bin}"
117+
echo "mergify_cli_version=${installed}" >> "${GITHUB_OUTPUT}"

renovate.json

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
"lockFileMaintenance": { "enabled": true },
1212
"packageRules": [
1313
{
14-
"matchDatasources": ["pypi"],
15-
"matchPackageNames": ["mergify-cli"],
14+
"matchDatasources": ["github-releases"],
15+
"matchPackageNames": ["Mergifyio/mergify-cli"],
1616
"minimumReleaseAge": "2 days"
1717
},
1818
{
@@ -40,8 +40,9 @@
4040
"matchStrings": [
4141
"\\| `mergify_cli_version` \\| string \\| \\w+ \\| `(?<currentValue>[^`]+)` \\|"
4242
],
43-
"datasourceTemplate": "pypi",
44-
"depNameTemplate": "mergify-cli"
43+
"datasourceTemplate": "github-releases",
44+
"depNameTemplate": "Mergifyio/mergify-cli",
45+
"versioningTemplate": "pep440"
4546
},
4647
{
4748
"description": "Bump the self-referenced action version pinned in the README usage example on each new setup-cli release.",

0 commit comments

Comments
 (0)