ci / gitleaks (push) Successful in 9s
ci / docs-check (push) Successful in 32s
ci / lint (push) Successful in 42s
ci / test-mcp (push) Successful in 1m20s
ci / security (push) Successful in 1m34s
ci / test-integration (push) Successful in 2m38s
ci / test-unit (push) Successful in 3m48s
ci / build-and-push (push) Successful in 35s
ci / gitleaks (pull_request) Successful in 7s
ci / lint (pull_request) Successful in 37s
ci / test-unit (pull_request) Successful in 3m39s
ci / test-mcp (pull_request) Successful in 1m5s
ci / docs-check (pull_request) Successful in 27s
ci / security (pull_request) Successful in 1m31s
ci / test-integration (pull_request) Successful in 2m47s
ci / build-and-push (pull_request) Successful in 29s
216 lines
8.5 KiB
YAML
216 lines
8.5 KiB
YAML
name: publish
|
|
|
|
# Build the Python package with uv and publish it to the self-hosted Gitea PyPI
|
|
# registry on merge. dev -> a dev package (aegis-gitea-mcp-dev, .devN versions);
|
|
# main -> the stable package (aegis-gitea-mcp). Gated on lint, tests and the security
|
|
# checks so a release can never ship red. Reuses the REGISTRY_TOKEN package secret
|
|
# (same one ci.yml uses); if it is absent the job fails loudly instead of publishing
|
|
# anonymously.
|
|
on:
|
|
push:
|
|
branches:
|
|
- dev
|
|
- main
|
|
|
|
jobs:
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Lint (same gate as ci.yml, via uv and the lockfile).
|
|
# ---------------------------------------------------------------------------
|
|
lint:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up Python
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
- name: Set up uv
|
|
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
|
with:
|
|
version: "0.12.5"
|
|
enable-cache: false
|
|
- name: Install dependencies
|
|
run: uv sync --locked
|
|
- name: Run lint
|
|
run: |
|
|
uv run ruff check .
|
|
uv run ruff format --check .
|
|
uv run mypy src
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Test: unit tests with the coverage gate from pyproject.toml.
|
|
# ---------------------------------------------------------------------------
|
|
test:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
with:
|
|
persist-credentials: false
|
|
- name: Set up Python
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
- name: Set up uv
|
|
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
|
with:
|
|
version: "0.12.5"
|
|
enable-cache: false
|
|
- name: Install dependencies
|
|
run: uv sync --locked
|
|
- name: Run tests
|
|
run: uv run pytest
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Security: the same gates as ci.yml (secret scan over the history, security
|
|
# tests, dependency audit, bandit rules). Nothing is published without them.
|
|
# ---------------------------------------------------------------------------
|
|
security:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
with:
|
|
fetch-depth: 0
|
|
persist-credentials: false
|
|
- name: Set up Python
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
- name: Set up uv
|
|
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
|
with:
|
|
version: "0.12.5"
|
|
enable-cache: false
|
|
- name: Install dependencies
|
|
run: uv sync --locked
|
|
- name: Install gitleaks
|
|
shell: bash
|
|
run: |
|
|
bin_dir="${RUNNER_TEMP:-/tmp}/gitleaks-bin"
|
|
bash scripts/ci/install_gitleaks.sh "${bin_dir}"
|
|
echo "${bin_dir}" >> "$GITHUB_PATH"
|
|
- name: Scan history
|
|
shell: bash
|
|
run: gitleaks git --config .gitleaks.toml --redact --no-banner --verbose --exit-code 1 .
|
|
- name: Security tests
|
|
run: uv run pytest tests/security --no-cov
|
|
- name: Dependency audit
|
|
run: bash scripts/ci/pip_audit.sh
|
|
- name: Ruff security rules
|
|
run: uv run ruff check --select S .
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Build with uv and publish to the Gitea PyPI registry.
|
|
#
|
|
# dev -> aegis-gitea-mcp-dev X.Y.Z.dev<run_number> (always unique)
|
|
# main -> aegis-gitea-mcp X.Y.Z (no-op if already there)
|
|
#
|
|
# The package name + version are patched into pyproject.toml at build time
|
|
# only, never committed. The committed file keeps name "aegis-gitea-mcp"
|
|
# and version "X.Y.Z".
|
|
# ---------------------------------------------------------------------------
|
|
publish:
|
|
needs: [lint, test, security]
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up Python
|
|
uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5.6.0
|
|
with:
|
|
python-version: "3.12"
|
|
|
|
- name: Set up uv
|
|
uses: astral-sh/setup-uv@d4b2f3b6ecc6e67c4457f6d3e41ec42d3d0fcb86 # v5.4.2
|
|
with:
|
|
version: "0.12.5"
|
|
enable-cache: false
|
|
|
|
- name: Require publish credentials
|
|
shell: bash
|
|
env:
|
|
REGISTRY_TOKEN: ${{ secrets.REGISTRY_TOKEN }}
|
|
run: |
|
|
if [ -z "${REGISTRY_TOKEN}" ]; then
|
|
echo "::error::REGISTRY_TOKEN secret is not set. Configure a PAT with write:package." >&2
|
|
exit 1
|
|
fi
|
|
|
|
- name: Compute channel (package name + version)
|
|
id: chan
|
|
shell: bash
|
|
run: |
|
|
BASE="$(grep -E '^version = ' pyproject.toml | head -1 | sed -E 's/^version = "([^"]+)".*/\1/')"
|
|
if [ "${GITHUB_REF_NAME}" = "main" ]; then
|
|
PKG_NAME="aegis-gitea-mcp"
|
|
PKG_VERSION="${BASE}"
|
|
CHANNEL="stable"
|
|
else
|
|
PKG_NAME="aegis-gitea-mcp-dev"
|
|
# Drop an existing .devN suffix (the committed version may be a
|
|
# development version) so the result has exactly one dev segment.
|
|
PKG_VERSION="${BASE%%.dev*}.dev${GITHUB_RUN_NUMBER}"
|
|
CHANNEL="dev"
|
|
fi
|
|
echo "pkg_name=${PKG_NAME}" >> "$GITHUB_OUTPUT"
|
|
echo "pkg_version=${PKG_VERSION}" >> "$GITHUB_OUTPUT"
|
|
echo "channel=${CHANNEL}" >> "$GITHUB_OUTPUT"
|
|
echo "Publishing ${PKG_NAME} ${PKG_VERSION} (${CHANNEL})"
|
|
|
|
- name: Patch package name + version (build only, not committed)
|
|
shell: bash
|
|
run: |
|
|
sed -i -E "s/^name = \".*\"/name = \"${{ steps.chan.outputs.pkg_name }}\"/" pyproject.toml
|
|
sed -i -E "s/^version = \".*\"/version = \"${{ steps.chan.outputs.pkg_version }}\"/" pyproject.toml
|
|
echo "--- patched [project] header ---"
|
|
grep -E '^(name|version) = ' pyproject.toml
|
|
|
|
- name: Build sdist + wheel
|
|
shell: bash
|
|
run: uv build
|
|
|
|
- name: Upload build artifacts
|
|
# Best-effort: some Gitea act_runner versions don't fully support the
|
|
# v4 artifact backend. The real deliverable is published to the registry
|
|
# below, so a failed artifact upload must not fail the release.
|
|
continue-on-error: true
|
|
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
|
with:
|
|
name: dist-${{ steps.chan.outputs.channel }}
|
|
path: dist/*
|
|
|
|
- name: Publish to Gitea PyPI registry
|
|
shell: bash
|
|
env:
|
|
# Reuse the existing package secret (same one ci.yml uses). The
|
|
# token authenticates as its owning Gitea user, so the actor is the
|
|
# username and the token is the password. uv reads the credentials from these variables, so the token never
|
|
# appears on a command line (process listings, shell traces).
|
|
UV_PUBLISH_USERNAME: ${{ github.actor }}
|
|
UV_PUBLISH_PASSWORD: ${{ secrets.REGISTRY_TOKEN }}
|
|
run: |
|
|
# --check-url makes uv skip files already in the registry, so a main
|
|
# push that did not bump the version is a clean no-op instead of a 409.
|
|
uv publish \
|
|
--publish-url https://git.hiddenden.cafe/api/packages/Hiddenden/pypi \
|
|
--check-url https://git.hiddenden.cafe/api/packages/Hiddenden/pypi/simple/
|
|
|
|
# Optional second step to also publish to public PyPI lives behind its own
|
|
# secret. Intentionally left as a disabled stub: this pass does NOT push
|
|
# to public PyPI.
|
|
#
|
|
# - name: Publish to public PyPI
|
|
# if: ${{ secrets.PYPI_TOKEN != '' }}
|
|
# shell: bash
|
|
# env:
|
|
# UV_PUBLISH_USERNAME: __token__
|
|
# UV_PUBLISH_PASSWORD: ${{ secrets.PYPI_TOKEN }}
|
|
# run: uv publish
|