Skip to content

ci: replace release-plz with in-house xtask release tool - #981

Draft
mcrakhman wants to merge 12 commits into
mainfrom
mcrakhman/remove-release-plz
Draft

ci: replace release-plz with in-house xtask release tool#981
mcrakhman wants to merge 12 commits into
mainfrom
mcrakhman/remove-release-plz

Conversation

@mcrakhman

@mcrakhman mcrakhman commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Why replace release-plz

release-plz is a solid general-purpose tool, and much of what it does well we simply keep (Verified commits via GitHub's createCommitOnBranch API, cargo-semver-checks, env-var secrets, CI-native/non-interactive operation). We're replacing it not because those are weak, but because of a few structural mismatches with how we actually release: we want to choose the version ourselves, we want npm in the same pipeline as crates, and we want the release logic to be our own code we can tailor rather than configuration wrapped around a black box.

The real differences

  • Explicit, human-chosen version — vs. release-plz inferring (and auto-bumping) it. release-plz derives the next version from conventional-commit history and will auto-bump the major when cargo-semver-checks detects a breaking API change. We deliberately don't want the tool deciding that. xtask makes the version a required --version flag, validated against a strict legal-successor model (version.rs): from a stable X.Y.Z you may go to one reset bump ((X+1).0.0 / X.(Y+1).0 / X.Y.(Z+1)), optionally -rc.1; from -rc.N only to -rc.(N+1) or the promotion. A human picks the number; the tool only rejects illegal jumps.

    As a corollary, xtask still runs breaking-change detection (breaking.rs, API signal from cargo-semver-checks + intent from !/BREAKING CHANGE:), but it is diagnostic only — surfaced in the PR body, never changing the version. Same signal release-plz uses; opposite policy on who acts on it.

  • npm in the same pipeline — vs. a separate bolted-on workflow. This is the biggest concrete win. Under release-plz, npm was entirely outside the tool: release-plz.toml and its commands only touched crates, and two extra jobs reproduced the version bump, wasm-pack build, dist-tag derivation, and publish in ~100 lines of hand-rolled bash keyed off release-plz's job outputs. xtask makes npm a first-class step of the same flows: prepare-release mirrors the version + refreshes the lockfile for each [[npm]] component, and release builds via wasm-pack and publishes both the wasm crate and the JS wrapper at the same version under the derived dist-tag (npmops.rs, release.toml). One tool, one source of truth for the version.

  • Our own code we can tailor — vs. configuring around a black box. release-plz is configured only through what release-plz.toml exposes (ours was a single release_always = false); anything beyond that meant working around it in bash (the npm jobs above being the prime example). xtask puts the bespoke logic — the successor model, the npm mirror, our tag/PR conventions, the diagnostic breaking-change report — directly in the pipeline.

What's in this PR

  • New xtask crate — the release tool, run as cargo xtask prepare-release / cargo xtask release. Layered pure-core / thin-I/O-shell design with unit tests.
  • Two thin workflow_dispatch workflows: prepare-release.yml (opens/refreshes the release PR) and release.yml (publishes crates + npm, cuts tags + GitHub releases; idempotent/resumable).
  • New repo-root release.toml config and a cargo xtask alias in .cargo/config.toml.
  • Removed release-plz.toml and the two release-plz-*.yml workflows.
  • Hygiene: untracked the accidentally-committed .idea/ files (now gitignored) and fixed stale doc references.

🤖 Generated with Claude Code

mcrakhman and others added 12 commits July 6, 2026 16:00
Add the `cargo xtask` release tool (ported from toy-kv) that replaces
release-plz with a small, testable, in-house binary. Two subcommands:

- `cargo xtask prepare-release --version X` builds a single-commit release
  PR (version bump + per-crate changelogs + npm mirror) and opens/updates it.
- `cargo xtask release --sha <merge>` publishes crates in topological order,
  cuts per-crate `{name}-v{version}` tags + GitHub releases, and publishes the
  lumina-node npm packages. Both flows are idempotent and resumable.

Wiring for lumina:
- add `xtask` to the workspace members and a `cargo xtask` alias
- add `release.toml` configuring the branch prefix (`release-`) and the
  lumina-node-wasm / node-wasm/js npm component
- derive the wasm-pack build dir from the npm component's package_dir parent
  (node-wasm) instead of toy-kv's hardcoded `wasm`
- pin xtask's own serde (workspace serde lacks `derive`) and set package
  metadata directly (lumina's [workspace.package] only defines version+edition)
- retarget the workspace/commit fixture tests off toy-kv onto lumina

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Swap the two release-plz workflows for thin `workflow_dispatch` wrappers
around `cargo xtask`:

- `prepare-release.yml` — manual, takes a `version` input, runs
  `cargo xtask prepare-release --push` to open/refresh the release PR
  (uses the RELEASE_PLZ_TOKEN PAT so the pushed commit re-triggers PR CI).
- `release.yml` — manual, run after merging the release PR; runs
  `cargo xtask release --sha ${{ github.sha }}` to publish crates + npm and
  cut tags/releases. Idempotent, so it safely resumes a partial release.

Remove release-plz.toml and the release-plz-*.yml workflows.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The design spec is toy-kv-specific reference material and not needed to run
the tool. The remaining `release-spec/...` mentions are inert doc-comment
links.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Break the ~250-line `cmd_prepare::run` into named phase helpers
(guard_release_branch, discover_current_version, validate_requested_version,
bump_workspace_version, preflight_publishable, build_package_reports,
update_npm_wrappers, create_release_commit). `run` now reads as a linear
sequence of named steps, so the `// --- Step N ---` divider comments are
redundant and gone.

Also remove the numbered/scaffolding comments elsewhere where the code is
self-explanatory (npmops `// 1.`..`// 5.`, cmd_release `// Step 0/3`, prbody
`// Step 1/2/3`, lib.rs "Wave"/"M#" module grouping), keeping only the
substantive explanations.

Behavior preserved exactly, including the ordering constraint that
`git_default_branch` (which can run `git remote set-head` and reads the
current branch) is evaluated only *after* the branch-exists guard and
*before* the branch switch. All 254 tests pass; the four non-orchestration
files change comments only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sweep every module's comments to read like an engineer wrote them rather than
generated from a spec:

- drop the full-width banner/divider comments (`// ====`, `// ----`, and the
  `// --- label ---` section rules in test modules)
- remove the dead design-spec scaffolding that referenced now-deleted docs:
  module IDs (M1..M14), spec filenames and `§section` citations, "Flow N step",
  "Spec question", "the contract", "Wave N", "(orchestrator-fixed)"
- rewrite the module-level docs as short plain descriptions
- tighten comments that merely restated the code, keeping the load-bearing
  "why" notes (idempotency, PAT/CI attribution, git_default_branch ordering,
  the release-plz first-render trim, fail-open on missing cargo-semver-checks,
  in-workspace local-source hazard, tokens-by-env-name)

Comments only — every non-comment line is byte-for-byte unchanged (verified);
254 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Two correctness fixes for the release flow's tag creation:

- Pass `target_commitish: opts.sha` when creating the GitHub release. The local
  tag is never pushed, so GitHub creates the tag itself — and with no
  target_commitish it defaulted to the repo's default-branch HEAD rather than
  the commit we published from. Pinning it to the release SHA lands the tag on
  the right commit; GitHub ignores the field when the tag already exists, so
  re-runs stay idempotent.

- Add `ensure_sha_is_head`: refuse to release unless `opts.sha` resolves to the
  checked-out HEAD. The version is read from the working tree while tags target
  `opts.sha`, so a mismatch would tag a commit that doesn't carry the published
  version. Both are resolved via `rev-parse`, so a short SHA / ref name still
  matches a full HEAD.

Adds a real-repo test for the guard and extends the create_release_body test to
assert target_commitish. 255 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Untrack the .idea/ JetBrains files that were committed by mistake and add
.idea/ to .gitignore. Also scrub the dangling references to the removed
release-spec/orchestrator.md in release.toml and the release workflows,
repointing them at xtask/README.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The docs job runs `cargo clippy --workspace -- -D missing-docs` plus a
rustdoc `-D warnings` pass, and xtask tripped both:

- Add `#![allow(missing_docs)]` to the xtask crate root — it is
  `publish = false` internal tooling, not a documented public API.
- Fix a broken intra-doc link (`[`git-cliff-core`]`) in changelog.rs that
  failed the rustdoc `-D warnings` step.
- Exclude xtask from the wasm32 missing-docs step; it is native-only
  (ureq/git-cliff-core have no wasm support) and can't target wasm.
- Point xtask's anyhow/serde/serde_json/base64 at the workspace versions so
  the internal tool never diverges from the rest of the tree.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Neither file changed in this branch; a newer clippy promoted these to errors
under the CI's `-D warnings`:

- types: collapse an `if let Some(_) = _ else { return None }` into `?`
  (clippy::question_mark) in round_up_to_power_of_2.
- grpc test helper: drop a redundant `&` in a format arg
  (clippy::useless_borrows_in_formatting).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
xtask's release-tooling deps (git-cliff-core, tera, ureq, …) are heavier and
float independently of the library crates. As a workspace member they dragged
the whole tree's dependency resolution — in particular the `-Z direct-minimal-versions`
CI check — upward: git-cliff-core alone forces newer serde/dyn-clone/tera than
the published crates pin.

Split xtask into its own workspace so those deps stay out of the main tree:

- Root Cargo.toml: drop xtask from members, add it to `exclude`. This also
  shrinks the main Cargo.lock back down (~950 fewer lines).
- xtask/Cargo.toml: add `[workspace]`, give it a concrete version/edition and
  concrete deps (it can no longer inherit `version.workspace` etc.). It gets
  its own Cargo.lock.
- .cargo/config.toml: run the alias via `--manifest-path xtask/Cargo.toml`.
- ci.yml: xtask is no longer in the clippy/test matrices (it never was), so add
  a dedicated job that runs its clippy (-D warnings) and tests; revert the
  now-invalid `--workspace --exclude xtask` in the docs job.
- Fix the discover() integration test (xtask is no longer a member) and clear
  clippy lints the new -D warnings job surfaces.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The minimal-versions CI check had drifted red on main: newer transitive
requirements outran two declared floors. Neither is caused by this branch, but
fix them here so the check passes end-to-end:

- bytes 1.6 -> 1.7.1 (workspace): hyper-util 0.1.19 requires bytes >=1.7.1.
- quote 1.0.35 -> 1.0.37 (grpc-macros): rstest_macros 0.26 requires quote >=1.0.37.

Verified with `cargo minimal-versions check --direct --all-features` for both
native and wasm32 targets.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant