Repository navigation
Add Homebrew install for the macOS app - #91
Conversation
Users can tap this repo and `brew install --cask convt` for the signed Apple silicon app. A non-blocking Release job rewrites the cask from release-manifest.json so a tap failure cannot fail publication. Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
| homebrew_spec=spec_from_file_location('homebrew_cask',Path(__file__).with_name('homebrew_cask.py')) | ||
| homebrew=module_from_spec(homebrew_spec); homebrew_spec.loader.exec_module(homebrew) | ||
| source=Path(__file__).resolve().parents[2]/'Casks'/'convt.rb' | ||
| text(Path('homebrew/convt.rb'),homebrew.bump_cask(source.read_text(),version=version,sha256=mac['sha256'])) |
There was a problem hiding this comment.
🔴 Staged cask loses its artifact URL
For a manifest hosted at downloads.convt.app, templates.py emits a GitHub DMG URL instead of the artifact URL. Releases published only to the manifest's host leave the staged cask unable to install.
Learn more
The package-manager renderer takes an artifact record from a release manifest. The release script generates that manifest with https://downloads.convt.app as its default base URL. The cask template hard-codes a GitHub release URL, and bump_cask changes only its version and digest. Rendering therefore changes the download host even when the manifest points to a working artifact elsewhere.
Example: A 0.3.0 manifest lists https://downloads.convt.app/0.3.0/convt-macos-arm64.dmg. The generated cask points to https://github.com/opencoredev/convt/releases/download/v0.3.0/convt-macos-arm64.dmg; if the DMG was uploaded only to the download host, Homebrew fails to install it.
Recommended fix: Render the staged cask from mac['url'], preserving the exact manifest artifact URL. If the in-repository GitHub cask must remain the source template, give bump_cask an explicit URL override and use it when templates.py renders a manifest-backed cask.
Was this helpful? React with 👍 or 👎 to provide feedback.
| if changed and args.commit: | ||
| message = f"chore(homebrew): bump convt to {version}" | ||
| if homebrew.commit_if_changed(repo, cask, message): | ||
| print(f"committed {cask.relative_to(repo)}") |
There was a problem hiding this comment.
🟡 Failed cask commit blocks tap update
If commit_if_changed fails, main exits before publish_tap runs. The valid tap stays on the previous release despite its independent token and write access.
Learn more
The updater first writes a new cask to its local checkout, then optionally commits it to the source repository and publishes it to a separate Homebrew tap. Only errors from push_head are caught inside the source-repository branch. An error from commit_if_changed instead reaches the outer handler, skipping the independent tap publication. This matters when the repository commit fails but the tap credentials and repository work.
Example: The new release is 0.3.0 and HOMEBREW_TAP_TOKEN can update the tap. If git commit fails in the source checkout, the script returns 1 without cloning or updating the tap, so tap users remain on 0.2.0.
Recommended fix: Handle commit and push errors within the optional source-repository operation, record them, and continue to publish_tap using the already-written cask. Report both operation results afterward.
Was this helpful? React with 👍 or 👎 to provide feedback.
| </div> | ||
| <DownloadButton artifact={slot?.artifact ?? null} large /> | ||
| {slot?.artifact && <Sha value={slot.artifact.sha256} />} | ||
| {os === "macos" && <HomebrewInstall />} |
There was a problem hiding this comment.
🔍 Homebrew instructions remain visible without downloads
When the macOS download says “Shipping today,” HomebrewInstall still offers an install command. Confirm whether that fallback is intended before the first published release or during manifest outages.
Was this helpful? React with 👍 or 👎 to provide feedback.
| expect(page).toContain("brew install --cask convt"); | ||
| expect(page).toContain("brew tap opencoredev/convt"); |
There was a problem hiding this comment.
|
| expect(page).toContain("brew install --cask convt"); | ||
| expect(page).toContain("brew tap opencoredev/convt"); |
There was a problem hiding this comment.
Missing instructions escape tests
The new assertions only search the route’s source text. They still pass if both HomebrewInstall calls are removed or the commands stop appearing on the page. Add a rendered-output test for the Mac install block so the test protects the behavior users see.
Confidence: 5/5.
Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
Derive the committed cask version and digest in tests so a later release bump does not fail CI. Always honor --push even when the cask file already matches, and keep tap publication independent of the source-repo commit. Mock the missing-tap clone. Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
| "depends_on macos:", | ||
| 'app "convt.app"', | ||
| "zap trash:", | ||
| "convt-macos-arm64.dmg", |
There was a problem hiding this comment.
templates.py now rejects valid Mac download URLs whose filenames include the version, such as the existing fixture convt-0.1.0-macos-arm64.dmg. After bump_cask copies that URL, this required string makes validate_cask(bumped) raise an error. The script stops before writing the cask or later package indexes.
Check the cask’s shape without requiring the GitHub-only filename. Confidence: 5/5.
| "convt-macos-arm64.dmg", |
| repo = work / "tap" | ||
| dest = repo / "Casks" / "convt.rb" | ||
| dest.parent.mkdir(parents=True, exist_ok=True) | ||
| dest.write_text(cask.read_text()) |
There was a problem hiding this comment.
Old retries replace new releases
Retrying just Update Homebrew cask for an older release can undo a newer cask update. The job keeps its original RELEASE_VERSION, but publish_tap clones the current tap and overwrites its cask without comparing versions. The updater also overwrites the current cask on main. New Homebrew installs can then use the older app.
Skip older incoming versions in both repositories before writing or pushing them. Confidence: 4/5.
| if url is not None: | ||
| bumped, count = re.compile(r'^(\s*url\s+")([^"]+)(")', re.M).subn( | ||
| rf"\g<1>{url}\g<3>", bumped, count=1 | ||
| ) |
There was a problem hiding this comment.
bump_cask changes url to the manifest’s download location but leaves verified: "github.com/opencoredev/convt/" behind, including for downloads.convt.app. The generated cask names the wrong verified download source, so release managers must correct the file.
Update or remove verified when replacing the URL. Confidence: 5/5.
Pass the manifest DMG URL through to bump_cask so a retry cannot leave the cask pointing at an old host. Refuse a version older than the checked-in cask, and commit only the cask file so leftover staged paths stay out of the source-repo commit. Co-authored-by: Leo <leoisadev1@users.noreply.github.com>
| current, _ = cask_fields(text) | ||
| if version_key(version) < version_key(current): | ||
| raise ValueError(f"refusing to downgrade cask from {current} to {version}") |
There was a problem hiding this comment.
Once Casks/convt.rb advances past 0.3.0, this new check breaks several release tests. They read the current cask and call bump_cask with the fixed version 0.3.0, so they raise before reaching their assertions. CI runs these tests on pull requests.
Use a fixed cask fixture for tests that need specific versions. Confidence: 5/5.
| version, sha256, url = release_fields(args) | ||
| bumped = homebrew.bump_cask(text, version=version, sha256=sha256, url=url) |
There was a problem hiding this comment.
Manifest updates now replace the cask’s interpolated URL with a literal release URL. After that, the documented --version --sha256 command returns url=None. It changes the version and checksum but keeps the previous release’s URL. Publishing that cask makes Homebrew download the old app and reject its checksum.
Preserve URL interpolation where possible, or require a matching URL for manual updates. Confidence: 5/5.
One-time steps for Leo
I could not create
opencoredev/homebrew-tapfrom here (ghis read-only). Until that repo exists, the working install is:To get the shorter
brew install --cask opencoredev/tap/convtcommand:opencoredev/homebrew-tap(empty is fine; the first successful release job will addCasks/convt.rband a short README).HOMEBREW_TAP_TOKEN: a fine-grained PAT (or classicrepotoken) that can push toopencoredev/homebrew-tap.mainis protected againstGITHUB_TOKENpushes, either allow GitHub Actions to push cask-only commits, or rely on the tap repo only and ignore the in-repoCasks/convt.rbbump.No other secrets are required. The new Release job never fails the publication itself (
continue-on-error: true).What this does
Casks/convt.rbfor the signed, notarized Apple silicon DMG from the latest GitHub release (v0.2.0,convt-macos-arm64.dmg). There is no Intel or universal Mac build today, so the cask isdepends_on arch: :arm64.livecheck(:github_latest),zap, and links the bundledconvtCLI ontoPATH.gh release createsucceeds, a new Update Homebrew cask job readsrelease-manifest.json, rewrites version + sha256, commits to this repo, and (ifHOMEBREW_TAP_TOKENis set) pushes the same file toopencoredev/homebrew-tap. A missing tap, a 404, or a blocked push cannot fail the Release workflow./downloadnow show thebrew tap/brew install --casklines. The download-page change is a small add so it should not fight PR Redesign /download as a get-the-app install guide #20 or docs: describe the shipped desktop app and cloud API in README and AGENTS.md #77.Skipped
No Linux Homebrew formula. An AppImage is a poor Homebrew formula, and Linux already has
.deb,.rpm, and the AppImage. Revisit if we later ship a small relocatable tarball.Release artifact names are unchanged. The Release workflow change is one additive job after publish, so it should merge cleanly next to PR #73.
Checks
python3 -m unittest discover -s scripts/release -p 'test_*.py'— 32 tests, OKbrew style --cask— clean (via a throwaway local tap; Homebrew rejects a raw path)brew audit --cask— cleanbrew audit --cask --online— downloaded the v0.2.0 DMG and verified the sha256; then failed only because this Linux box has nohdiutilto mount the image. macOS CI runs style + offline audit.bun run checkandbun run check-types— OKbun test apps/web/test/unit/download.test.tsx— OKDo not merge; a reviewer should.