Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
114 changes: 114 additions & 0 deletions docs/RELEASE_PIPELINE_PLAN.md
Original file line number Diff line number Diff line change
@@ -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`
Loading