Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
changelog:
exclude:
authors:
- dependabot[bot]
- mergify[bot]
104 changes: 104 additions & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
name: Continuous Integration
permissions: read-all

on:
pull_request:
branches:
- main
- devs/**

concurrency:
# yamllint disable-line rule:line-length
group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
cancel-in-progress: true

jobs:
linters:
timeout-minutes: 5
runs-on: ubuntu-24.04
steps:
- name: Checkout 🛎️
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Setup Python 🔧
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: 3.14.5

- name: Check workflow files
uses: docker://rhysd/actionlint:1.7.12@sha256:b1934ee5f1c509618f2508e6eb47ee0d3520686341fec936f3b79331f9315667
with:
args: -color

- name: Test 🔍
run: |
# nosemgrep: generic.ci.security.use-frozen-lockfile.use-frozen-lockfile-pip
pip install semgrep yamllint
semgrep --config=auto --error
yamllint .

autodoc:
timeout-minutes: 5
runs-on: ubuntu-24.04
steps:
- name: Checkout
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6

- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
enable-cache: false

- name: Regenerate documentation
run: ./generate-doc.sh

- name: Verify Changed files
run: |
if ! git diff --exit-code -- README.md; then
echo "::error::Action documentation is out of date. Run \`./generate-doc.sh\`"
exit 1
fi

test-install:
timeout-minutes: 5
runs-on: ubuntu-24.04
steps:
- name: Checkout 🛎️
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3

- name: Install pinned mergify-cli
id: pinned
uses: ./

- name: Assert pinned install
env:
VERSION: ${{ steps.pinned.outputs.mergify_cli_version }}
run: |
test -n "$VERSION"
mergify --version

- name: Install latest mergify-cli
id: latest
uses: ./
with:
mergify_cli_version: latest

- name: Assert latest install
env:
VERSION: ${{ steps.latest.outputs.mergify_cli_version }}
run: |
test -n "$VERSION"
mergify --version

all-greens:
if: ${{ !cancelled() }}
needs:
- linters
- autodoc
- test-install
runs-on: ubuntu-latest
steps:
- name: Verify all jobs succeeded
uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # release/v1
with:
jobs: ${{ toJSON(needs) }}
27 changes: 27 additions & 0 deletions .yamllint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
extends: default
ignore: |
.git
docs/node_modules
zfixtures
.venv
rules:
document-start: disable
truthy: disable
comments:
level: error
# Renovate pins digests as "@<sha> # v1.2.3" with a single space before the
# comment; allow it instead of yamllint's default of 2.
min-spaces-from-content: 1
# Buggy checks:
# https://github.com/adrienverge/yamllint/issues/375
# https://github.com/adrienverge/yamllint/issues/141
# https://github.com/adrienverge/yamllint/issues/384
comments-indentation: disable
# Renovate pins actions/images to digests, producing lines that exceed any
# sensible width (e.g. docker://...@sha256:<64 hex>). Disable the check so
# renovate can pin freely without per-line opt-outs.
line-length: disable
quoted-strings:
quote-type: double
required: only-when-needed
allow-quoted-quotes: true
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,47 @@
# setup-cli
GitHub Action to install the Mergify CLI (mergify-cli) with version pinning and Renovate autoupdate

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

It sets up Python, installs `uv`, then installs `mergify-cli` (pinned by default,
`latest` supported) and exposes the resolved version as an output.

More information on https://mergify.com

## Usage

Pin the action to a released major (see the [releases](https://github.com/Mergifyio/setup-cli/releases)):

```yaml
- uses: Mergifyio/setup-cli@v1

- run: mergify --version
```

Pin a specific `mergify-cli` version, or install the latest one:

```yaml
- uses: Mergifyio/setup-cli@v1
id: setup-cli
with:
mergify_cli_version: latest

- run: echo "Installed mergify-cli ${{ steps.setup-cli.outputs.mergify_cli_version }}"
```

## Inputs

<!-- AUTO-DOC-INPUT:START - Do not remove or modify this section -->

| Input | Type | Required | Default | Description |
| --- | --- | --- | --- | --- |
| `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. |
| `python_version` | string | false | `3.14` | Python version to set up for the install (passed to actions/setup-python). |

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

## Outputs

| Output | Description |
| --- | --- |
| `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. |
56 changes: 56 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: Setup Mergify CLI
description: Install the Mergify CLI (mergify-cli) with version pinning.
author: Mergify
branding:
icon: terminal
color: blue
inputs:
mergify_cli_version:
description: |
Version of mergify-cli to install. Use `latest` to install the latest
released version without pinning.
# renovate: datasource=pypi depName=mergify-cli
default: 2026.6.8.1
python_version:
description: Python version to set up for the install (passed to actions/setup-python).
default: "3.14"
outputs:
mergify_cli_version:
description: |
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.
value: ${{ steps.install.outputs.mergify_cli_version }}
runs:
using: composite
steps:
- name: Setup Python 🔧
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ inputs.python_version }}

- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
with:
# mergify is too small to benefit + there is no version lock (so no cache key either)
enable-cache: false

- name: Install mergify-cli
id: install
shell: bash
env:
MERGIFY_CLI_VERSION: ${{ inputs.mergify_cli_version }}
run: |
if [ -z "$MERGIFY_CLI_VERSION" ] || [ "$MERGIFY_CLI_VERSION" = "latest" ]; then
# --upgrade implies --refresh, so uv re-resolves against PyPI and
# installs the newest release even on persistent self-hosted runners
# where an older mergify-cli is already cached.
uv tool install --upgrade mergify-cli
else
uv tool install "mergify-cli==$MERGIFY_CLI_VERSION"
fi
mergify --version
# `mergify --version` may print a placeholder while versioning becomes
# Rust-native, so read the resolved version from the package metadata.
installed=$(uv tool list | awk '/^mergify-cli /{print $2}' | sed 's/^v//')
echo "mergify_cli_version=$installed" >> "$GITHUB_OUTPUT"
74 changes: 74 additions & 0 deletions generate-doc.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# /// script
# requires-python = ">=3.11"
# dependencies = ["pyyaml"]
# ///
"""Generate the README Inputs table from action.yml.

Replaces tj-actions/auto-doc: parses the action's inputs and rewrites the
GitHub-flavoured Markdown table between the AUTO-DOC-INPUT markers in README.md.
"""

import pathlib
import re

import yaml

ROOT = pathlib.Path(__file__).parent
ACTION = ROOT / "action.yml"
README = ROOT / "README.md"
START = "<!-- AUTO-DOC-INPUT:START - Do not remove or modify this section -->"
END = "<!-- AUTO-DOC-INPUT:END -->"


def render_description(text: str) -> str:
"""Render an action.yml description as a single Markdown table cell.

Wrapped prose lines are joined with spaces; `*`-prefixed lines (e.g. the
list of actions) become `<br>`-separated bullets so they render as a list
inside the cell rather than a run of literal asterisks.
"""
parts: list[str] = []
for raw in text.strip().splitlines():
line = raw.strip()
if not line:
continue
if line.startswith("* "):
parts.append("<br>• " + line[2:].strip())
elif parts:
parts[-1] += " " + line
else:
parts.append(line)
return "".join(parts)


def render_table(inputs: dict) -> str:
rows = [
"| Input | Type | Required | Default | Description |",
"| --- | --- | --- | --- | --- |",
]
for name in sorted(inputs):
spec = inputs[name] or {}
required = "true" if spec.get("required") else "false"
default = spec.get("default")
default_cell = f"`{default}`" if default not in (None, "") else ""
description = render_description(str(spec.get("description", "")))
rows.append(f"| `{name}` | string | {required} | {default_cell} | {description} |")
return "\n".join(rows)


def main() -> None:
action = yaml.safe_load(ACTION.read_text(encoding="utf-8"))
table = render_table(action.get("inputs") or {})
block = f"{START}\n\n{table}\n\n{END}"

readme = README.read_text(encoding="utf-8")
pattern = re.escape(START) + r".*?" + re.escape(END)
if not re.search(pattern, readme, flags=re.DOTALL):
raise SystemExit("AUTO-DOC-INPUT markers not found in README.md")

new = re.sub(pattern, lambda _: block, readme, flags=re.DOTALL)
README.write_text(new, encoding="utf-8", newline="\n")


if __name__ == "__main__":
main()
7 changes: 7 additions & 0 deletions generate-doc.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

set -euo pipefail

command -v uv >/dev/null 2>&1 || { echo "uv is not installed: https://docs.astral.sh/uv/" >&2; exit 1; }

exec uv run "$(dirname "$0")/generate-doc.py"
60 changes: 60 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:best-practices",
":semanticCommitTypeAll(chore)"
],
"prHourlyLimit": 10,
"rebaseWhen": "conflicted",
"minimumReleaseAge": "7 days",
"osvVulnerabilityAlerts": true,
"lockFileMaintenance": { "enabled": true },
"packageRules": [
{
"matchDatasources": ["pypi"],
"matchPackageNames": ["mergify-cli"],
"minimumReleaseAge": "2 days"
},
{
"description": "Bump the self-referenced action version immediately: it is our own freshly-released major (README usage example + ci.yaml dogfooding), so the third-party stability wait is pointless.",
"matchDepNames": ["Mergifyio/setup-cli"],
"minimumReleaseAge": "0"
}
],
"customManagers": [
{
"customType": "regex",
"managerFilePatterns": [
"/^action\\.yml$/"
],
"matchStrings": [
"# renovate: datasource=(?<datasource>[a-z-]+) depName=(?<depName>[^\\s]+)(?: versioning=(?<versioning>[a-z0-9-]+))?\\s+default:\\s*(?<currentValue>[^\\s]+)"
]
},
{
"description": "Keep the auto-generated README inputs table in sync with the mergify_cli_version default in action.yml, so the bump PR lands both files in one go and the autodoc check passes.",
"customType": "regex",
"managerFilePatterns": [
"/^README\\.md$/"
],
"matchStrings": [
"\\| `mergify_cli_version` \\| string \\| \\w+ \\| `(?<currentValue>[^`]+)` \\|"
],
"datasourceTemplate": "pypi",
"depNameTemplate": "mergify-cli"
},
{
"description": "Bump the self-referenced action version pinned in the README usage example on each new setup-cli release.",
"customType": "regex",
"managerFilePatterns": [
"/^README\\.md$/"
],
"matchStrings": [
"uses: Mergifyio/setup-cli@(?<currentValue>v\\d+)"
],
"datasourceTemplate": "github-tags",
"depNameTemplate": "Mergifyio/setup-cli",
"versioningTemplate": "github-actions"
}
]
}