Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .githooks/pre-push
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ set -uo pipefail

[ ! -f scripts/run_tests.sh ] && { echo "⚠️ no scripts/run_tests.sh"; exit 0; }

bash scripts/run_tests.sh
env -u CMUX_SOCKET_PATH -u CMUX_DAEMON_SOCKET bash scripts/run_tests.sh
exit_status=$?

if [ "$exit_status" -ne 0 ]; then
Expand Down
19 changes: 16 additions & 3 deletions docs/releases-and-brew.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,21 @@ One command does the whole pipeline:

It will: verify a clean tree + green build/tests, bump `package.json`, commit,
push `main`, tag `vX.Y.Z`, then update the Homebrew formula's `url` + `sha256`
in `~/Gits/homebrew-layers` and push the tap. Afterwards:
in `~/Gits/homebrew-layers` and push the tap. Afterwards, run the verification
helper with the released version:

```bash
brew update && brew upgrade etanhey/layers/cmuxlayer
~/Gits/cmuxlayer/scripts/release-verify.sh 0.3.0
```

The helper fetches and hard-resets the tap clone Homebrew actually reads at
`$(brew --repository)/Library/Taps/etanhey/homebrew-layers` to `origin/main`,
runs `brew upgrade etanhey/layers/cmuxlayer`, and fails unless
`brew list --versions cmuxlayer` prints exactly the released version. This
explicit sync is required because that tap's `origin` can be the local
`~/Gits/homebrew-layers` checkout while its `main` has no upstream tracking, so
`brew update` alone does not guarantee a fast-forward.

Manual equivalent, if you prefer:

1. `package.json` → bump `version`.
Expand All @@ -107,7 +116,10 @@ Manual equivalent, if you prefer:
5. In `~/Gits/homebrew-layers/Formula/cmuxlayer.rb` set `url` to the new tag and
`sha256` to the value from step 4; `brew audit etanhey/layers/cmuxlayer`;
commit + push.
6. `brew upgrade etanhey/layers/cmuxlayer`.
6. Sync the tap Homebrew reads:
`git -C "$(brew --repository)/Library/Taps/etanhey/homebrew-layers" fetch origin && git -C "$(brew --repository)/Library/Taps/etanhey/homebrew-layers" reset --hard origin/main`.
7. `brew upgrade etanhey/layers/cmuxlayer`.
8. Assert `brew list --versions cmuxlayer` is exactly `cmuxlayer X.Y.Z`.

The formula also carries a `head` block, so `--HEAD` installs always track
`main` with **no** sha/tag bump — that is the on-the-go dogfood path.
Expand Down Expand Up @@ -197,3 +209,4 @@ explicit `workspace` only when you deliberately want a different one.
| `~/.golems/bin/cmuxlayer-mcp` | launcher: brew (default) vs live source (`CMUXLAYER_DEV=1`) |
| `EtanHey/homebrew-layers` → `Formula/cmuxlayer.rb` | the brew formula (stable tag + `head`) |
| `scripts/release.sh` | one-command release: bump → tag → formula bump → push |
| `scripts/release-verify.sh` | sync Homebrew's tap clone → upgrade → assert installed version |
22 changes: 22 additions & 0 deletions scripts/release-verify.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
# Sync the tap clone Homebrew actually reads, upgrade cmuxlayer, and verify it.
set -euo pipefail

die() { echo "release-verify: $*" >&2; exit 1; }

VERSION="${1:-}"
[ -n "$VERSION" ] || die "usage: release-verify.sh <X.Y.Z>"
[[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || die "version must be semver X.Y.Z, got '$VERSION'"
command -v brew >/dev/null 2>&1 || die "brew is not installed"

BREW_TAP_DIR="$(brew --repository)/Library/Taps/etanhey/homebrew-layers"
[ -d "$BREW_TAP_DIR/.git" ] || die "Homebrew tap clone not found at $BREW_TAP_DIR"

git -C "$BREW_TAP_DIR" fetch origin
git -C "$BREW_TAP_DIR" reset --hard origin/main
brew upgrade etanhey/layers/cmuxlayer

INSTALLED="$(brew list --versions cmuxlayer)"
[ "$INSTALLED" = "cmuxlayer $VERSION" ] || die "expected cmuxlayer $VERSION, got '${INSTALLED:-not installed}'"

echo "release-verify: cmuxlayer $VERSION is installed from the synced tap"
6 changes: 3 additions & 3 deletions scripts/release.sh
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
#
# Steps: clean-tree + green build/tests gate → bump package.json → commit +
# push main → tag vX.Y.Z + push tag → update formula url+sha256 in the
# homebrew-layers tap → push tap → tell you to `brew upgrade`.
# homebrew-layers tap → push tap → tell you to run release verification.
#
# See docs/releases-and-brew.md.
set -euo pipefail
Expand Down Expand Up @@ -50,7 +50,7 @@ fi

echo "release: gating on typecheck + tests…"
run "bun run typecheck"
run "bun run test"
run "env -u CMUX_SOCKET_PATH -u CMUX_DAEMON_SOCKET bun run test"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Unset the actual daemon socket env var

This removes CMUX_DAEMON_SOCKET, but the codebase reads CMUXLAYER_DAEMON_SOCKET for daemon socket overrides (src/daemon-socket-path.ts and the existing nightly contract wrapper use that name). When a developer has CMUXLAYER_DAEMON_SOCKET exported, the release gate still inherits the live daemon override, so the supposedly hermetic bun run test path can bind/probe the ambient daemon socket; the pre-push hook has the same typo. Please unset CMUXLAYER_DAEMON_SOCKET here and in .githooks/pre-push.

Useful? React with 👍 / 👎.

echo "release: gating on real-cmux contracts (warn-only skip when no live CMUX_SOCKET_PATH is reachable)…"
run "bun run test:contract"

Expand Down Expand Up @@ -98,7 +98,7 @@ cat <<EOF

release: done — cmuxlayer $TAG is tagged and the formula is bumped.
Next:
brew update && brew upgrade etanhey/layers/cmuxlayer
$REPO_DIR/scripts/release-verify.sh "$VERSION"

# Outbox-semantics releases only (see docs/releases-and-brew.md "Pre-deploy hygiene"):
# if this release changes outbox dedup-id derivation or the delivery gate,
Expand Down
32 changes: 31 additions & 1 deletion tests/pre-pr-scripts.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,13 +46,43 @@ describe("pre-PR script ladder", () => {

it("runs the opt-in contract lane from the release preflight", () => {
const release = readFileSync(join(repoRoot, "scripts", "release.sh"), "utf8");
const hermeticGate = release.indexOf('run "bun run test"');
const hermeticGate = release.indexOf(
'run "env -u CMUX_SOCKET_PATH -u CMUX_DAEMON_SOCKET bun run test"',
);
const contractGate = release.indexOf('run "bun run test:contract"');

expect(hermeticGate).toBeGreaterThan(-1);
expect(contractGate).toBeGreaterThan(hermeticGate);
});

it("removes ambient cmux socket pins from the pre-push regression gate", () => {
const hook = readFileSync(
join(repoRoot, ".githooks", "pre-push"),
"utf8",
);

expect(hook).toContain(
"env -u CMUX_SOCKET_PATH -u CMUX_DAEMON_SOCKET bash scripts/run_tests.sh",
);
});

it("syncs the brew tap clone and verifies the installed release version", () => {
const verify = readFileSync(
join(repoRoot, "scripts", "release-verify.sh"),
"utf8",
);
const release = readFileSync(join(repoRoot, "scripts", "release.sh"), "utf8");

expect(verify).toContain(
'BREW_TAP_DIR="$(brew --repository)/Library/Taps/etanhey/homebrew-layers"',
);
expect(verify).toContain('git -C "$BREW_TAP_DIR" fetch origin');
expect(verify).toContain('git -C "$BREW_TAP_DIR" reset --hard origin/main');
expect(verify).toContain("brew upgrade etanhey/layers/cmuxlayer");
expect(verify).toContain("brew list --versions cmuxlayer");
expect(release).toContain('scripts/release-verify.sh "$VERSION"');
});

it("refuses the live harness unless CMUX_LIVE_HARNESS=1 is set", () => {
const result = spawnSync(
process.execPath,
Expand Down
Loading