ci(release): add workflow_dispatch to run Phase 2 manually #20
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release (v2) | |
| # Two-phase release flow. | |
| # | |
| # Phase 1 — automatic on git tag push (v*). | |
| # Runs tests, regenerates the OpenAPI spec, builds the wheel/dist, | |
| # and attaches them to a DRAFT GitHub Release. Nothing public yet. | |
| # | |
| # Phase 2 — fires when you click "Publish release" on that draft in | |
| # the GitHub UI (release.published event). | |
| # Publishes the wheel/sdist to PyPI, builds the macOS DMG and | |
| # uploads it to the release, syncs the Homebrew tap, syncs AUR. | |
| on: | |
| push: | |
| tags: [ "v*" ] | |
| release: | |
| types: [ published ] | |
| workflow_dispatch: | |
| inputs: | |
| tag: | |
| description: "Release tag to (re)publish, e.g. v3.0.0. Runs Phase 2 manually." | |
| required: true | |
| permissions: | |
| contents: write | |
| # Serialize all release work for a given version so a manual dispatch can't | |
| # race a release-event run (would double-publish to PyPI / double-commit the tap). | |
| concurrency: | |
| group: release-${{ github.event.inputs.tag || github.ref_name }} | |
| cancel-in-progress: false | |
| env: | |
| # release event → github.ref_name is the tag; workflow_dispatch → the input. | |
| VERSION: ${{ github.event.inputs.tag || github.ref_name }} | |
| jobs: | |
| # ─────────────────────────────────────────────────────────────────── | |
| # PHASE 1 — automatic on tag push | |
| # ─────────────────────────────────────────────────────────────────── | |
| test: | |
| if: github.event_name == 'push' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| - run: uv python install 3.14 | |
| - run: uv sync --extra dev --extra tui | |
| - run: uv run pytest -x -q -m "not gui" | |
| stage: | |
| if: github.event_name == 'push' | |
| needs: [ test ] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| - run: uv python install 3.14 | |
| - name: Install dependencies for OpenAPI generation | |
| run: uv sync --extra dev | |
| - name: Regenerate OpenAPI spec | |
| # Always regenerate at tag time so the artifact matches the | |
| # released code, even if the committed docs/openapi.yaml drifted. | |
| run: uv run python tools/gen_openapi.py | |
| - run: uv build | |
| - name: Create draft GitHub Release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| draft: true | |
| files: | | |
| dist/* | |
| docs/openapi.yaml | |
| generate_release_notes: true | |
| # ─────────────────────────────────────────────────────────────────── | |
| # PHASE 2 — fires when you click "Publish release" on the draft | |
| # ─────────────────────────────────────────────────────────────────── | |
| publish-pypi: | |
| if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-latest | |
| # Trusted-publisher OIDC: short-lived token minted per run, no secret. | |
| permissions: | |
| contents: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref_name }} | |
| - uses: astral-sh/setup-uv@v8.2.0 | |
| with: | |
| enable-cache: true | |
| - run: uv python install 3.14 | |
| - name: Install dependencies for OpenAPI generation | |
| run: uv sync --extra dev | |
| - name: Regenerate OpenAPI spec | |
| # Rebuilding fresh in Phase 2 is faster than wiring up an | |
| # artifact handoff from Phase 1's run. | |
| run: uv run python tools/gen_openapi.py | |
| - run: uv build | |
| - run: uv publish | |
| build-dmg: | |
| if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' | |
| runs-on: macos-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref_name }} | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.14" | |
| - name: Install deps | |
| run: pip install pyinstaller rumps pyobjc && pip install -e ".[tray-mac]" | |
| - name: Build SusOps.app | |
| run: pyinstaller packaging/macos/susops.spec --clean --noconfirm | |
| - name: Create dmg | |
| run: | | |
| VER="${VERSION#v}" | |
| hdiutil create -volname "SusOps" -srcfolder dist/SusOps.app -ov -format UDZO "SusOps-${VER}-arm64.dmg" | |
| - name: Upload dmg to release | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| run: | | |
| VER="${VERSION#v}" | |
| gh release upload "$VERSION" "SusOps-${VER}-arm64.dmg" --clobber | |
| - uses: actions/upload-artifact@v7 | |
| with: | |
| name: dmg | |
| path: "SusOps-*-arm64.dmg" | |
| retention-days: 1 | |
| update-tap: | |
| if: github.event_name == 'release' || github.event_name == 'workflow_dispatch' | |
| needs: [ publish-pypi, build-dmg ] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.event.inputs.tag || github.ref_name }} | |
| path: main | |
| - uses: actions/checkout@v6 | |
| with: | |
| repository: mashb1t/homebrew-susops | |
| token: ${{ secrets.HOMEBREW_TAP_TOKEN }} | |
| path: tap | |
| - uses: actions/setup-python@v6 | |
| with: | |
| python-version: "3.14" | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: dmg | |
| path: main | |
| - name: Update formula and cask sha256s | |
| working-directory: main | |
| run: | | |
| VER="${VERSION#v}" | |
| # Install [tui] so compute_resource_shas.py sees the full | |
| # runtime closure and writes accurate sha256s. | |
| pip install -e ".[tui]" | |
| python scripts/update_homebrew_sha.py "${VER}" --dmg "SusOps-${VER}-arm64.dmg" | |
| - name: Copy to tap | |
| run: | | |
| cp main/packaging/homebrew/Formula/susops.rb tap/Formula/susops.rb | |
| cp main/packaging/homebrew/Casks/susops.rb tap/Casks/susops.rb | |
| - name: Commit and push | |
| working-directory: tap | |
| run: | | |
| git config user.name "github-actions[bot]" | |
| git config user.email "github-actions[bot]@users.noreply.github.com" | |
| git add Formula/susops.rb Casks/susops.rb | |
| git commit -m "chore: bump to ${VERSION}" | |
| git push | |
| # update-aur: | |
| # if: github.event_name == 'release' | |
| # needs: [publish-pypi] | |
| # runs-on: ubuntu-latest | |
| # steps: | |
| # - uses: actions/checkout@v6 | |
| # - uses: actions/setup-python@v6 | |
| # with: | |
| # python-version: "3.14" | |
| # - name: Update PKGBUILD | |
| # run: | | |
| # VER="${VERSION#v}" | |
| # python scripts/update_aur_pkgver.py "${VER}" | |
| # - name: Generate .SRCINFO via Arch Docker | |
| # run: | | |
| # docker run --rm \ | |
| # -v "${{ github.workspace }}/packaging/aur:/pkg" \ | |
| # archlinux:base-devel \ | |
| # sh -c "useradd -m builder && chown builder /pkg && su builder -c 'cd /pkg && makepkg --printsrcinfo > .SRCINFO'" | |
| # - uses: webfactory/ssh-agent@v0.10.0 | |
| # with: | |
| # ssh-private-key: ${{ secrets.AUR_SSH_KEY }} | |
| # - name: Add AUR host key | |
| # run: ssh-keyscan -t ed25519 aur.archlinux.org >> ~/.ssh/known_hosts | |
| # - name: Clone and push AUR repo | |
| # run: | | |
| # git clone ssh://aur@aur.archlinux.org/susops.git aur-repo | |
| # cp packaging/aur/PKGBUILD aur-repo/PKGBUILD | |
| # cp packaging/aur/.SRCINFO aur-repo/.SRCINFO | |
| # cd aur-repo | |
| # git config user.name "github-actions[bot]" | |
| # git config user.email "github-actions[bot]@users.noreply.github.com" | |
| # git add PKGBUILD .SRCINFO | |
| # git commit -m "chore: bump to ${VERSION}" | |
| # git push |