--- name: release-process description: End-to-end release runbook for playwright-rust — version bump, supply-chain refresh, per-crate CHANGELOGs, tag-prefix routing for the three workspace crates, the safer push-then-tag workflow that waits for CI before publishing, and the post-release follow-ups. metadata: internal: true --- # Release Process This skill captures the procedural steps for shipping a playwright-rust release, for in-context reference when walking through one manually or guiding the user. ## Pre-flight, before touching any version string The checklist below assumes these already pass; run them first so a failure lands here rather than halfway through a bump. All are read-only against the tree as it stands. ```bash cargo nextest run --workspace --all-features # what release.yml runs cargo nextest run --workspace --run-ignored ignored-only # engine-specific stress set cargo test --doc --workspace --all-features cargo xtask verify-changelog-links # [Unreleased] and the link footer agree cargo xtask verify-driver-version # every pinned reference matches build.rs cargo xtask verify-agent-docs # skill compiles and names every feature cargo xtask verify-site-snippets cargo xtask sync-protocol-spec --check # vendored spec is the pinned driver's cargo xtask verify-protocol-methods # every method the crate sends is in that spec cargo vet && cargo deny check && cargo audit ``` `cargo audit` fetches the RustSec database over git, which the Claude Code sandbox blocks; a "couldn't fetch advisory database" there is the sandbox, not an advisory. CI's Security & Quality job on the same commit is the authoritative run in that case. Then check the cross-crate couplings that a bump makes bite. Step 4 below lists the dependency lines that move together; confirm each is at the version about to be superseded before editing any of them, so a line that was already stale does not get mistaken for one this bump changed. ## Workspace layout — three independently-versioned crates Three publishable crates, each with its own CHANGELOG and tag prefix: | Crate | Path | CHANGELOG | Tag prefix | |------------------------|-----------------------------------|----------------------------------------------------|----------------| | `playwright-rs` | `crates/playwright/` | `crates/playwright/CHANGELOG.md` | `vX.Y.Z` | | `playwright-rs-macros` | `crates/playwright-rs-macros/` | `crates/playwright-rs-macros/CHANGELOG.md` | `macros-vX.Y.Z`| | `playwright-rs-trace` | `crates/playwright-rs-trace/` | `crates/playwright-rs-trace/CHANGELOG.md` | `trace-vX.Y.Z` | The top-level `CHANGELOG.md` is an **index file**, not a changelog — release notes are generated per-crate from each crate's own CHANGELOG. The `xtask` workspace member is `publish = false` and has no CHANGELOG; its workflow is documented in [`crates/xtask/`](../../../crates/xtask/). ## Versioning - `0.x.y` — pre-1.0, API may change (current stage) - `1.0.0` — stable API, ready for production - Patch (`x.y.Z`) — bug fixes, security advisories, no API changes - Minor (`x.Y.0`) — additive features, deprecations, no breaking changes permitted in 1.x but acceptable in 0.x - Major (`X.0.0`) — breaking changes (post-1.0) Security advisories against transitive deps **always** warrant a patch release, even if functional behavior is unchanged. See the **supply-chain** skill. ## Pre-release checklist Steps below assume you're releasing **`playwright-rs`** (the main crate). For the macros or trace crate, swap the paths and tag prefix per the table above; the workflow is otherwise identical. 1. **Tests are green on `main`** before starting 2. **Decide the version** (`X.Y.Z`) — independent of the other crates 3. **Bump the version** in the relevant `Cargo.toml`: - For `playwright-rs`: workspace `version` in the top-level `Cargo.toml` - For `playwright-rs-macros`: `version` in `crates/playwright-rs-macros/Cargo.toml` - For `playwright-rs-trace`: `version` in `crates/playwright-rs-trace/Cargo.toml` 4. **If a sibling crate's version changed too**, update the dep line in `crates/playwright/Cargo.toml`: - `playwright-rs-macros = { version = "...", path = "..." }` for the macros bump - `playwright-rs-trace = { version = "...", path = "..." }` for the trace bump — **two lines carry it**, the optional dependency behind the `trace` feature and the dev-dependency the tracing integration test uses, and both need the new version. Cargo does not fall back to crates.io for a path dependency whose version requirement fails, so a stale line breaks every workspace command until it is updated - `xtask`'s `playwright-rs = { path = "...", version = "..." }` if the main crate version changes (cargo-deny's no-wildcard rule) 5. **Refresh `cargo vet`** — see the **supply-chain** skill for the `cargo vet regenerate unpublished` / `cargo vet regenerate exemptions` flow 6. **Update the relevant CHANGELOG** (`crates//CHANGELOG.md`): - Rename `## [Unreleased]` to `## [X.Y.Z] - YYYY-MM-DD` - Add a fresh empty `## [Unreleased]` heading above - Update the compare-link footer: add `[X.Y.Z]: ` and repoint `[Unreleased]` at the new tag. This step was skipped on three consecutive releases, leaving those headings rendering as literal `[0.14.0]` text, so it is now enforced by `cargo xtask verify-changelog-links` (pre-commit; run it directly if you want to check before committing). 7. **Sync README.md to the release** — applies to `playwright-rs` only. The repo's `README.md` describes the **latest published release**, not `main`'s in-progress state. A pointer line under the **Status:** header directs readers at `crates/playwright/CHANGELOG.md` `[Unreleased]` for anything not yet on crates.io. At release time, fold in everything the `[Unreleased]` CHANGELOG has been previewing — feature flags table, install/CI snippets, Testing & Debugging additions, etc. Also bump the install snippet's pinned `"0.X"` (line 136 area) if this is a minor or major bump. If this release carries a driver bump, update the README badge only: install snippets are version-free by design (they go through the `install-browsers` example / `install_browsers`), so verify none regressed to a pinned `npx playwright@X.Y.Z`. 8. **Sync the landing site's release-facing constants** — applies to `playwright-rs` only, and these are **not** covered by `cargo xtask verify-driver-version` (that guard deliberately anchors only on `PLAYWRIGHT_DEV`, since the released values legitimately lag `main` between releases). Easy to miss, and both are rendered on the published `/vX.Y.Z` snapshot: - `crates/site/src/components/hero.rs` — `PLAYWRIGHT_RELEASED` must become the driver version this release bundles (i.e. match `PLAYWRIGHT_DEV` at release time). - `crates/site/snippets/install.toml` — the crates.io pin (`"0.X"` and its `0.X.y` comment). Also drop `unreleased=true` from any `FeatureCard` in `crates/site/src/components/features.rs` whose feature ships in this release (unreleased cards render **only** on the dev build, so they would be missing from the release snapshot), and prune the matching assertions in `crates/site-e2e/tests/landing_page.rs`. 9. **Verify locally**: - `cargo nextest run --workspace` - `cargo clippy --workspace --all-targets --all-features -- -D warnings` - `cargo test --doc --workspace` - `cargo audit && cargo deny check && cargo vet` - **Dry-run the publish**: `cargo publish --dry-run -p ` — catches packaging issues (missing files, license check, README path) before the irreversible real `cargo publish`. Pass `--allow-dirty` if you're verifying mid-edit. **Coordinated first-publish caveat**: when a release pushes a not-yet-published sibling crate (e.g. v0.13.0's first publish of `playwright-rs-macros`), the main crate's dry-run fails with `no matching package named '' found ... required by package 'playwright-rs'` because cargo resolves all deps against the crates.io index. Dry-run the sibling crates in dependency order (`-p playwright-rs-macros` → `-p playwright-rs-trace` → `-p playwright-rs`); the main crate's dry-run only completes after the siblings are actually published. For pre-release verification, `cargo package --list -p playwright-rs --allow-dirty` confirms the tarball contents without index lookups. ## The safer push-then-tag workflow A pushed git tag triggers `release.yml` which publishes to crates.io. **Crates.io publishing is irreversible** — you cannot unpublish, only yank. Always validate on CI before tagging. ### Single-crate release (the common case) ```bash # 1. Commit the version-bump changes for the chosen crate git add Cargo.toml Cargo.lock crates//Cargo.toml \ crates//CHANGELOG.md \ supply-chain/imports.lock supply-chain/config.toml # (also stage README.md if you bumped the main crate's minor version) git commit -m "Bump to vX.Y.Z" # 2. Push the COMMIT first (no tag yet) git push origin main # 3. Watch CI — Test on linux/mac/windows + Security & Quality gh run watch # or check the Actions tab in GitHub # 4. Only after ALL required checks are green, create and push the tag # Tag prefix maps to crate (see workspace table at top of file): # v0.13.0 → playwright-rs # macros-v0.1.1 → playwright-rs-macros # trace-v0.1.1 → playwright-rs-trace git tag -a vX.Y.Z -m "Release vX.Y.Z — " git push origin vX.Y.Z ``` ### Coordinated release (multiple crates bumped together) When a `playwright-rs` release also requires bumping a sibling crate (e.g. v0.13.0 wants a fresh `playwright-rs-macros` 0.2.0), publish the sibling first so the dep is available on crates.io when the main crate's `cargo publish` runs: ```bash # Single commit bumps all relevant Cargo.toml + CHANGELOG files. git push origin main gh run watch # CI must be green # Push tags in dependency order. Wait ~30s between tags so the # crates.io index propagates before the next publish runs. git tag -a macros-vA.B.C -m "Release playwright-rs-macros vA.B.C" git push origin macros-vA.B.C sleep 30 # crates.io index propagation git tag -a trace-vD.E.F -m "Release playwright-rs-trace vD.E.F" git push origin trace-vD.E.F sleep 30 git tag -a vX.Y.Z -m "Release playwright-rs vX.Y.Z" git push origin vX.Y.Z ``` If a sibling's version is unchanged this cycle, just skip its tag. If CI fails on `main` after the version-bump commit: - Don't tag. Land a follow-up commit fixing the failure (or revert). - A failed `main` is recoverable; a published bad version is not. ## What `release.yml` does on tag push The workflow handles all three tag prefixes (`v*`, `macros-v*`, `trace-v*`) with one routed pipeline: 1. Runs the test suite on linux/macOS/windows as a final pre-publish gate (always, regardless of tag prefix). 2. **Resolves the tag** — `Resolve crate, changelog, and version from tag` step parses `${GITHUB_REF#refs/tags/}` and routes: - `macros-v*` → `playwright-rs-macros` + `crates/playwright-rs-macros/CHANGELOG.md` - `trace-v*` → `playwright-rs-trace` + `crates/playwright-rs-trace/CHANGELOG.md` - `v*` → `playwright-rs` + `crates/playwright/CHANGELOG.md` 3. **Generates release notes** from the resolved CHANGELOG via `parse-changelog ` — the **per-crate CHANGELOG is the single source of truth** for release notes; no manual paste needed. 4. **Creates the GitHub Release** with `name = " "` and the body from the parsed CHANGELOG section. 5. **Publishes to crates.io** — exactly one publish step fires per tag, gated by `startsWith(github.ref_name, '')`. Failure aborts the workflow (no `continue-on-error`); the release tag stays in place but the publish didn't happen, so re-running with the same tag after fixing the issue is safe. The workflow is library-only: no binary artifacts are built, archived, or attested. If a CLI is ever shipped, add a separate binary-release pipeline rather than bolting onto this workflow. ## Post-release 1. **Verify** the GitHub Release at `https://github.com/padamson/playwright-rust/releases/tag/vX.Y.Z` 2. **Verify** crates.io has the new version. The JSON API often refuses this with a data-access-policy error; the sparse index answers reliably: `curl -s https://index.crates.io/pl/ay/playwright-rs | tail -1` 3. **Publish the versioned site snapshot** — applies to `playwright-rs` only, and **nothing triggers this automatically**: ```bash gh workflow run pages.yml --ref vX.Y.Z -f version=X.Y.Z ``` `--ref vX.Y.Z` is load-bearing: the workflow builds `/v/` from *current source at that ref*, so dispatching from `main` would publish `main`'s content under the release's URL. A tag push does **not** fire `pages.yml` (its `on.push` filters to branches), so skipping this leaves `versions.json` advertising the previous release as `latest` and omits the new version from the dropdown. This was missed for 0.15.1 and again for 0.16.0, which is why it is now a numbered step rather than a comment in the workflow header. Verify after: `curl -s https://playwright-rust.dev/versions.json` 4. **First-time publish bookkeeping** — if this is the first crates.io release of a workspace crate, add `[policy.] audit-as-crates-io = true` to `supply-chain/config.toml` in a follow-up commit. Cannot be done pre-release because `cargo vet` rejects the policy until the crate exists on crates.io. 5. **Flip the release-state doc** — one follow-up commit, `[skip ci]`, for a minor release that closed driver surface: `docs/implementation-plans/v1.0-gap-analysis.md`, the version's section heading and the Coverage Summary paragraph. A patch release that changes no coverage needs nothing here. `docs/roadmap.md` carries no per-release status by design and is never touched at release time. The gap analysis claims a release exists, so it must **not** ride the version-bump commit: that lands on `main` before CI and before the tag, and a failed publish would leave `main` advertising a release nobody can install. Keep it in the "cut, awaiting tag" phrasing until the publish is verified in steps 1-2, then flip it in one commit. 6. **Update tracking issues** if this release closes any 7. **Announce** if applicable (depends on release significance) ## Common pitfalls - **Hand-editing `supply-chain/imports.lock`** — never; use `cargo vet regenerate unpublished` (see supply-chain skill) - **Tagging before CI** — a single failing platform is enough to make a release un-rerunnable - **Forgetting the `[Unreleased]` reset** in the per-crate CHANGELOG — leaves the next session's CHANGELOG additions homeless - **Editing the top-level `CHANGELOG.md`** — it's an index, not a changelog. Per-crate CHANGELOGs are the source of truth; if you tried adding a release entry to the index it won't appear in the generated GitHub release notes. - **Skipping the README version bump** on minor releases of `playwright-rs` — the install snippet's pinned `"0.X"` controls what new users see in the README on GitHub before the version on crates.io is current - **Coordinated release without sleep between tags** — crates.io index propagation takes ~10–30s; `cargo publish -p playwright-rs` will fail to resolve a freshly-published `playwright-rs-macros` if the tags are pushed back-to-back without a wait - **Pushing the wrong prefix** — `vX.Y.Z` always means `playwright-rs`; `macros-vX.Y.Z` is the macros crate; `trace-vX.Y.Z` is the trace crate. The `release.yml` "Resolve crate, changelog, and version from tag" step rejects unknown prefixes