Files
agent-desktop/old_docs/RELEASING.md
T

135 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Releasing QwenPaw
_中文版:[RELEASING_zh.md](RELEASING_zh.md)_
QwenPaw ships four artifacts from a single version — the **PyPI** wheel, the
**Docker** image, the **desktop** apps (Tauri, Windows + macOS) and the
**plugins** bundle. They are published together by one orchestrated workflow so
that a failure in any one of them blocks the whole release: you can never end up
with, e.g., a web release that has no matching desktop build.
> Orchestrator: [`.github/workflows/release.yml`](.github/workflows/release.yml).
> The older per-artifact workflows are kept as a fallback — see
> [Rollback to the legacy flow](#rollback-to-the-legacy-flow).
## TL;DR
1. Create a **draft** GitHub Release (tag + notes). Do **not** click *Publish*.
2. Actions → **Release (unified)***Run workflow* (leave `dry_run` off).
3. It builds + verifies everything; only if all of it passes does it publish all
artifacts and flip the release to *published*.
4. If anything fails, nothing is published and the draft is left untouched — fix
and re-run.
## How a release works
`release.yml` runs in three phases:
1. **Resolve** — finds the target draft (the `tag` input, or auto-detects the
single existing draft), resolves the draft's `target_commitish` to a concrete
SHA, and pins every downstream job to that SHA (so *what is built* == *what is
published*). On a real release it also fails fast if the DashScope secret is
missing.
2. **Prepare** (build + verify, publishes nothing) — in parallel:
- `build-wheel` — build the Python wheel (with the bundled console).
- `verify-web` — pip-install, Docker health-check and install-script checks.
- `build-desktop` — build the Tauri Windows + macOS apps and run the
install → launch → real-chat UI verification.
- `build-plugins` — pack the plugin bundle.
3. **Gate + Publish** — every publish job `needs` **all** prepare jobs, so a
single failure above skips the entire publish phase. When all prepare jobs are
green: publish to PyPI, push the multi-arch Docker image, attach the desktop
installers to the release + upload them to OSS, publish plugins — then, as the
**last** step, flip the draft to *published* (pinned to the built SHA). After
publishing, it promotes the desktop `latest`/updater, deploys the website
(stable/post only — betas are skipped), and opens the Release Duty verification
issue.
A full run is ~6075 min, dominated by the desktop Tauri builds.
## Cutting a release
1. **Create the draft release**
- UI: Releases → *Draft a new release* → set the tag + notes → **Save draft**
(do **not** publish). For a pre-release, tick *Set as a pre-release*.
- CLI:
```bash
gh release create v2.0.0-beta.8 --draft --prerelease \
--target main --title "v2.0.0-beta.8" --notes "..."
```
- The tag should correspond to `src/qwenpaw/__version__.py` (`resolve`
validates this with `packaging` normalization and fails on a mismatch, e.g.
tag `v2.0.1-beta.1` must match version `2.0.1b1`).
- Prefer pinning the draft to a commit (`--target <sha>`). If you use
`--target main`, avoid merging to `main` between creating the draft and
running the workflow, otherwise the build uses the newer `main` HEAD.
2. **Run the workflow**: Actions → **Release (unified)** → *Run workflow* on
`main`. Leave `tag` empty to auto-detect the single draft (or set it
explicitly); leave `dry_run` **unchecked**.
3. **Watch it**: on success the release flips to *published* with all artifacts
attached and a Release Duty issue is opened. On failure, see
[Troubleshooting](#troubleshooting).
## Version types: beta / stable / post
The procedure is identical for all types — the type is inferred from the **tag**:
| Type | Example tag | Draft "pre-release"? | Docker tags | PyPI |
|------|-------------|----------------------|-------------|------|
| beta / rc / alpha / dev | `v2.0.0-beta.8` | yes | `<version>` + `pre` (no `latest`) | uploaded; treated as a pre-release by pip (`--pre`) |
| stable | `v2.0.0` | no | `<version>` + `pre` + `latest` | normal |
| post | `v2.0.0.post4` | no | `<version>` + `pre` + `latest` | post release |
Notes:
- Pre-release detection is **tag-based**: a tag containing `beta`/`alpha`/`rc`/`dev`
is a pre-release (so use the `-beta.N` form); `stable` and `.postN` tags also
update the Docker `latest` tag.
- The **website** (GitHub Pages, `qwenpaw.agentscope.io`) is deployed only for
**stable** and `.postN` releases; pre-releases are skipped so the public site
advertises only GA versions.
- The desktop OSS `latest` files and the Tauri auto-update manifest are currently
updated for **every** release, including betas (this matches the previous
`desktop-release.yml` behavior and is unchanged here). Making the desktop
`latest`/updater stable-only is a possible future improvement.
## Troubleshooting
**Guarantee:** the draft is flipped to *published* only after **all** publish
jobs succeed. If anything fails, the release stays a draft.
| Situation | What happened | What to do |
|-----------|---------------|------------|
| A prepare job fails (desktop / web verify / wheel / plugins) | All publish + `finalize` + `duty-issue` are **skipped**; nothing published; draft untouched | Read the failed job's logs and fix (or re-run if flaky) → **Re-run failed jobs**, or re-run the workflow. No cleanup needed. |
| A publish job fails after the gate (e.g. Docker push fails after PyPI already uploaded) | `finalize` needs all publishes, so the draft is **not** flipped; but some artifacts may already be live | **Re-run failed jobs** (already-succeeded jobs are not re-run; Docker re-push is idempotent, OSS uses `--force`) → the draft flips once they pass. If a published PyPI version is now taken and cannot be reused, cut a `.postN` instead. |
| `finalize` fails | All artifacts published but the release was not flipped | Re-run `finalize`, or manually `gh release edit <tag> --draft=false --target <sha>` (or click *Publish*). |
| `duty-issue` fails | Release is published; only the tracking issue is missing | Re-run the job, or dispatch `release-duty.yml` with the `tag`. Non-blocking. |
| `promote-desktop` fails | Release is published, but the desktop `latest` files / updater manifest / index were not refreshed (existing users' auto-updater does not see the new version yet; versioned downloads still work) | Re-run the job — it is idempotent (`ossutil cp --force`). Non-blocking for first-install users. |
| `deploy-website` fails (stable/post only) | Release is published, but the public site (`qwenpaw.agentscope.io`) still shows the previous version | Re-run the job, or manually dispatch `deploy-website.yml` (workflow_dispatch). Idempotent, non-blocking. |
| "Multiple draft releases found" | More than one draft exists | Re-run *Run workflow* with an explicit `tag`. |
| "No draft release found" / "not a draft" | No draft, or wrong tag | Create the draft / fix the tag, then re-run. |
| resolve rejects the tag (version mismatch) | The draft tag doesn't match `src/qwenpaw/__version__.py` | Align the tag with the version (packaging-normalized, e.g. `v2.0.1-beta.1` ↔ `2.0.1b1`), then re-run. |
## Rollback to the legacy flow
The pre-existing per-artifact workflows are intentionally retained. If the
orchestrator is broken, publish the release the old way — **Publish** the GitHub
Release (or `gh release create ...`), which triggers `publish-pypi` /
`docker-release` / `desktop-release` / `plugins-release` on `release: published`.
> **Warning:** the legacy flow does **not** gate the web release on the desktop
> build (the very problem this orchestrator fixes), so use it only as an
> emergency fallback.
## Fork / dry-run testing
Run **Release (unified)** with `dry_run: true` to exercise the gate and the
draft→published flip **without** touching production: PyPI upload, Docker push
and OSS upload become no-ops, while the desktop build/verify, the draft flip and
the duty issue still run for real. On a fork this only affects the fork's own
release page.
Note: the desktop build's install → launch → chat UI verification still runs
under `dry_run` and needs the `QWENPAW_DASHSCOPE_API_KEY` secret — `dry_run` only
skips the resolve-stage fail-fast check, not the verification itself.