From ea125d548e6cff476e837727092dc3261d77fede Mon Sep 17 00:00:00 2001 From: Dimitri Kennedy Date: Wed, 2 Sep 2026 10:33:36 -0400 Subject: [PATCH 1/3] fix(release): pin Ghostty source revision --- .github/workflows/release-macos-app.yml | 14 +++++++-- .../GhosttyVTBridge/GHOSTTY_REVISION | 1 + apps/macos/README.md | 4 ++- scripts/macos-ghostty-setup.ts | 27 +++++++++++++---- tests/macos-ghostty-source.test.ts | 29 +++++++++++++++++++ 5 files changed, 67 insertions(+), 8 deletions(-) create mode 100644 apps/macos/Experiments/GhosttyVTBridge/GHOSTTY_REVISION create mode 100644 tests/macos-ghostty-source.test.ts diff --git a/.github/workflows/release-macos-app.yml b/.github/workflows/release-macos-app.yml index a19fdcf1..19b39fc3 100644 --- a/.github/workflows/release-macos-app.yml +++ b/.github/workflows/release-macos-app.yml @@ -146,15 +146,25 @@ jobs: VENDOR_DIR="apps/macos/vendor/ghostty" BRIDGE_DIR="apps/macos/Experiments/GhosttyVTBridge" + REVISION_FILE="$BRIDGE_DIR/GHOSTTY_REVISION" OUT_DIR="apps/macos/App/GhosttyVT/ghostty/lib" OUT_LIB="$OUT_DIR/libhack_ghostty_vt.dylib" + GHOSTTY_REVISION="$(tr -d '[:space:]' < "$REVISION_FILE")" if [ ! -d "$VENDOR_DIR" ]; then mkdir -p "$(dirname "$VENDOR_DIR")" - git clone --depth 1 https://github.com/ghostty-org/ghostty "$VENDOR_DIR" + git clone --filter=blob:none --no-checkout https://github.com/ghostty-org/ghostty "$VENDOR_DIR" fi - git -C "$VENDOR_DIR" rev-parse --short HEAD + git -C "$VENDOR_DIR" fetch --depth 1 origin "$GHOSTTY_REVISION" + git -C "$VENDOR_DIR" checkout --detach --force FETCH_HEAD + + ACTUAL_GHOSTTY_REVISION="$(git -C "$VENDOR_DIR" rev-parse HEAD)" + if [ "$ACTUAL_GHOSTTY_REVISION" != "$GHOSTTY_REVISION" ]; then + echo "Expected Ghostty $GHOSTTY_REVISION, checked out $ACTUAL_GHOSTTY_REVISION" + exit 1 + fi + echo "Ghostty revision: $ACTUAL_GHOSTTY_REVISION" mkdir -p "$OUT_DIR" diff --git a/apps/macos/Experiments/GhosttyVTBridge/GHOSTTY_REVISION b/apps/macos/Experiments/GhosttyVTBridge/GHOSTTY_REVISION new file mode 100644 index 00000000..e9889ef7 --- /dev/null +++ b/apps/macos/Experiments/GhosttyVTBridge/GHOSTTY_REVISION @@ -0,0 +1 @@ +53bd14fecfd68c6c0ab64d37b5943247299e2b40 diff --git a/apps/macos/README.md b/apps/macos/README.md index d8346697..178bc771 100644 --- a/apps/macos/README.md +++ b/apps/macos/README.md @@ -95,7 +95,9 @@ bun run macos:ghostty:setup This script: -- clones the Ghostty repo into `apps/macos/vendor/ghostty` +- checks out the pinned Ghostty revision from + `apps/macos/Experiments/GhosttyVTBridge/GHOSTTY_REVISION` into + `apps/macos/vendor/ghostty` - builds the VT bridge via Zig - installs `libhack_ghostty_vt.dylib` into: `~/Library/Application Support/Hack/ghostty/lib` diff --git a/scripts/macos-ghostty-setup.ts b/scripts/macos-ghostty-setup.ts index 2ce7083b..c5534513 100644 --- a/scripts/macos-ghostty-setup.ts +++ b/scripts/macos-ghostty-setup.ts @@ -7,6 +7,10 @@ import { $ } from "bun"; const repoRoot = path.resolve(import.meta.dir, ".."); const vendorDir = path.join(repoRoot, "apps/macos/vendor/ghostty"); const bridgeDir = path.join(repoRoot, "apps/macos/Experiments/GhosttyVTBridge"); +const ghosttyRevision = readFileSync( + path.join(bridgeDir, "GHOSTTY_REVISION"), + "utf8" +).trim(); const installDir = path.join( process.env.HOME ?? "", "Library/Application Support/Hack/ghostty/lib" @@ -44,11 +48,24 @@ const _isAtLeast = ( return current.patch >= min.patch; }; -if (existsSync(vendorDir)) { - await $`git -C ${vendorDir} fetch --depth 1 origin main`; - await $`git -C ${vendorDir} reset --hard origin/main`; -} else { - await $`git clone --depth 1 https://github.com/ghostty-org/ghostty ${vendorDir}`; +if (!/^[0-9a-f]{40}$/.test(ghosttyRevision)) { + throw new Error("GHOSTTY_REVISION must contain one full Git commit SHA"); +} + +if (!existsSync(vendorDir)) { + await $`git clone --filter=blob:none --no-checkout https://github.com/ghostty-org/ghostty ${vendorDir}`; +} + +await $`git -C ${vendorDir} fetch --depth 1 origin ${ghosttyRevision}`; +await $`git -C ${vendorDir} checkout --detach --force FETCH_HEAD`; + +const checkedOutRevision = ( + await $`git -C ${vendorDir} rev-parse HEAD`.text() +).trim(); +if (checkedOutRevision !== ghosttyRevision) { + throw new Error( + `Expected Ghostty ${ghosttyRevision}, checked out ${checkedOutRevision}` + ); } const minVersionMatch = readFileSync( diff --git a/tests/macos-ghostty-source.test.ts b/tests/macos-ghostty-source.test.ts new file mode 100644 index 00000000..7f463c9e --- /dev/null +++ b/tests/macos-ghostty-source.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, test } from "bun:test"; +import path from "node:path"; + +const repoRoot = path.resolve(import.meta.dir, ".."); +const bridgeDir = path.join(repoRoot, "apps/macos/Experiments/GhosttyVTBridge"); + +describe("Ghostty source pin", () => { + test("uses one immutable revision in local and release builds", async () => { + const revision = ( + await Bun.file(path.join(bridgeDir, "GHOSTTY_REVISION")).text() + ).trim(); + const setupScript = await Bun.file( + path.join(repoRoot, "scripts/macos-ghostty-setup.ts") + ).text(); + const releaseWorkflow = await Bun.file( + path.join(repoRoot, ".github/workflows/release-macos-app.yml") + ).text(); + + expect(revision).toMatch(/^[0-9a-f]{40}$/); + expect(setupScript).toContain('path.join(bridgeDir, "GHOSTTY_REVISION")'); + expect(releaseWorkflow).toContain( + 'REVISION_FILE="$BRIDGE_DIR/GHOSTTY_REVISION"' + ); + expect(setupScript).not.toContain("fetch --depth 1 origin main"); + expect(releaseWorkflow).not.toContain( + "git clone --depth 1 https://github.com/ghostty-org/ghostty" + ); + }); +}); From 3c9c3d36a625a1944c7115eb48d3bd85cede67c3 Mon Sep 17 00:00:00 2001 From: Dimitri Kennedy Date: Wed, 2 Sep 2026 10:39:52 -0400 Subject: [PATCH 2/3] fix(release): enforce Ghostty pin in local bundle --- scripts/macos-ghostty-bundle.ts | 17 +++++++++++++++++ tests/macos-ghostty-source.test.ts | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/scripts/macos-ghostty-bundle.ts b/scripts/macos-ghostty-bundle.ts index 9bec10e9..e9812cd0 100644 --- a/scripts/macos-ghostty-bundle.ts +++ b/scripts/macos-ghostty-bundle.ts @@ -7,6 +7,10 @@ import { $ } from "bun"; const repoRoot = path.resolve(import.meta.dir, ".."); const vendorDir = path.join(repoRoot, "apps/macos/vendor/ghostty"); const bridgeDir = path.join(repoRoot, "apps/macos/Experiments/GhosttyVTBridge"); +const ghosttyRevision = readFileSync( + path.join(bridgeDir, "GHOSTTY_REVISION"), + "utf8" +).trim(); const outDir = path.join(repoRoot, "apps/macos/App/GhosttyVT/ghostty/lib"); const outLib = path.join(outDir, "libhack_ghostty_vt.dylib"); @@ -75,6 +79,19 @@ const main = async (): Promise => { ); } + if (!/^[0-9a-f]{40}$/.test(ghosttyRevision)) { + throw new Error("GHOSTTY_REVISION must contain one full Git commit SHA"); + } + + const checkedOutRevision = ( + await $`git -C ${vendorDir} rev-parse HEAD`.text() + ).trim(); + if (checkedOutRevision !== ghosttyRevision) { + throw new Error( + `Ghostty ${checkedOutRevision} is checked out; expected ${ghosttyRevision}. Run \`bun run macos:ghostty:setup\` to restore the pinned revision.` + ); + } + const minVersionMatch = readFileSync( path.join(vendorDir, "build.zig.zon"), "utf8" diff --git a/tests/macos-ghostty-source.test.ts b/tests/macos-ghostty-source.test.ts index 7f463c9e..f72fe2fa 100644 --- a/tests/macos-ghostty-source.test.ts +++ b/tests/macos-ghostty-source.test.ts @@ -12,12 +12,17 @@ describe("Ghostty source pin", () => { const setupScript = await Bun.file( path.join(repoRoot, "scripts/macos-ghostty-setup.ts") ).text(); + const bundleScript = await Bun.file( + path.join(repoRoot, "scripts/macos-ghostty-bundle.ts") + ).text(); const releaseWorkflow = await Bun.file( path.join(repoRoot, ".github/workflows/release-macos-app.yml") ).text(); expect(revision).toMatch(/^[0-9a-f]{40}$/); expect(setupScript).toContain('path.join(bridgeDir, "GHOSTTY_REVISION")'); + expect(bundleScript).toContain('path.join(bridgeDir, "GHOSTTY_REVISION")'); + expect(bundleScript).toContain("git -C ${vendorDir} rev-parse HEAD"); expect(releaseWorkflow).toContain( 'REVISION_FILE="$BRIDGE_DIR/GHOSTTY_REVISION"' ); From 4ace6be2192fc50a5f7166c1c26e917c536ace2c Mon Sep 17 00:00:00 2001 From: Dimitri Kennedy Date: Wed, 2 Sep 2026 10:46:44 -0400 Subject: [PATCH 3/3] fix(release): reset pinned Ghostty source --- scripts/macos-ghostty-bundle.ts | 2 ++ tests/macos-ghostty-source.test.ts | 3 +++ 2 files changed, 5 insertions(+) diff --git a/scripts/macos-ghostty-bundle.ts b/scripts/macos-ghostty-bundle.ts index e9812cd0..618354d8 100644 --- a/scripts/macos-ghostty-bundle.ts +++ b/scripts/macos-ghostty-bundle.ts @@ -92,6 +92,8 @@ const main = async (): Promise => { ); } + await $`git -C ${vendorDir} reset --hard ${ghosttyRevision}`; + const minVersionMatch = readFileSync( path.join(vendorDir, "build.zig.zon"), "utf8" diff --git a/tests/macos-ghostty-source.test.ts b/tests/macos-ghostty-source.test.ts index f72fe2fa..de97446d 100644 --- a/tests/macos-ghostty-source.test.ts +++ b/tests/macos-ghostty-source.test.ts @@ -23,6 +23,9 @@ describe("Ghostty source pin", () => { expect(setupScript).toContain('path.join(bridgeDir, "GHOSTTY_REVISION")'); expect(bundleScript).toContain('path.join(bridgeDir, "GHOSTTY_REVISION")'); expect(bundleScript).toContain("git -C ${vendorDir} rev-parse HEAD"); + expect(bundleScript).toContain( + "git -C ${vendorDir} reset --hard ${ghosttyRevision}" + ); expect(releaseWorkflow).toContain( 'REVISION_FILE="$BRIDGE_DIR/GHOSTTY_REVISION"' );