From 283f1a55ebb91214de2816a1f9c8ac5612dc0ef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristi=C3=A1n=20Partl?= Date: Tue, 2 Jun 2026 13:07:47 +0200 Subject: [PATCH 1/2] ci: add release workflow triggered on version tags Push a v* tag to build the Windows onedir bundle with PyInstaller and publish a GitHub Release. Runs the test suite as a gate first, zips dist\Screamer with a SHA256 checksum, and uses the gh CLI to create the release with auto-generated notes. Hyphenated tags publish as pre-releases. --- .github/workflows/release.yml | 64 +++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..2a6d484 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,64 @@ +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write # required for gh release create + +jobs: + release: + name: build and publish (windows / py3.12) + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r requirements-build.txt + + # Gate the release on the same test suite CI runs — never ship a broken build. + - name: Run tests + env: + QT_QPA_PLATFORM: offscreen + run: python -m unittest discover -s tests -v + + - name: Build executable + run: python -m PyInstaller --noconfirm --clean screamer.spec + + # screamer.spec uses COLLECT (onedir), so the artifact is the dist\Screamer + # folder zipped whole — users extract it and run Screamer\Screamer.exe. + - name: Package + shell: pwsh + run: | + $tag = $env:GITHUB_REF_NAME + $zip = "Screamer-$tag-windows-x64.zip" + Compress-Archive -Path dist\Screamer -DestinationPath $zip + $hash = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLower() + "$hash $zip" | Out-File -FilePath "$zip.sha256" -Encoding ascii + "ASSET_ZIP=$zip" | Out-File -FilePath $env:GITHUB_ENV -Append + "ASSET_SHA=$zip.sha256" | Out-File -FilePath $env:GITHUB_ENV -Append + + # Hyphenated tags (v1.0.0-rc1, v1.2.0-beta) publish as pre-releases. + - name: Publish release + env: + GH_TOKEN: ${{ github.token }} + shell: pwsh + run: | + $tag = $env:GITHUB_REF_NAME + $ghArgs = @( + "release", "create", $tag, + $env:ASSET_ZIP, $env:ASSET_SHA, + "--title", $tag, + "--generate-notes" + ) + if ($tag -match "-") { $ghArgs += "--prerelease" } + & gh @ghArgs From f7c352b022cbbf2b73ffce7adfb6ed0ed0c6a06e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kristi=C3=A1n=20Partl?= Date: Tue, 2 Jun 2026 13:07:47 +0200 Subject: [PATCH 2/2] docs: document release process and pipeline plan Add a Releases section to the README explaining the tag-to-release flow, and check in the implementation plan for the release pipeline. --- README.md | 15 +++++ docs/RELEASE_PIPELINE_PLAN.md | 114 ++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+) create mode 100644 docs/RELEASE_PIPELINE_PLAN.md diff --git a/README.md b/README.md index c1f8f18..a554336 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,18 @@ Configure your STT provider (Groq, OpenAI, or any OpenAI-compatible endpoint) vi - Persistent settings and secure key storage - Microphone device selection with auto-calibration - Post-type key support (Enter, Tab, Space, Backspace) + +## Releases + +Pushing a version tag builds the Windows executable and publishes a GitHub Release +automatically (see `.github/workflows/release.yml`): + +```bash +git tag v1.0.0 +git push origin v1.0.0 +``` + +The workflow runs the test suite, builds with PyInstaller, and attaches a +`Screamer-v1.0.0-windows-x64.zip` (plus a `.sha256` checksum) to the release. +Tags with a hyphen (e.g. `v1.0.0-rc1`) are published as pre-releases. Download the +zip, extract it, and run `Screamer\Screamer.exe`. diff --git a/docs/RELEASE_PIPELINE_PLAN.md b/docs/RELEASE_PIPELINE_PLAN.md new file mode 100644 index 0000000..e78cf98 --- /dev/null +++ b/docs/RELEASE_PIPELINE_PLAN.md @@ -0,0 +1,114 @@ +# Plan — GitHub Release Pipeline + +## Goal +On a version tag push, automatically build the Windows `.exe`, package it, and publish a GitHub Release with the artifact attached. No manual build/upload steps. + +## Scope +- New workflow file: `.github/workflows/release.yml`. +- Trigger: push of a tag matching `v*` (e.g. `v1.0.0`, `v1.2.0-rc1`). +- Build on `windows-latest` (PyInstaller produces a Windows-native onedir bundle; cannot cross-build). +- Gate the release on the test suite passing first (don't ship a broken build). +- Package `dist\Screamer\` (onedir output) into a versioned zip. +- Publish a GitHub Release using the built-in `gh` CLI (no third-party action — matches project's minimal-dependency ethos). +- Auto-generate release notes from commit history. +- Mark pre-releases automatically when the tag contains a hyphen (`-rc`, `-beta`, etc.). +- Attach a SHA256 checksum file alongside the zip. + +Out of scope: code signing (separate concern, needs a cert), auto-bumping version numbers, changelog curation, multi-arch. + +## Key facts grounding the design +- `screamer.spec` uses `COLLECT` → output is a **directory** `dist\Screamer\` containing `Screamer.exe` + Qt DLLs. Not a single file. So the artifact must be a **zip of the folder**. +- `build_windows.ps1` creates its own `.venv`. In CI that's wasteful — install deps into the runner's Python directly and call PyInstaller, mirroring the build script's pip + pyinstaller steps. +- Existing `ci.yml` already runs the unittest suite on `windows-latest` with `QT_QPA_PLATFORM=offscreen`. The release workflow reuses that exact invocation as a gate. +- Build deps: `requirements.txt` + `requirements-build.txt` (pins `pyinstaller==6.14.2`). +- Python 3.12 is the target per `docs/PLAN.md`. + +## Workflow design (`.github/workflows/release.yml`) + +```yaml +name: Release + +on: + push: + tags: + - "v*" + +permissions: + contents: write # required for gh release create + +jobs: + release: + name: build and publish (windows / py3.12) + runs-on: windows-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt -r requirements-build.txt + + - name: Run tests (gate) + env: + QT_QPA_PLATFORM: offscreen + run: python -m unittest discover -s tests -v + + - name: Build executable + run: python -m PyInstaller --noconfirm --clean screamer.spec + + - name: Package + shell: pwsh + run: | + $tag = $env:GITHUB_REF_NAME + $zip = "Screamer-$tag-windows-x64.zip" + Compress-Archive -Path dist\Screamer -DestinationPath $zip + $hash = (Get-FileHash $zip -Algorithm SHA256).Hash.ToLower() + "$hash $zip" | Out-File -FilePath "$zip.sha256" -Encoding ascii + "ASSET_ZIP=$zip" | Out-File -FilePath $env:GITHUB_ENV -Append + "ASSET_SHA=$zip.sha256" | Out-File -FilePath $env:GITHUB_ENV -Append + + - name: Publish release + env: + GH_TOKEN: ${{ github.token }} + shell: pwsh + run: | + $tag = $env:GITHUB_REF_NAME + $ghArgs = @( + "release", "create", $tag, + $env:ASSET_ZIP, $env:ASSET_SHA, + "--title", $tag, + "--generate-notes" + ) + if ($tag -match "-") { $ghArgs += "--prerelease" } + & gh @ghArgs +``` + +> Review fix: the earlier draft passed `$pre` (possibly an empty string) as a trailing +> positional argument to `gh release create`. On a stable tag (no hyphen) that empty +> string would be parsed as an extra asset path with an empty name and the publish would +> fail. Building an args array and only appending `--prerelease` when needed avoids this. + +## Steps to implement +1. Create `.github/workflows/release.yml` with the workflow above. +2. Validate YAML syntax locally (Python `yaml.safe_load`). +3. Document the release process in `README.md` (a short "Releases" section: tag `vX.Y.Z`, push tag, CI builds and publishes). +4. Verify nothing else references release artifacts inconsistently. + +## Verification +- YAML parses without error. +- Logic walk-through: tag `v1.0.0` → tests run → PyInstaller builds `dist\Screamer\` → zip created → `gh release create` publishes with notes, non-prerelease. Tag `v1.0.0-rc1` → same but `--prerelease`. +- Cannot fully run end-to-end without pushing a real tag; that is left to the user. The workflow is validated by syntax + the fact that its build/test steps mirror the already-green `ci.yml` and `build_windows.ps1`. + +## Risks +- PyInstaller build time on CI (~several min) — acceptable, runs only on tags. +- `gh release create` fails if a release for the tag already exists — acceptable (re-tag or delete release to retry). +- If tests are flaky on CI, releases block — desired behavior (gate). + +## Commit split +1. `ci: add release workflow triggered on version tags` +2. `docs: document the release/tagging process in README`