From a0a12d5ecabcd9d7d662e67c94a92c485ffdb2f6 Mon Sep 17 00:00:00 2001 From: Yakhyokhuja Valikhujaev Date: Tue, 28 Apr 2026 00:22:12 +0900 Subject: [PATCH] fix: Fix pypi publish re-run issue (#113) --- .github/workflows/ci.yml | 12 +-- .github/workflows/docs.yml | 6 +- .github/workflows/pipeline.yml | 20 ++--- .github/workflows/publish.yml | 130 --------------------------------- .github/workflows/release.yml | 84 --------------------- CONTRIBUTING.md | 18 ++--- docs/contributing.md | 2 +- 7 files changed, 26 insertions(+), 246 deletions(-) delete mode 100644 .github/workflows/publish.yml delete mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 026b040..9753ba0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,8 +17,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 5 steps: - - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - uses: actions/checkout@v5 + - uses: actions/setup-python@v6 with: python-version: "3.11" - uses: pre-commit/action@v3.0.1 @@ -50,10 +50,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: "pip" @@ -80,10 +80,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" cache: "pip" diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 096d963..bbfca1b 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -1,8 +1,6 @@ name: Deploy Documentation on: - release: - types: [released] workflow_dispatch: permissions: @@ -12,11 +10,11 @@ jobs: deploy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: fetch-depth: 0 - - uses: actions/setup-python@v5 + - uses: actions/setup-python@v6 with: python-version: "3.11" diff --git a/.github/workflows/pipeline.yml b/.github/workflows/pipeline.yml index ee510ed..e61981d 100644 --- a/.github/workflows/pipeline.yml +++ b/.github/workflows/pipeline.yml @@ -20,10 +20,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" @@ -66,10 +66,10 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: 'pip' @@ -91,13 +91,13 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN }} - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" @@ -148,12 +148,12 @@ jobs: steps: - name: Checkout tag - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: v${{ inputs.version }} - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" cache: 'pip' @@ -193,13 +193,13 @@ jobs: steps: - name: Checkout tag - uses: actions/checkout@v4 + uses: actions/checkout@v5 with: ref: v${{ inputs.version }} fetch-depth: 0 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@v6 with: python-version: "3.11" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml deleted file mode 100644 index 312c0e1..0000000 --- a/.github/workflows/publish.yml +++ /dev/null @@ -1,130 +0,0 @@ -name: Publish to PyPI - -on: - push: - tags: - - "v*.*.*" - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - validate: - runs-on: ubuntu-latest - timeout-minutes: 5 - outputs: - version: ${{ steps.get_version.outputs.version }} - tag_version: ${{ steps.get_version.outputs.tag_version }} - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" # Needs 3.11+ for tomllib - - - name: Get version from tag and pyproject.toml - id: get_version - run: | - TAG_VERSION=${GITHUB_REF#refs/tags/v} - echo "tag_version=$TAG_VERSION" >> $GITHUB_OUTPUT - - PYPROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") - echo "version=$PYPROJECT_VERSION" >> $GITHUB_OUTPUT - - echo "Tag version: v$TAG_VERSION" - echo "pyproject.toml version: $PYPROJECT_VERSION" - - - name: Verify version match - run: | - if [ "${{ steps.get_version.outputs.tag_version }}" != "${{ steps.get_version.outputs.version }}" ]; then - echo "Error: Tag version (${{ steps.get_version.outputs.tag_version }}) does not match pyproject.toml version (${{ steps.get_version.outputs.version }})" - exit 1 - fi - echo "Version validation passed: ${{ steps.get_version.outputs.version }}" - - test: - runs-on: ubuntu-latest - timeout-minutes: 15 - needs: validate - - strategy: - fail-fast: false - matrix: - python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v5 - with: - python-version: ${{ matrix.python-version }} - cache: 'pip' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - python -m pip install ".[cpu,dev]" - - - name: Run tests - run: pytest -v - - publish: - runs-on: ubuntu-latest - timeout-minutes: 10 - needs: [validate, test] - permissions: - contents: write - id-token: write - environment: - name: pypi - url: https://pypi.org/project/uniface/ - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: 'pip' - - - name: Install build tools - run: | - python -m pip install --upgrade pip - python -m pip install build twine - - - name: Build package - run: python -m build - - - name: Check package - run: twine check dist/* - - - name: Publish to PyPI - env: - TWINE_USERNAME: __token__ - TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} - run: twine upload dist/* - - - name: Detect pre-release - id: prerelease - run: | - if [[ "${{ needs.validate.outputs.version }}" =~ (a|b|rc|\.dev)[0-9]+ ]]; then - echo "is_prerelease=true" >> $GITHUB_OUTPUT - else - echo "is_prerelease=false" >> $GITHUB_OUTPUT - fi - - - name: Create GitHub Release - uses: softprops/action-gh-release@v1 - with: - token: ${{ secrets.RELEASE_TOKEN }} - files: dist/* - generate_release_notes: true - prerelease: ${{ steps.prerelease.outputs.is_prerelease }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index ef15b21..0000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,84 +0,0 @@ -name: Release - -on: - workflow_dispatch: - inputs: - version: - description: 'Version (e.g. 3.6.0, 3.6.0b1, 3.6.0rc1)' - required: true - -concurrency: - group: release - cancel-in-progress: false - -jobs: - release: - runs-on: ubuntu-latest - timeout-minutes: 5 - permissions: - contents: write - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 - token: ${{ secrets.RELEASE_TOKEN }} - - - name: Set up Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Validate version (PEP 440) - run: | - python - <<'EOF' - import re, sys - v = "${{ inputs.version }}" - # PEP 440 subset: X.Y.Z, optional aN / bN / rcN / .devN - if not re.fullmatch(r'\d+\.\d+\.\d+((a|b|rc)\d+|\.dev\d+)?', v): - print(f"Invalid version: {v}") - print("Expected forms: 3.6.0, 3.6.0a1, 3.6.0b1, 3.6.0rc1, 3.6.0.dev1") - sys.exit(1) - EOF - - - name: Check tag does not exist - run: | - if git rev-parse "v${{ inputs.version }}" >/dev/null 2>&1; then - echo "Tag v${{ inputs.version }} already exists." - exit 1 - fi - - - name: Update pyproject.toml - run: | - python - <<'EOF' - import re, pathlib - p = pathlib.Path('pyproject.toml') - text = p.read_text() - new = re.sub(r'^version\s*=\s*".*"', f'version = "${{ inputs.version }}"', text, count=1, flags=re.M) - if new == text: - raise SystemExit("Failed to update version in pyproject.toml") - p.write_text(new) - EOF - - - name: Update uniface/__init__.py - run: | - python - <<'EOF' - import re, pathlib - p = pathlib.Path('uniface/__init__.py') - text = p.read_text() - new = re.sub(r"^__version__\s*=\s*'.*'", f"__version__ = '${{ inputs.version }}'", text, count=1, flags=re.M) - if new == text: - raise SystemExit("Failed to update __version__ in uniface/__init__.py") - p.write_text(new) - EOF - - - name: Commit, tag, push - run: | - git config user.name "github-actions[bot]" - git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add pyproject.toml uniface/__init__.py - git commit -m "chore: Release v${{ inputs.version }}" - git tag "v${{ inputs.version }}" - git push origin HEAD:${{ github.ref_name }} - git push origin "v${{ inputs.version }}" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 51cb056..af04064 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -194,7 +194,7 @@ Releases are fully automated via GitHub Actions. Only maintainers with branch-pr ### Cutting a release -1. Go to **Actions → Release → Run workflow** on GitHub. +1. Go to **Actions → Release Pipeline → Run workflow** on GitHub. 2. Enter the version following [PEP 440](https://peps.python.org/pep-0440/): - Stable: `0.7.0`, `1.0.0` - Pre-release: `0.7.0rc1`, `0.7.0b1`, `0.7.0a1`, `0.7.0.dev1` @@ -202,17 +202,13 @@ Releases are fully automated via GitHub Actions. Only maintainers with branch-pr ### What happens automatically -The `Release` workflow: +The `Release Pipeline` workflow runs all stages in sequence: -1. Validates the version string. -2. Updates `pyproject.toml` and `uniface/__init__.py`. -3. Commits `chore: Release vX.Y.Z` to `main`. -4. Creates and pushes tag `vX.Y.Z`. - -Pushing the tag then triggers: - -- **Publish to PyPI** — builds the package, runs tests on Python 3.10–3.14, uploads to PyPI, and creates a GitHub Release (flagged as pre-release for `a`/`b`/`rc`/`.dev` versions). -- **Deploy docs** — fires only after a **stable** GitHub Release is published. Pre-releases do not update the live documentation site. +1. **Validate** — checks the version string against PEP 440 and confirms the tag does not already exist. +2. **Test** — runs the test suite on Python 3.10–3.14. +3. **Release** — updates `pyproject.toml` and `uniface/__init__.py`, commits `chore: Release vX.Y.Z` to `main`, creates and pushes tag `vX.Y.Z`. +4. **Publish** — builds the package, uploads to PyPI, and creates a GitHub Release (flagged as pre-release for `a`/`b`/`rc`/`.dev` versions). +5. **Deploy docs** — runs only for **stable** versions. Pre-releases do not update the live documentation site. ### Verifying a release diff --git a/docs/contributing.md b/docs/contributing.md index a188fe7..a1f62e4 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -101,7 +101,7 @@ docs: Update installation instructions ## Releases -Releases are automated via GitHub Actions. Maintainers trigger **Actions → Release → Run workflow** with a [PEP 440](https://peps.python.org/pep-0440/) version (e.g. `0.7.0`, `0.7.0rc1`). The workflow bumps `pyproject.toml` + `uniface/__init__.py`, tags the commit, and publishes to PyPI. Docs redeploy only for stable releases. +Releases are automated via GitHub Actions. Maintainers trigger **Actions → Release Pipeline → Run workflow** with a [PEP 440](https://peps.python.org/pep-0440/) version (e.g. `0.7.0`, `0.7.0rc1`). The pipeline runs tests, bumps `pyproject.toml` + `uniface/__init__.py`, tags the commit, publishes to PyPI, and creates a GitHub Release. Docs redeploy only for stable releases. See [CONTRIBUTING.md](https://github.com/yakhyo/uniface/blob/main/CONTRIBUTING.md#release-process) for the full process.