Table of Contents
- The problem
- Design decisions and the alternatives
- What the first two days changed
- How a new version reaches the tap
- Using it
- Limits
The problem
podexit and sdkbump are command line tools written in TypeScript and published on npm as
@mcamdali/podexit and @mcamdali/sdkbump. For a Node.js developer npx @mcamdali/podexit is
enough. Both tools are for iOS projects, so they usually run on a Mac, where Homebrew is a common
way to install command line tools. A formula also gives brew upgrade and brew uninstall without
extra work.
A tap is the lightest way to offer that. It is a git repository named homebrew-<name>;
brew tap muhittincamdali/tap clones muhittincamdali/homebrew-tap, and its Formula/ directory
becomes installable. The tap was opened on 14 October 2026 with the podexit formula. By the next
morning it held three: podexit, sdkbump and sizeledger.
The question this tap had to answer is narrow: what exactly should a formula download for a tool
whose real home is the npm registry?
Design decisions and the alternatives
One tarball, built once. Both tools have a release workflow that runs lint, type check, tests
and build, installs the packed tarball in a smoke test, and only then runs npm pack. The same
step writes a checksum file:
1npm pack --ignore-scripts --pack-destination "$RUNNER_TEMP/release"2cp "$RUNNER_TEMP/notes.md" "$RUNNER_TEMP/release/notes.md"3(cd "$RUNNER_TEMP/release" && sha256sum ./*.tgz > SHA256SUMS)That tarball is attached to the GitHub release together with SHA256SUMS, and the same file is
handed to npm publish --provenance. Nothing is packed twice, so the GitHub copy and the npm copy
are the same bytes. On 15 October 2026 the release download and the registry download had the same
SHA-256 for both podexit 0.1.2 (2a0655c9…) and sdkbump 0.1.3 (2f5e8df0…).
Why point the formula at the release, not only at the registry. The podexit 0.1.0 formula was
written before podexit was on npm at all; the commit that added it says so, and says that the
release workflow will move the formula to the registry tarball once the package is there. The
GitHub release URL exists the moment the tag is released, it comes with a published checksum, and
it does not depend on the npm publish step succeeding. Both generators reflect this:
sdkbump's scripts/homebrew-formula.ts writes the release URL, while podexit's writes the registry
URL. Because the bytes are identical, either URL is correct for the same sha256.
Let Homebrew's npm helpers do the install. The formula itself is short. This is
Formula/podexit.rb as it is in the tap:
1class Podexit < Formula2 desc "Show which CocoaPods stop getting updates when trunk goes read-only"3 homepage "https://github.com/muhittincamdali/podexit"4 url "https://github.com/muhittincamdali/podexit/releases/download/v0.1.2/mcamdali-podexit-0.1.2.tgz"5 sha256 "2a0655c9751e8b849016d10e91fdf6a408999f1adb4ac7f1ec232b3e3783a0f7"6 license "MIT"7 8 depends_on "node"9 10 def install11 system "npm", "install", *std_npm_args12 bin.install_symlink libexec.glob("bin/*")13 endstd_npm_args installs the package with its dependencies into the formula's libexec,
and the symlink puts the podexit command on the PATH. depends_on "node" uses Homebrew's own
Node.js, so the tool does not depend on whichever Node.js version the user has installed.
A test block that runs the real tool. Homebrew runs test do after installing. A test that
only checks --version would pass even if the bundled files were incomplete, so podexit's test
writes a small Podfile.lock with one local pod and runs an offline report on it:
1 output = shell_output("#{bin}/podexit --offline --all --cache-dir #{testpath}/cache")2 assert_match "not affected", outputsdkbump's test runs the command on an empty directory and expects exit code 2 with the message
No iOS project found. sizeledger's test writes a 5,000-byte file into a Demo.app and expects
"bytes" : 5000 in the JSON output. None of the tests needs the network.
The alternative for compiled tools. sizeledger is written in Swift and ships prebuilt
binaries, so its formula has no depends_on. It chooses an archive with OS.mac? and
Hardware::CPU.intel? checks (macOS universal, Linux x86_64 or Linux aarch64), because Homebrew's
style rules do not allow url inside on_macos or on_linux blocks; a comment in the formula
says so.
What the first two days changed
The tap's workflow runs brew test-bot on every push and pull request: --only-tap-syntax for
style and brew audit --strict --online, and on pull requests --only-formulae, which builds the changed formulae from source and runs their
tests. Three changes in the first two days came from real problems; test-bot found two of them.
Linux could not resolve node. The first version of the workflow ran the Linux job on the
plain Ubuntu runner. There, brew test-bot --only-formulae stopped with "Failed to determine
dependencies" for any formula that depends on node. Homebrew's own tap template, the one
brew tap-new writes, runs Linux inside the ghcr.io/homebrew/brew container instead. Commit
c391558 switched to that layout:
1strategy:2 matrix:3 os: [macos-26]4 include:5 - os: ubuntu-latest6 container:7 image: ghcr.io/homebrew/brew:main8 options: --privileged9runs-on: ${{ matrix.os }}10container: ${{ fromJSON(toJSON(matrix.container)) }} # zizmor: ignore[unpinned-images]The same commit pinned Homebrew/actions/setup-homebrew to a commit SHA instead of @main. The rest
of the file now follows what brew tap-new generates: --only-cleanup-before, --only-setup and
--only-tap-syntax on every run, --only-formulae on pull requests. Every step runs under
bash -xeuo pipefail, so a failing command stops the job and the log shows each command. The
workflow grants nothing at the top level (permissions: {}) and gives the job read access to
actions, checks, contents and pull requests only.
The scoped package changed the file name. podexit 0.1.2 was the first version published under
the scope @mcamdali. npm pack names a scoped package <scope>-<name>-<version>.tgz, so the
release asset became mcamdali-podexit-0.1.2.tgz and the formula URL had to follow. sdkbump's
release workflow renames its tarball back to sdkbump-<version>.tgz, with a comment noting that it
is the same file, so its URLs keep the earlier shape.
A redundant version line. The first sizeledger formula declared version "0.1.0". The
pull request run failed on brew audit: "version 0.1.0 is redundant with version scanned from URL".
Every archive name already ends in sizeledger-0.1.0-<platform>.tar.gz, so Homebrew reads the
version from it. Commit 4876203 removed the line; the test block still compares --version with
version.to_s.
How a new version reaches the tap
Each tool's release workflow has a homebrew job. It waits for the tarball, computes the SHA-256,
writes the formula with the tool's scripts/homebrew-formula.ts, and uploads it as an artifact
named homebrew-formula. If the repository has a HOMEBREW_TAP_TOKEN secret, the job also clones
the tap, commits the formula on a branch and opens a pull request; test-bot then checks it like any
other change. Without the secret the job stops with a notice and the formula stays in the artifact.
That fallback was used for sizeledger: its repository has no HOMEBREW_TAP_TOKEN, so the formula
the release workflow wrote was added to the tap by a manual pull request, as the commit message
records.
Using it
1brew tap muhittincamdali/tap2brew install podexitor in one step:
1brew install muhittincamdali/tap/podexitThe same works for sdkbump and sizeledger.
Limits
- This is a personal tap, not Homebrew core. Formulae are reviewed only by this repository's CI.
- podexit and sdkbump need Homebrew's
node, which is installed as a dependency if it is missing. - The podexit formula in the tap still uses the GitHub release URL, while podexit's generator
writes the registry URL. Both serve the same file, so an update written by podexit's release workflow would change the URL but not the checksum.
- The tap has no tags or releases of its own; its history is the formula commits.
- test-bot runs macOS only on the
macos-26image (Apple silicon).
Source code: github.com/muhittincamdali/homebrew-tap
Tags
iOS Development News
Weekly Swift tips, SwiftUI tricks and iOS best practices. No spam, only valuable content.
Your subscription starts when you open the link in the confirmation email and press “Confirm my subscription”. The newsletter keeps open/click statistics; you can unsubscribe anytime with one click. Privacy

