name: CI # Lint + test on every PR and push to main/dev. # On a push to main/dev (i.e. a merge), build the Python package and publish it # to the Gitea PyPI registry — only if lint and tests pass first. on: pull_request: branches: [main, dev] push: branches: [main, dev] jobs: lint: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: Install dependencies run: uv sync --group dev - name: Ruff (lint + format check) run: | uv run ruff check . uv run ruff format --check . test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: Install dependencies run: uv sync --group dev - name: Run tests with coverage (fails under 80%) run: uv run pytest publish: # Only publish on a real merge into main or dev, never on PRs. if: github.event_name == 'push' && (github.ref == 'refs/heads/main' || github.ref == 'refs/heads/dev') needs: [lint, test] runs-on: ubuntu-latest permissions: contents: read # needed for checkout of the private repo packages: write steps: - uses: actions/checkout@v4 - uses: astral-sh/setup-uv@v5 with: enable-cache: true - name: Compute version # main publishes the clean version from pyproject.toml. # dev publishes a unique pre-release (X.Y.Z.devN) so merges never collide. run: | BASE=$(uv version --short) if [ "${{ github.ref_name }}" = "dev" ]; then VERSION="${BASE}.dev${{ github.run_number }}" uv version "$VERSION" --no-sync else VERSION="$BASE" fi echo "Publishing version $VERSION" echo "VERSION=$VERSION" >> "$GITHUB_ENV" - name: Build sdist + wheel run: uv build - name: Publish to Gitea PyPI registry env: # The auto-provided token works for the same owner's package registry. # Override with a PACKAGE_REGISTRY_TOKEN secret if your instance needs it. TWINE_USERNAME: ${{ github.actor }} TWINE_PASSWORD: ${{ secrets.PACKAGE_REGISTRY_TOKEN || secrets.GITHUB_TOKEN }} run: | # Gitea's PyPI registry does not support twine's --skip-existing, so # we run a plain upload and tolerate an "already exists" conflict in # the shell (dev versions are unique per run, so this only bites a # main re-run of an already-published release). set +e out=$(uvx twine upload \ --repository-url "${{ github.server_url }}/api/packages/${{ github.repository_owner }}/pypi" \ --non-interactive \ --verbose \ dist/* 2>&1) code=$? echo "$out" if [ $code -ne 0 ] && echo "$out" | grep -qiE "already exist|conflict|409"; then echo "::notice::Version already published — treating as success." exit 0 fi exit $code