fix(release): preserve Homebrew formula version (#2488)

This commit is contained in:
José Almeida
2026-08-10 15:06:16 +01:00
committed by GitHub
parent 0678d696c8
commit 7968691dc1
5 changed files with 21 additions and 11 deletions
+7 -1
View File
@@ -362,6 +362,8 @@ describe("release desktop artifacts", () => {
it("keeps the generated tap formula on the installed runtime smoke contract", () => {
const formula = readRepoFile("scripts/release/kandev.rb");
const updater = readRepoFile("scripts/release/update-homebrew-tap.sh");
const versionDeclaration = 'version "__VERSION__"';
expect(formula).toContain(
'assert_equal "v#{version}", shell_output("#{bin}/kandev --version").strip',
@@ -370,7 +372,11 @@ describe("release desktop artifacts", () => {
expect(formula).toContain("/health");
expect(formula).toContain('"status":"ok"');
expect(formula).toContain("<title>Kandev</title>");
expect(formula).not.toContain('version "__VERSION__"');
expect(formula).toContain(versionDeclaration);
expect(formula.indexOf(versionDeclaration)).toBeLessThan(
formula.indexOf('license "AGPL-3.0-only"'),
);
expect(updater).toContain('-e "s|__VERSION__|$VERSION|g"');
});
it("bumps desktop package and Tauri versions during release preparation", () => {
+8 -8
View File
@@ -18,12 +18,12 @@ Kandev uses one semantic version across the Git tag, native runtime bundles, des
The workflow has four mutually exclusive operating modes:
| Mode | Inputs | Result |
|---|---|---|
| Normal release | `bump=patch`, `minor`, or `major` | Creates and merges a release PR, tags its merge, builds, and publishes |
| Dry run | `dry_run=true` | Computes the next version and exercises CLI package/lock plus changelog generation in the runner; no PR, tag, artifact build, or publication |
| Desktop validation | `desktop_validation_only=true` | Builds web, five runtime bundles, and five desktop targets from the selected commit; no PR, tag, GitHub release, GHCR, npm, or Homebrew publication |
| Backfill | `backfill_tag=vX.Y.Z` | Rebuilds and repairs channels for the latest existing release tag without creating a version or tag |
| Mode | Inputs | Result |
| ------------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Normal release | `bump=patch`, `minor`, or `major` | Creates and merges a release PR, tags its merge, builds, and publishes |
| Dry run | `dry_run=true` | Computes the next version and exercises CLI package/lock plus changelog generation in the runner; no PR, tag, artifact build, or publication |
| Desktop validation | `desktop_validation_only=true` | Builds web, five runtime bundles, and five desktop targets from the selected commit; no PR, tag, GitHub release, GHCR, npm, or Homebrew publication |
| Backfill | `backfill_tag=vX.Y.Z` | Rebuilds and repairs channels for the latest existing release tag without creating a version or tag |
`dry_run` and desktop validation are not release candidates. Backfill cannot be combined with either and accepts only the latest exact SemVer tag after version manifests are checked for agreement.
@@ -65,7 +65,7 @@ Normal mode performs these stages:
5. **Build containers.** Publish amd64/arm64 base manifests, enforce the universal-image size gate, then publish multi-architecture universal images.
6. **Publish GitHub Release.** Attach runtime archives, checksums, desktop artifacts, notes, and the updater feed when eligible.
7. **Publish npm.** Use OIDC trusted publishing for five `@kdlbs/runtime-*` packages first, then the `kandev` launcher. Existing versions are skipped; the main package is not published after a runtime-package failure.
8. **Update Homebrew.** Push the formula update to `kdlbs/homebrew-kandev` using release checksums and the deploy key.
8. **Update Homebrew.** Push the formula update to `kdlbs/homebrew-kandev` using release checksums and the deploy key. The generated formula records `X.Y.Z` explicitly; platform archive suffixes such as `x64` are not version identifiers.
GHCR images are built before the GitHub Release. npm and Homebrew start only after the GitHub Release and may run in parallel. A late failure can therefore leave some channels complete and others missing.
@@ -105,7 +105,7 @@ After publication, verify:
- the Git tag points at the generated release merge;
- GitHub notes, five runtime archives, checksums, expected desktop installers, and conditional `latest.json`;
- all five runtime npm packages plus `kandev`, including a clean `npx kandev@latest`;
- Homebrew install/upgrade and `kandev --version`;
- Homebrew install/upgrade, a `Cellar/kandev/X.Y.Z` install path, and `kandev --version`;
- GHCR base and universal images on amd64 and arm64, including their immutable version tags;
- desktop launch on affected platforms and signed/notarized status where configured;
- backend health, a minimal task, agentctl startup, and Updates-screen behavior;
+3 -2
View File
@@ -17,12 +17,12 @@ Kandev is currently installable via `brew install kdlbs/kandev/kandev` from the
## What
- Add a package-manager build contract that produces the native runtime bundle from an already-installed source checkout. It builds the Vite SPA, syncs it into the Go embed input, compiles the host `kandev` and `agentctl` binaries, and cross-compiles the four Linux/macOS remote `agentctl` helpers.
- Keep package-manager dependency installation outside that contract. Homebrew declares Go, Node 24, and pnpm as build dependencies and Git as a runtime facility.
- Keep package-manager dependency installation outside that contract. Homebrew declares Go, Node 24, and pnpm 10 as build dependencies. Git is part of Homebrew's standard environment and is not declared as a formula dependency.
- Compile only the host `kandev` binary with cgo and the `fts5` tag. `mattn/go-sqlite3` supplies the SQLite amalgamation, so the formula does not declare an external SQLite dependency unless build or linkage evidence requires it.
- Merge and publish the source-build contract in a Stable Kandev release before authoring the Core formula. Core must consume the immutable GitHub tag archive for that release, never a branch or unreleased commit.
- Install the bundle under `libexec/bin` and expose one `bin/kandev` wrapper produced by `write_env_script`. The wrapper sets `KANDEV_BUNDLE_DIR=<libexec>` and `KANDEV_VERSION=<version>`.
- Use stable numeric tags for `livecheck`; prerelease and Nightly npm versions are not Homebrew channels.
- Keep `kdlbs/homebrew-kandev` as the upstream binary fast path alongside Homebrew Core's source-built bottles. The shared release-bundle validator must run before those binary archives are published, and the generated tap formula must smoke-test its version, readiness endpoint, and embedded SPA.
- Keep `kdlbs/homebrew-kandev` as the upstream binary fast path alongside Homebrew Core's source-built bottles. The generated tap formula must set the Stable SemVer explicitly because platform archive names end in architecture tokens such as `x64`, and it must smoke-test its version, readiness endpoint, and embedded SPA. The shared release-bundle validator runs before those binary archives are published.
- Preserve all four remote `agentctl` helpers in custom-tap installations. The tap must use an exact-path Homebrew mismatched-binary audit allowlist rather than pruning helpers, so Docker and SSH targets can differ from the Homebrew host. Decision: [ADR-2026-08-05-homebrew-remote-helper-audit](../../decisions/2026-08-05-homebrew-remote-helper-audit.md).
The runtime bundle contains exactly:
@@ -44,6 +44,7 @@ The Darwin arm64 helper must carry a Mach-O code signature so Apple Silicon can
- **GIVEN** a new kandev release `vX.Y.Z` is tagged, **WHEN** Homebrew's auto-bump worker runs, **THEN** `livecheck` resolves the new tag from GitHub Releases and a bump PR is opened against the formula.
- **GIVEN** a maintainer reviews the PR, **WHEN** they run `brew install --build-from-source kandev` locally, **THEN** the build completes without network or sandbox failures and `brew test kandev` passes.
- **GIVEN** a Stable release updates `kdlbs/homebrew-kandev`, **WHEN** its platform archive is built and the tap formula is tested, **THEN** the archive contains the complete executable runtime and the installed launcher serves both `/health` and the embedded Kandev page.
- **GIVEN** a Stable release updates `kdlbs/homebrew-kandev`, **WHEN** Homebrew evaluates a platform archive URL ending in `x64` or `arm64`, **THEN** the generated formula's explicit version keeps the Cellar path and `version` value at `X.Y.Z` rather than the architecture suffix.
- **GIVEN** a tap archive contains remote helpers for CPU architectures other than the Homebrew host, **WHEN** Homebrew audits the installed formula on macOS or Linux, **THEN** only the four declared remote-helper paths are exempted and the complete runtime remains installed.
## Out of scope
+2
View File
@@ -3,6 +3,8 @@
class Kandev < Formula
desc "Manage tasks, orchestrate agents, review changes, and ship value"
homepage "https://github.com/kdlbs/kandev"
# Conditional asset names end in x64/arm64; without this, Homebrew uses version 64.
version "__VERSION__"
license "AGPL-3.0-only"
on_macos do
+1
View File
@@ -118,6 +118,7 @@ mkdir -p "$(dirname "$FORMULA_PATH")"
GITHUB_BASE="https://github.com/kdlbs/kandev/releases/download/${TAG}"
sed \
-e "s|__VERSION__|$VERSION|g" \
-e "s|__GITHUB_BASE__|$GITHUB_BASE|g" \
-e "s|__SHA_MACOS_ARM64__|$SHA_MACOS_ARM64|g" \
-e "s|__SHA_MACOS_X64__|$SHA_MACOS_X64|g" \