---
name: release
description: Cut a Stylebot release — CHANGELOG entry, version bump, release PR, tag, and store zips. Use when the user wants to "release", "cut a release", "ship version X.Y.Z", "bump the version", or prepare builds for the Chrome Web Store, Edge Add-ons, or AMO.
---

# Cut a Stylebot release

Releases go through a **pull request**, not a direct push to `main`. The PR is what
gates the release on CI — in particular the Edge e2e suite, which only runs on release
PRs (`.github/workflows/e2e-edge.yml`).

## 1. Branch

Branch off the latest `main`:

```
git fetch origin main && git checkout -b release/vX.Y.Z origin/main
```

The `release/` prefix is **not** cosmetic — `.github/workflows/e2e-edge.yml` gates on
`startsWith(github.head_ref, 'release/')`. Any other branch name silently skips the Edge
run, which is the whole point of the release PR.

## 2. Changelog

Prepend a new section to `CHANGELOG.md` (newest first):

```markdown
## Version X.Y.Z (Month Year)

- Short description of the change (#PR)
```

Keep that heading format exactly — `.github/workflows/release.yml` parses it to build the
release body, and a heading it can't match fails the release.

Write entries from the user's perspective — what changed for someone using the
extension, not what changed in the code. Every bullet ends with its `(#PR)` reference.
`git log --oneline <last-release-tag>..HEAD` gives you the raw material; PR numbers are
the `(#NNN)` suffixes that squash merges leave in commit subjects.

## 3. Version bump

Update `version` in both:

- `package.json`
- `src/assets/manifest/manifest.json`

`src/assets/manifest/manifest-firefox.json` has **no** `version` field — it only overrides
`background` and `browser_specific_settings`, and webpack shallow-merges it over the base
manifest. Leave it alone.

## 4. Commit and open the PR

```
git commit -am "Release X.Y.Z"
git push -u origin release/vX.Y.Z
```

`Release X.Y.Z` is a deliberate exception to the Conventional Commits rule in AGENTS.md,
matching every previous release commit. Don't write `chore(release): ...`.

Open the PR against `main`. Do not create a GitHub Release yet — the tag comes after
merge.

## 5. Wait for CI

Four checks must pass before merging:

| Check | Covers |
| --- | --- |
| `build` | `yarn build` |
| `validation` | lint, typecheck, unit tests, locale validation |
| `e2e` | Playwright suite on Chrome |
| `e2e (firefox)` | Playwright suite on Firefox |
| `e2e (edge)` | Playwright suite on Edge — **release PRs only** |

If `e2e (edge)` shows as *skipped*, the branch name is wrong: it must start with
`release/`. A skipped run is not a passed run.

## 6. Merge

Squash-merge the PR. `.github/workflows/release.yml` then creates the GitHub Release
automatically: it reads the version from `package.json`, tags the squashed commit
`vX.Y.Z`, and uses that version's `CHANGELOG.md` section as the release body.

Nothing to do by hand — but check the workflow went green. It fails loudly rather than
publishing something wrong if the tag already exists, if `package.json` and
`src/assets/manifest/manifest.json` disagree on the version, or if the changelog section is
missing.

## 7. Build the store packages

```
yarn build          # -> dist/        Chrome Web Store + Edge Add-ons
yarn build:firefox  # -> firefox-dist/  AMO
```

Zip each output directory and upload by hand. There is no automated packaging or store
upload — this step is manual.

## Optional follow-up

Minor releases (X.Y.0) usually get a marketing page under
`site/src/pages/releases/X.Y.js`. Patch releases don't.
