Builder 可从 Windows、Linux 或任何平台构建和开发 iOS 应用。Builder 是一个不需要 Mac 的 iOS 开发 CLI 工具,它使用 G…
Builder 可从 Windows、Linux 或任何平台构建和开发 iOS 应用。Builder 是一个不需要 Mac 的 iOS 开发 CLI 工具,它使用 G…
Build and develop iOS apps from Windows, Linux, or any platform.
Builder is a CLI tool for iOS development without a Mac. It uses GitHub Actions (default), Codemagic, or Bitrise for remote builds and MobAI for on-device development.
Your Repository GitHub Actions (macOS)
└─ .github/workflows/ └─ ios-build.yml
└─ ios-build.yml ├─ Check out the snapshot
├─ Build with Xcode
builder ios build ───────────────────► Upload IPA artifact
│ pushes a snapshot of
│ your working tree
└─ Downloads IPA ◄─────────────── artifact: ipa
builder ios build builds what is on disk, not your last commit: uncommitted
and untracked files are included, so you can try a change without committing
it. The snapshot is a throwaway commit pushed to a hidden ref that is deleted
when the build finishes; no branch is created and nothing is committed on your
behalf. .gitignore still applies, so ignored files such as .env or
GoogleService-Info.plist are absent from the build.
builder auth github
cd your-ios-project
builder init
This detects your GitHub repo, creates the workflow files, and offers to commit, push, and trigger your first build - all interactively.
builder ios build
The CLI triggers the workflow and downloads the IPA to ./dist/.
builder ios share
Builds the working tree for the iOS simulator and makes that simulator usable
from the MobAI app, so you can tap through a build without
a Mac. It shows up under CI Devices, stays available while you are using it, and
closes when you release it there or leave it unused (30 minutes by default, use
--duration to change). A coding agent connected to MobAI (Claude Code, Codex,
Cursor) can drive the simulator the same way.
Free with any MobAI account, on MobAI 3.0 or later. Needs
a MOBAI_API_KEY repository secret: create the key in the MobAI app under
Account → API Keys, then:
gh secret set MOBAI_API_KEY
Where the GitHub API is not reachable, both workflows can also be started by pushing a tag. Commit the tree you want built, then:
git tag ios-build/my-build && git push origin ios-build/my-build # IPA build
git tag ios-share/my-build && git push origin ios-share/my-build # simulator
The run is named after the tag. Build settings come from builder.json in the
tagged commit (ios.path, ios.scheme, ios.signing, ios.configuration,
flutter.version, kmp.jdkVersion), the simulator stays available for the
default 30 minutes, and the tag is deleted when the run ends. The IPA is
attached to the run as an artifact. A tag carries no flags, so a tagged IPA
build cannot pick a profile per run; it applies the profile
named by defaultProfile, if there is one. The simulator build takes no
profile at all.
GitHub Actions remains the default, so existing commands continue to work. Add Codemagic and Bitrise without logging out of GitHub. First follow the app creation and repository connection guide to create each provider app, authorize GitHub access, and find its app ID:
builder auth codemagic
builder auth bitrise
builder auth status
builder init --provider codemagic --app-id YOUR_APP_ID --branch main
builder init --provider bitrise --app-id YOUR_APP_SLUG --branch main
builder ios build --provider codemagic
builder ios build --provider bitrise
init writes codemagic.yaml or bitrise.yml at the repo root plus the shared
runner script .builder/ci/runner.sh. Commit them to the configured branch
and connect the same repository to each provider before building. See
provider setup, signing, simulator sessions, and free allowances.
| Framework | iOS Path | Auto-detected |
|---|---|---|
| Native iOS/Swift | . (root) |
Yes |
| React Native | ios/ |
Yes |
| Expo (managed or ejected) | ios/ |
Yes |
| Flutter | ios/ |
Yes |
| Kotlin Multiplatform | iosApp/ |
Yes |
| Cordova/Ionic | platforms/ios/ |
Yes |
The runner installs JavaScript dependencies with the package manager the project
already uses — npm, Yarn, pnpm or Bun, from packageManager in package.json or
from the lockfile — on the Node version from .nvmrc, .node-version or
engines.node.
Dependencies install the way they do for React Native: the project's own package
manager (npm, Yarn, pnpm or Bun) and Node version, with expo prebuild running
through that same manager.
A managed Expo project has no ios/ directory in git. builder init detects it
as Expo (managed), still records "ios": { "path": "ios" }, and the runner
generates the native project with expo prebuild --platform ios --no-install
before building it. Ejected projects keep the committed ios/ they have: the
prebuild step skips a directory that already holds an Xcode project.
expo prebuild has to run unattended, so the app config must set the bundle
identifier — expo.ios.bundleIdentifier in app.json, or ios.bundleIdentifier
in app.config.js / app.config.ts. Without one, prebuild would stop and ask
for it; instead the build fails immediately and names the missing setting.
The default Debug configuration builds an IPA that loads its JavaScript from
Metro, so set "ios": { "configuration": "Release" } in builder.json for a
standalone IPA with the bundle baked in.
An ios/ directory left over from running expo prebuild locally is not
uploaded: managed projects gitignore it, and the working-tree snapshot skips
gitignored files. That is what you want — the runner prebuilds from the app
config on every build, so it cannot drift from a stale local copy.
Download builder-windows-amd64.exe from Releases, rename it to builder.exe, and add it to PATH.
brew install mobai-app/tap/ios-builder
The formula is named ios-builder; the command it installs is builder.
curl -sSL https://raw.githubusercontent.com/MobAI-App/ios-builder/main/install.sh | bash
git clone https://github.com/MobAI-App/ios-builder.git
cd ios-builder
go build -o builder ./cmd/builder
…
Every release/upload/submit/asc command takes --json for
machine-readable output and never prompts, so agents and CI jobs can drive them.
builder.json:
{
"project": "MyApp",
"platform": "ios",
"github": {
"owner": "username",
"repo": "my-ios-app"
},
"ios": {
"path": "ios",
"scheme": "",
"bundleId": "com.example.app",
"configuration": "Debug"
},
"profiles": {
"development": { "distribution": "development" },
"store": { "distribution": "store" }
},
"mobai": {
"url": "http://localhost:8686",
"device_id": ""
},
"flutter": {
"watch": {
"dirs": ["lib"],
"patterns": [".dart"],
"ignore": [".g.dart", ".freezed.dart"],
"debounce": 100
}
}
}
| Field | Description | Default |
|---|---|---|
ios.path |
Path to the Xcode project relative to the repo root | detected by init |
ios.scheme |
Xcode scheme to build | auto-detected |
ios.bundleId |
App bundle identifier, used by signing setup and by ios release to find the App Store Connect app before the first IPA exists |
detected by init when the project has one app target; else saved by signing setup, else the newest IPA in ./dist/ |
ios.extensions |
Bundle identifiers of the app's extension targets (widgets, share/notification extensions, watch apps, app clips), each signed with its own profile | filled by init and signing setup from the Xcode project; list them by hand for a managed Expo project |
ios.configuration |
Xcode build configuration. Builds are Debug unless you set Release; Debug is faster and is what the dev commands expect |
Debug |
ios.signing |
Legacy: sign builds that select no profile, with the unsuffixed IOS_CERTIFICATE, IOS_CERTIFICATE_PASSWORD and IOS_PROVISIONING_PROFILE secrets. Profiles ignore it; use distribution there |
false |
Profiles are named sets of build settings, in the spirit of eas.json, selected
with --profile on ios build:
{
"ios": { "path": "ios", "bundleId": "com.example.app" },
"defaultProfile": "development",
"profiles": {
"development": { "distribution": "development" },
"preview": { "distribution": "internal",
"env": { "API_URL": "https://staging.example.com" } },
"production": { "distribution": "store", "scheme": "MyApp", "provider": "codemagic" }
}
}
builder ios build --profile preview
| Field | Description |
|---|---|
distribution |
The only signing setting: development, ad-hoc (or internal, the same thing), store or enterprise. The build signs with that distribution's signing set and its provisioning profile must be of that type; the IPA is exported with the matching method. Omitted means an unsigned build |
configuration |
Overrides the derived configuration: Debug for development, Release for every other distribution, ios.configuration for unsigned profiles |
scheme |
Overrides ios.scheme |
provider |
Overrides the top-level provider (github, codemagic, bitrise) |
env |
String map exported as environment variables on the runner before dependencies are installed and the app is built, so pod install, npm install, flutter pub get, Gradle and xcodebuild all see them |
How a build's settings are resolved:
--profile, the profile named by defaultProfile applies. With
neither, the top-level ios.* and provider settings are used exactly as
before, so existing projects are unaffected.--unsigned and --provider on the command line override the profile.ios build only. ios share takes no --profile:
simulator builds are always Debug and unsigned, and use ios.scheme and the
top-level provider (or --provider).env values are build-time configuration, not secrets. They are stored in
builder.json, sent to the CI provider as plain workflow inputs, and visible in
the run's inputs and logs. Keep tokens and passwords in the provider's secrets
(gh secret set on GitHub, or the [Codemagic / Bitrise secrets
guide](docs/provider-secre
暂无开放 Issues,或尚未同步最近议题。