Files
zero/packaging/homebrew/zero.rb
Vasanth T 01c3498205 feat(update): recognise Homebrew installs and add mise, go install docs (#910)
* feat(update): recognise Homebrew installs and add mise, go install docs

* fix(update): match the keg shape, correct the docs, cover Apply and cross-target

* test(update): build the release fixture from the running platform

* fix(update): require a Homebrew receipt before refusing self-update

Review found the keg check was shape-only, and it was right: a directory
tree a user built themselves can have exactly a keg's shape. Classifying
one as Homebrew refuses a `zero upgrade` that would have worked
perfectly, on a machine Homebrew has never touched.

Two changes, cheap one first. The shape rule now requires the full
<prefix>/Cellar/<formula>/<version>/bin/<binary> depth rather than one
segment fewer, which already excludes the exact path the review raised.
Then, only for a path that still looks like a keg, one Stat for
INSTALL_RECEIPT.json — the file Homebrew writes into every keg and
nothing else does.

The receipt is what the old comment argued against, on the grounds that
it would put filesystem I/O on a path that runs at every version check.
That is no longer true of it: the Stat happens only after the shape test
has already matched, so an ordinary install still reaches no filesystem
here at all.

A directory named INSTALL_RECEIPT.json is not a receipt, which a bare
Stat would have accepted silently.

NOT taken: the same review asked for the Homebrew branch to be moved
ahead of the cross-target one in upgradeGuidance. That ordering is
deliberate and already pinned by
TestUpgradeGuidanceKeepsCrossTargetAnswerForHomebrew, whose comment
predicted exactly this reading — "it reads like an ordering bug until you
see which question is being answered". Homebrew is a property of the
binary on THIS machine; when the check was asked about a different
target the answer belongs to that other machine, and `brew upgrade zero`
would change this one instead. The reasoning is now recorded at the
branch as well as in the test, so the next reader finds it in both
places.

The install docs claimed detection was the Cellar path alone, which
undersold it, and stated the self-update rule absolutely in one paragraph
before excepting mise in the next. Both say the same thing now.

* test(update): give the Homebrew fixtures a receipt, and stop gating the receipt rule on the host

The previous commit made a receipt the thing that distinguishes a keg from any
directory sitting under a path containing "Cellar". Two fixtures still built a
keg without one, so detection correctly answered standalone and both failed:

  TestDetectInstallMethodRecognisesAHomebrewKeg  keg path: got "standalone", want "homebrew"
  TestApplyRefusesToUpdateAHomebrewKeg           refusal does not name the command that works:
                                                 download release archive: dial tcp: lookup example.test

The second is worth reading twice: with detection returning standalone, Apply
stopped refusing and went to the network, so the failure surfaced as a DNS error
rather than as a missing refusal. Both fixtures write a receipt now.

The reason this reached CI at all is the second half. Every test of the receipt
rule passed runtime.GOOS to isHomebrewPath, which already takes the target as a
parameter, so all of them skipped on Windows and the rule could only ever break
somewhere else. They pass an explicit target now and run everywhere. The two
that exercise DetectInstallMethod stay host-gated, because those are about the
wired entry point rather than the rule, but the rule underneath them is no
longer invisible here.

Verified the receipt path arithmetic on both non-Windows targets rather than
trusting the host-gated tests: isHomebrewPath is false before the receipt is
written and true after, with the receipt at the keg directory hasHomebrewReceipt
actually reads.
2026-08-21 12:33:21 +08:00

66 lines
2.4 KiB
Ruby

# Homebrew formula for Zero.
#
# NOT PUBLISHED. This is a draft for review. Nothing installs from it until it
# is copied into a tap (Gitlawb/homebrew-tap, Formula/zero.rb) and the release
# workflow is taught to bump the version and checksums on each tag. Both of
# those are release decisions, so they are deliberately not made here.
#
# Verify locally without a tap:
#
# brew install --build-from-source ./packaging/homebrew/zero.rb
# brew audit --strict --formula ./packaging/homebrew/zero.rb
#
# The checksums below are the real ones published with v0.7.0, read from the
# .sha256 files that ship beside each archive, so this draft is installable as
# written rather than being a skeleton with placeholders.
class Zero < Formula
desc "Terminal coding agent"
homepage "https://github.com/Gitlawb/zero"
version "0.7.0"
license "MIT"
on_macos do
on_arm do
url "https://github.com/Gitlawb/zero/releases/download/v0.7.0/zero-v0.7.0-macos-arm64.tar.gz"
sha256 "75e859fe25f3f63785f512f20b9c9501c67394166f10746f80e374672a1a8b7f"
end
on_intel do
url "https://github.com/Gitlawb/zero/releases/download/v0.7.0/zero-v0.7.0-macos-x64.tar.gz"
sha256 "184256abd5738b77d44cf4a99d71ac32d0a0355714ebc698a2221a69aeb71976"
end
end
on_linux do
on_arm do
url "https://github.com/Gitlawb/zero/releases/download/v0.7.0/zero-v0.7.0-linux-arm64.tar.gz"
sha256 "dd0355f78b6ab044e1181184e29432d0ab7652a1dc27a161960f06e8520b4f21"
end
on_intel do
url "https://github.com/Gitlawb/zero/releases/download/v0.7.0/zero-v0.7.0-linux-x64.tar.gz"
sha256 "f5120c2cc1e9f45ebf69d472b6026eb8e37eee2c113211efedd7d0917437490c"
end
end
def install
bin.install "zero"
# The Linux archive carries the sandbox helper beside the binary. Without it
# on PATH, native sandboxing is silently unavailable rather than broken, so
# install it whenever the archive provides one.
bin.install "zero-linux-sandbox" if File.exist?("zero-linux-sandbox")
end
def caveats
<<~EOS
Update with `brew upgrade zero`.
`zero upgrade` refuses on a Homebrew install on purpose: replacing the keg
binary would leave Homebrew describing a version that is no longer on disk,
and the next `brew upgrade` would revert it.
EOS
end
test do
assert_match version.to_s, shell_output("#{bin}/zero --version")
end
end