CI patterns
Three GitHub Actions workflows cover the common plugin lifecycle: PR validation, release publishing, and a periodic SDK-compatibility check.
Build on PR
Validate every PR against a Python matrix and the latest SDK.
# .github/workflows/ci.yml
name: CI
on:
pull_request:
push:
branches: [main]
jobs:
python:
runs-on: ubuntu-latest
strategy:
matrix:
python: ['3.12', '3.13']
steps:
- uses: actions/checkout@v4
- name: Install uv
uses: astral-sh/setup-uv@v3
- name: Set up Python
run: uv python install ${{ matrix.python }}
- name: Install dependencies
run: uv sync --all-extras
- name: Run tests
run: uv run pytest -v --tb=short
- name: Validate plugin
run: uv run mint doctor
frontend:
runs-on: ubuntu-latest
if: hashFiles('frontend/package.json') != ''
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install
run: cd frontend && bun install --frozen-lockfile
- name: Type check
run: cd frontend && bun run typecheck
- name: Build
run: cd frontend && bun run build
build-bundle:
runs-on: ubuntu-latest
needs: [python, frontend]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install dependencies
run: |
uv sync
[ -f frontend/package.json ] && cd frontend && bun install --frozen-lockfile
- name: Build bundle to a temp dir
run: uv run mint build --output-dir _ci_buildKey choices:
--frozen-lockfilefor bothuv syncandbun install— fails the build if the lockfile drifted- Matrix on Python 3.12 and 3.13 — match the platform's supported range
mint doctorruns in CI — catches structural mistakes (missing entry point, malformed migrations) before merge- Build into
_ci_build/— exercises the full pipeline; the artifact is discarded after the run
Publish on tag
Tag a release, the workflow builds the bundle and pushes to PyPI + GitHub Releases.
# .github/workflows/release.yml
name: Release
on:
push:
tags: ['v*']
permissions:
contents: write # for creating releases
id-token: write # for PyPI trusted publishing
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # for hatch-vcs
- uses: astral-sh/setup-uv@v3
- uses: oven-sh/setup-bun@v2
with:
bun-version: latest
- name: Install
run: |
uv sync
[ -f frontend/package.json ] && cd frontend && bun install --frozen-lockfile
- name: Build wheel
run: uv run python -m build --wheel
- name: Build bundle
run: uv run mint build
- name: Publish wheel to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
# No password — uses Trusted Publishing via OIDC
with:
packages-dir: dist/
- name: Compute checksum
run: |
cd dist
sha256sum *.mint > my-plugin.sha256
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
files: |
dist/*.mint
dist/my-plugin.sha256
generate_release_notes: trueSetup:
- Configure PyPI Trusted Publishing for your repo so no
PYPI_TOKENis required - For older PyPI accounts without trusted publishing: store
PYPI_TOKENas a repo secret and reference it in the publish step'spasswordinput via the standard GitHub Actionssecretsexpression syntax
Submit to a registry on release
Extend the release workflow to PR a registry update:
submit-to-registry:
runs-on: ubuntu-latest
needs: build-and-publish
if: ${{ !contains(github.ref_name, '-') }} # stable releases only
steps:
- uses: actions/checkout@v4
with:
repository: MorscherLab/mld-registry
token: ${{ secrets.REGISTRY_PR_TOKEN }}
- name: Update index.json
run: |
# Add a new version entry; details depend on the registry's update tooling
- name: Open PR
uses: peter-evans/create-pull-request@v6
with:
commit-message: "Add my-plugin ${{ github.ref_name }}"
branch: update-my-plugin-${{ github.ref_name }}
title: "Add my-plugin ${{ github.ref_name }}"Skip the registry update for pre-release tags (v1.0.0-beta.1) — pre-releases stay on the GitHub Release URL only.
SDK-compatibility check
Daily (or weekly) verify that your plugin still builds against the latest stable mint-sdk:
# .github/workflows/sdk-compat.yml
name: SDK compatibility
on:
schedule:
- cron: '0 6 * * 1' # Mondays 06:00 UTC
workflow_dispatch:
jobs:
test-against-latest-sdk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v3
- name: Force latest mint-sdk
run: |
uv add 'mint-sdk@latest'
uv sync
- name: Run tests
run: uv run pytest -v
- name: mint doctor
run: uv run mint doctor
- name: Open issue on failure
if: failure()
uses: peter-evans/create-issue-from-file@v5
with:
title: 'SDK compatibility broken (week of ${{ github.run_id }})'
content-filepath: ${{ github.event.repository.html_url }}/actions/runs/${{ github.run_id }}This catches breaking SDK changes early, before users hit them.
Caching
Both uv and bun caches significantly speed up CI:
- uses: actions/cache@v4
with:
path: |
~/.cache/uv
~/.bun/install/cache
key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock', '**/bun.lock') }}
restore-keys: |
${{ runner.os }}-uv-Aim for sub-minute CI on PRs. Slow CI is the most common reason teams stop running it.
Pre-commit hooks
For local pre-commit checks (run locally; CI is the authority):
# .pre-commit-config.yaml
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.5.0
hooks:
- id: ruff
- id: ruff-format
- repo: local
hooks:
- id: mint-doctor
name: mint doctor
entry: uv run mint doctor
language: system
pass_filenames: false
types_or: [python, toml]pre-commit install once per clone. Teams can opt in or out per developer; CI is the source of truth.
Notes
- Don't gate CI on the SDK's beta channel by default — beta builds may break and shouldn't fail your PRs. The compatibility-check workflow above is the right place to test against beta.
- For plugins that ship to a private registry, the registry's webhook can re-trigger your release workflow when the SDK ships a new patch — opt-in to that for tight coupling.
- Keep secrets out of forks: gate publish jobs on
if: github.repository_owner == '<your-org>'.
Related
- Packaging — what
mint buildproduces - Publishing — where to push the artifact
- Versioning — what
--premeans and when to bump majors