The Problem
iOS development from non-Mac platforms requires orchestrating multiple tools—GitHub Actions for remote builds, MobAI for on-device development, and platform-specific build commands. The CLI consolidates this but has structural issues that increase cognitive load when modifying build logic, and CI hygiene problems that risk credential leakage and unpatched dependencies.
What This Does
ios-builder is a Go CLI tool (45 files, 28 Go source files) that builds iOS apps (native, Flutter, React Native) from Windows, Linux, or WSL. It uses GitHub Actions for remote macOS builds and MobAI for hot-reload development on real devices. The codebase has 147 functions and 41 classes across packages: cmd/builder (entry point), internal/dev (Flutter/React Native handling), internal/config (configuration), internal/github (API client), and internal/mobai (device coordination).
Key entry points traced via the internal call graph: runDevFlutter (cmd/builder/flutter.go:43) reaches 48 functions and is called by nothing else in the repo—it is the primary dispatcher for Flutter development workflows. runDevReactNative (cmd/builder/rn.go:39) reaches 49 functions for React Native. runInit (cmd/builder/root.go:179) reaches 16 functions for project initialization. runSigningSetup (cmd/builder/signing.go:45) reaches 14 functions for code signing configuration. The call graph shows 181 resolved self-edges; Stop is called from 13 places, Load from 11, and NewManager from 9, indicating these are central hubs where changes ripple widely. internal/dev/flutter.go defines NewFlutterHandler, Setup, Attach, DebugConfig, Stop and is called from 7 files; internal/dev/session.go defines NewSession, SetSkipInstall, FindIPA, Start, Stop and is called from 5 files; internal/github/client.go defines NewClient, request, do, EncryptSecret, Client and is called from 7 files, handling all GitHub API interaction including token management and workflow dispatch.
How It Is Wired
Execution starts at cmd/builder/main.go which delegates to command-specific runners in cmd/builder/. The runDevFlutter function in cmd/builder/flutter.go orchestrates the Flutter development cycle: it creates a NewFlutterHandler (defined in internal/dev/flutter.go), begins file watching via NewFileWatcher (in internal/dev/watcher.go), and conditionally runs Start or Attach based on whether an app is already installed. File watching uses a polling watcher (internal/dev/watcher_poll.go) with 6-line duplicated blocks across watcher.go, watcher_poll.go, cmd/builder/flutter.go, and cmd/builder/rn.go—extraction of a shared helper would DRY the logic. When the user runs builder dev rn, it routes to runDevReactNative which similarly sets up a NewReactNativeHandler (internal/dev/reactnative.go) and handles Metro port configuration. Build workflows are dispatched through internal/github/client.go which calls request and do to interact with the GitHub API, ultimately triggering .github/workflows/ios-build.yml on macOS runners. Code signing secrets are managed through cmd/builder/signing.go and stored via internal/auth/auth.go functions GetToken and storeToken.
How To Use It
Setup:
# Authenticate with GitHub
builder auth github
# Initialize in your iOS project directory
cd your-ios-project
builder init
Configuration: Create builder.json in the project root with fields for project, platform, github.owner/repo, ios.path, ios.signing, and mobai.url/device_id. The config file is loaded by loadConfig in cmd/builder/root.go.
Running it:
# Build IPA via GitHub Actions
builder ios build
# Build without signing (if configured)
builder ios build --unsigned
# Flutter hot-reload on device
builder dev flutter
# React Native hot-reload
builder dev rn
Code signing:
builder signing setup
Real-World Use
A team developing a Flutter app on Windows uses builder auth github to register a GitHub token, then builder init to create .github/workflows/ios-build.yml and commit it. Developers run builder dev flutter for hot-reload on connected iOS devices; the CLI watches configured directories (default lib/ with .dart patterns) and sends file changes to the MobAI server at http://localhost:8686. When ready for a release build, builder ios build triggers the GitHub Action, which checks out code, builds with Xcode, and uploads the IPA to ./dist/. The team can then download the artifact or distribute it via TestFlight.
Code Health & Issues
The static analysis found 7 findings across 3 severity levels:
- [HIGH] Pin third-party GitHub Actions to a commit SHA -
.github/workflows/ci.ymlandrelease.ymlreferencegolangci/golangci-lint-action@v7andsoftprops/action-gh-release@v1without pinned SHAs. A tag move would execute unvetted code with repository secrets. - [MEDIUM] Declare least-privilege permissions for GITHUB_TOKEN - 1 workflow declares no
permissionsblock; the token inherits repository defaults, allowing steps to push commits or mint releases using the repository's own token. - [MEDIUM] Enable Dependabot or Renovate - 1 manifest, no update bot configured. Without automated dependency updates, known-vulnerable packages remain unpatched.
- [MEDIUM] Gate pull requests on a dependency vulnerability scan - No dependency scan in CI. A known-vulnerable package could reach production without detection.
- [MEDIUM] Move large binaries to Git LFS or out of the repository -
assets/ios-builder-demo.gifis 13.3MB; one clone and CI checkout transfer ~13MB of uncompressed media that nobody diffs. - [MEDIUM] Set persist-credentials: false on checkout -
checkoutstep keeps the token in.git/configfor later steps; a malicious postinstall script could read pushable credentials without explicit passage. - [LOW] Set timeout-minutes on the workflow jobs - 2 workflows declare no job timeout; a wedged step runs to the six-hour platform default.
The Bottom Line
This is a functional CLI for cross-platform iOS development that successfully abstracts GitHub Actions and MobAI coordination. The Go codebase is well-structured with clear entry points and a resolvable call graph, but duplicated watcher logic, deep nesting in cmd/builder/mobai.go, and high branching density in internal/github/workflow.go increase modification risk. The CI hygiene issues—unpinned Action versions, missing token permissions, and a large uncompressed demo GIF—are concrete risks for any organization using repository secrets. Teams already invested in MobAI and GitHub Actions for iOS builds will get immediate value, but should pin Action SHAs, add permissions: contents: read to workflows, and migrate the demo GIF to Git LFS before promoting this to a shared team tool.