Build and release
Building
Section titled “Building”bun run build # ./dist/slackcli for the current platformbun run build:linux # bun-linux-x64 → dist/slackcli-linuxbun run build:macos # bun-darwin-x64 → dist/slackcli-macosbun run build:windows # bun-windows-x64 → dist/slackcli-windows.exebun run build:all # all threeAll of them go through scripts/build.ts, a thin wrapper around
bun build --compile --minify whose one real job is injecting the version:
--define __APP_VERSION__=<version from package.json>A local (untargeted) build also gets --sourcemap; cross-compiled targets do
not.
Versioning
Section titled “Versioning”src/version.ts resolves the version in one place:
export function getAppVersion(): string { if (typeof __APP_VERSION__ !== 'undefined') return __APP_VERSION__; // compiled binary return packageJson.version; // running from source}isRunningUnderBun() distinguishes a source run from a compiled binary. It is
what disables the self-updater and the “update available” notice during
development.
package.json version is the single source of truth. The release workflow
refuses to build if the pushed tag disagrees with it.
Two workflows run on every push and PR to main.
ci.yml
workflow-lint— runs the pinnedactionlintpre-commit hook over.github/workflows/. This exists because an invalid workflow file is a startup failure: GitHub cannot parse it, so it never resolves theon:triggers, the run carries no logs, and its scheduled runs are never created.stale.ymlshipped that way and never ran once. CI runs the same hook id as.pre-commit-config.yaml, so local and CI cannot drift.test— install,bun run type-check,bun run build, verify the binary answers--versionand--help, and enforce the 150 MB binary size budget.
test.yml — bun test, plus an integration job that builds the binary and
smoke-tests it.
Other policy workflows: pr-linked-issue.yml (a PR must link an open issue),
signed-commits.yml, and stale.yml.
Why Bun is pinned
Section titled “Why Bun is pinned”CI and the release workflow both pin Bun 1.3.13. Bun 1.3.12 produced corrupt macOS code signatures (oven-sh/bun#29120). Bump the pin deliberately, in both files at once, after reading Bun’s release notes.
Why the 150 MB budget matters
Section titled “Why the 150 MB budget matters”It is not cosmetic — it is the constraint that shaped auth login-auto. A
bundled Playwright would blow the budget, which is why cdp-client.ts exists as
a hand-rolled DevTools Protocol client instead. Anything that would add tens of
megabytes needs a different design, not a raised limit.
Releasing
Section titled “Releasing”Releases are triggered by pushing a v*.*.* tag, and the repo has a /release
skill that drives the whole sequence. By hand it is:
-
Open the release issue and get it labelled
ready-for-pr(the constitution applies to releases too). -
On a branch: bump
versioninpackage.json, promote the Unreleased section ofCHANGELOG.md. -
Open the PR linking that issue; merge once green.
-
Push the annotated tag from
main:Terminal window git tag -a v0.9.2 -m "v0.9.2" && git push origin v0.9.2
release.yml then:
verify-version— fails fast if the tag does not matchpackage.json, so binaries can never ship with a version baked in that drifts from source.build— a matrix of five targets:linux-x64,linux-arm64,darwin-x64,darwin-arm64,windows-x64.release— collects the artefacts, generateschecksums.txtwithsha256sum, and publishes a GitHub Release with generated notes.update-homebrew— mints a short-lived token from a GitHub App scoped toshaharia-lab/homebrew-taponly, and updates the formula there.
Permissions are least-privilege throughout: the workflow default is
contents: read, and only the release job opts into contents: write.
If a tag was pushed with the wrong version, fix package.json on main, then
delete and re-push the tag — the error message from verify-version says the
same.
The self-updater
Section titled “The self-updater”src/lib/updater.ts backs slackcli update. Three behaviours worth knowing
before you change it:
- It fails closed on verification: the release asset’s digest must be a
sha256:digest published by GitHub and must match what was downloaded. A missing or unexpected digest aborts the update rather than installing an unverified binary. - It refuses to act when installed via Homebrew (detected from the exec path
containing
homebrew,Cellar, orlinuxbrew) or when running under Bun. - The background check runs at most every 24 hours, caches to
~/.config/slackcli/update-check.json, and prints its notice to stderr onbeforeExit— so it never contaminates--jsonon stdout.