Skip to content

chore: bump version to v2.9.0 #40

chore: bump version to v2.9.0

chore: bump version to v2.9.0 #40

Workflow file for this run

name: Release
on:
push:
tags:
- 'v*.*.*' # Trigger on version tags like v0.4.1
workflow_dispatch: # Manual build-only validation (no PyPI, no registry push)
permissions:
contents: read
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false # never abort a half-pushed release
env:
IMAGE: ghcr.io/jztan/redmine-mcp-server
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
fail-fast: false
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
- name: Set up environment
run: |
uv sync --locked --extra test
- name: Install pip-audit
run: pip install pip-audit
- name: Audit dependencies for known vulnerabilities
# Ignore list lives in scripts/audit.sh so local preflight and CI stay in sync.
# Gates publish on a clean audit so a transitive vuln cannot ship after tag push.
run: |
uv export --no-hashes --no-emit-project > /tmp/requirements-audit.txt
bash scripts/audit.sh -r /tmp/requirements-audit.txt --strict
- name: Generate test SSL certificates
run: bash tests/fixtures/ssl/generate-test-certs.sh
- name: Run tests with coverage
run: |
uv run pytest --cov=src/redmine_mcp_server --cov-report=xml --cov-report=term -m "not integration" tests/
- name: Upload coverage to Codecov
if: matrix.python-version == '3.13'
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage.xml
fail_ci_if_error: false
verbose: true
build-image:
# No `needs:`, so it runs in parallel with `test` to shorten the release critical
# path (release.py polls the run with a 900s timeout). Atomicity is preserved
# because publish-pypi gates on BOTH test and build-image below.
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Set up QEMU
uses: docker/setup-qemu-action@06116385d9baf250c9f4dcb4858b16962ea869c3 # v4.1.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
- name: Log in to GHCR
if: github.event_name == 'push'
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build multi-arch image (push staging tag only on tag events)
# On a tag push: build amd64+arm64 and push a deterministic sha-<commit>
# staging tag (NOT a version tag). On workflow_dispatch: build only, to
# validate both architectures compile; nothing is pushed.
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
with:
context: .
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name == 'push' }}
tags: ${{ env.IMAGE }}:sha-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
publish-pypi:
needs: [test, build-image]
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Set up Python
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6
with:
python-version: '3.13'
- name: Install build dependencies
run: |
python -m pip install --upgrade pip
pip install build twine
- name: Build package
run: python -m build
- name: Check package
run: twine check dist/*
- name: Publish to PyPI
# --skip-existing makes the step idempotent: a re-run after a partial or
# complete prior upload skips already-published files instead of failing.
env:
TWINE_USERNAME: __token__
TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
run: twine upload --skip-existing dist/*
promote-image:
# build-image is listed explicitly (not just transitively via publish-pypi)
# so the "staging sha-<commit> image must already exist" contract is robust
# to future changes in publish-pypi's needs.
needs: [build-image, publish-pypi]
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Log in to GHCR
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Compute release tags
id: meta
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
with:
images: ${{ env.IMAGE }}
flavor: latest=auto
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
- name: Promote staging image to release tags
# Copy the already-built sha-<commit> manifest list to the version tags.
# No rebuild: imagetools create repackages the existing multi-arch manifest.
# Use metadata-action's explicit `json` output (always produced) rather
# than the auto-exported DOCKER_METADATA_OUTPUT_JSON env var (which only
# exists when DOCKER_METADATA_SET_OUTPUT_ENV is true), and pass it via env
# so the JSON is never interpolated into the shell.
env:
METADATA_JSON: ${{ steps.meta.outputs.json }}
run: |
tags=$(jq -cr '.tags | map("--tag " + .) | join(" ")' <<< "$METADATA_JSON")
# shellcheck disable=SC2086
docker buildx imagetools create $tags "${IMAGE}:sha-${GITHUB_SHA}"