Troupe Cloud
Builds
Troupe Cloud builds an app from a git ref or an uploaded tarball and keeps the result as an artifact that a
device installs or you download. This page covers sources, targets, the expo and xcode presets and their
options, logs and artifacts.
How a build runs
- Probe. A sandbox fetches the source and reads what it is: the commit, the app roots, the frameworks. Nothing of yours runs yet.
- Plan. A preset turns those facts and your request into a list of typed steps, pinned to the probed commit.
- Build. A fresh build machine runs the steps and uploads the outputs.
- Artifact. Troupe computes each output's SHA-256 itself and records it as an artifact.
| Status | Meaning |
|---|---|
awaitingSource | A tarball build waits for its upload |
queued | Waiting to be inspected, or for a build machine |
starting, running | On a build machine; statusDetail names the running step |
succeeded | Its artifacts are ready |
failed | Your project's build failed, or the source cannot be built; failure says why |
errored | Troupe's side failed; the build was not at fault |
canceled | Someone canceled it |
Start a build
In the web app, open a project and choose Start a build: the repository URL and ref, the platform, the target and the configuration. More options holds the app root, the preset and its options, the timeout and the caches. The build's page then shows its status, its phases, its artifacts and its log as it is written.
Over the API it is builds.create, which needs builds:write.
Every role holds it.
curl https://api.troupe.ali.ve/v1/builds/create \
--header "Authorization: Bearer $TROUPE_API_KEY" \
--header "Troupe-Contract: 1.0" \
--header "Content-Type: application/json" \
--data '{
"projectId": "prj_…",
"source": { "kind": "git", "url": "https://github.com/acme/shop", "ref": "main" },
"osFamily": "ios",
"target": "simulator"
}'The answer is the build, with its buildId (bld_…) and the status queued. Read it again with
builds.get until it ends:
until curl --silent "https://api.troupe.ali.ve/v1/builds/get?projectId=prj_…&buildId=bld_…" \
--header "Authorization: Bearer $TROUPE_API_KEY" |
jq --exit-status '.build.status | test("succeeded|failed|errored|canceled")' > /dev/null
do sleep 10; doneSources
A git ref. url is an https:// address the build can read without credentials: no user name or
password, no query, no IP address. ref is one of:
| Ref | Built as |
|---|---|
A branch name, main | refs/heads/main |
A full ref, refs/tags/v1.2.0 | That ref |
| A commit id | That commit |
A bare name is always a branch, so a tag called main is never built by mistake. A name git would read as
something else, such as tags/v1 or HEAD, is refused: write the full ref. Submodules and Git LFS are not
supported yet; a request that sets submodules or lfs is refused.
A tarball. For a private repository or a working tree, upload the source. The web app starts builds from a repository only; a tarball build goes through the API:
- Call builds.create with
"source": { "kind": "tarball" }. The build isawaitingSource, and the answer carries an upload session with itsuploadId. - Call uploads.grant with the
uploadId, the namesource.tar.gz, the content typeapplication/gzip, the file's size inbytesand itssha256. It returns a signedPUTrequest: a URL and headers. - Send the file with that request.
- Call uploads.complete. The build moves to
queued.
The tarball is a gzipped tar of the source tree, with the tree's root at the archive's root, of at most 2 GiB. The upload must finish within an hour. Uploaded sources are kept for 7 days.
Targets
osFamily | target | Output | Preset |
|---|---|---|---|
ios | simulator | A simulator app, as a gzipped tar | expo, xcode |
ios | store | An .ipa for App Store Connect, signed as it is built | expo |
android | emulator | An APK | expo |
android | deviceInternal | An APK for internal distribution | expo |
android | store | An AAB for Play | expo |
No preset builds an iOS deviceInternal target yet: such a build fails with target-unsupported.
configuration is release or debug. The expo preset builds release unless you ask otherwise: the
JavaScript bundle is embedded, so the app runs with no bundler. debug is offered for the simulator and the
emulator and expects a bundler. The xcode preset builds debug by default.
A store or deviceInternal build must say what it signs with: a signing credential's id in signing, or
"none". An iOS store build cannot be "none". See Signing credentials.
Presets
A preset recognises a kind of project and knows the steps that build it. Leave preset out and the probe's
facts choose one; name it to be sure.
expo
For an Expo app: an app root whose package.json depends on expo. The preset accepts Expo SDK 53 to 58;
another SDK fails with sdk-unsupported.
| Step | What it does |
|---|---|
checkout | The probed commit |
install | Dependencies from the committed lockfile (Bun, pnpm, Yarn or npm), frozen, with lifecycle scripts off |
fingerprint | The project's Expo fingerprint, recorded in the build's metadata |
prebuild | expo prebuild for the platform, clean by default |
pods, then xcode.build-sim | iOS simulator: CocoaPods, then xcodebuild for the generic iOS Simulator |
gradle.assemble or gradle.bundle | Android: the project's own Gradle wrapper builds the APK or the AAB |
verify | iOS: reads the built app's bundle id, versions and URL schemes into the artifact's metadata |
upload | Stores the output |
A source without a committed lockfile fails. An emulator build compiles native code for arm64-v8a unless
architectures says otherwise.
| Option | Meaning |
|---|---|
scheme | The Xcode scheme to build; by default the generated workspace's one scheme |
clean | Regenerate the native project from scratch; true by default |
architectures | Android: the ABIs to build native code for, comma-separated, from armeabi-v7a, arm64-v8a, x86 and x86_64 |
xcode
For a native iOS app: an Xcode project or workspace in the source, or an XcodeGen spec that generates one. It
builds the iOS simulator target only, and is chosen only where the source holds no Expo or React Native app.
The build is the project's own xcodebuild: there is no install or CocoaPods step, so a project that uses
CocoaPods commits its Pods directory. Swift packages resolve from GitHub.
| Option | Meaning |
|---|---|
project | The .xcodeproj, .xcworkspace or XcodeGen spec (.yml) to build, relative to the app root; by default the only one there |
scheme | The scheme to build; by default the project's one shared scheme, else its own name |
A React Native app that does not use Expo has no preset yet.
Options
All of these are optional fields of builds.create.
| Field | Meaning |
|---|---|
appRoot | The app's directory inside the source, such as apps/mobile; detected when left out |
configuration | release or debug |
preset, presetOptions | The preset and its options. Options are checked at once when you name the preset, otherwise when the build is planned |
timeoutMs | The longest the build may run: 60000 to 10800000 (3 hours), 3600000 (1 hour) by default |
cache | { "restore": true, "save": true } by default; a store build neither restores nor saves unless asked |
trigger | The branch, defaultBranch or pullRequest that caused the build; it scopes the dependency caches |
signing | A signing credential's id or "none"; required for store and deviceInternal, refused for the others |
env | Up to 100 environment values of up to 8192 characters for the native steps, never for install |
stepOverrides | Up to 16 changes to the planned steps: insert before or after, replace, remove |
Values in env are never stored or returned: the build keeps only their names, in envNames, and its log
masks the values. They can end up inside the app if your project compiles them in. Names the build machine
sets itself, such as PATH, HOME, NODE_OPTIONS and JAVA_HOME, are refused.
What a build can reach
A build's network is limited to the hosts its preset lists, on port 443, and the host of its own source.
| Preset | Hosts |
|---|---|
expo | The npm registry, the CocoaPods CDN, React Native's repository, Maven Central and GitHub; for Android also Gradle's distributions and plugin portal, Google's Maven and JitPack |
xcode | GitHub, for Swift packages |
A dependency from a private registry, or from another host, fails the step that fetches it.
Follow a build
builds.logs replays the log and then follows it, as an event stream:
curl --no-buffer "https://api.troupe.ali.ve/v1/builds/logs?projectId=prj_…&buildId=bld_…" \
--header "Authorization: Bearer $TROUPE_API_KEY" \
--header "Accept: text/event-stream"Each line carries a seq, a time, the step's id, the stream it came from (stdout, stderr, runner or
controller) and the text. A follow ends when the build ends, and after 40 minutes at the latest: open it
again with since set to the last seq you read. follow=false returns what is there and ends.
When a build fails, failure holds a reason, a message and, when a step failed, its stepId.
reason | Meaning |
|---|---|
unknown-ref, source-not-found, source-unreachable | The source could not be fetched |
not-detected | No preset recognises the source, or the app root is not an app |
target-unsupported, configuration-unsupported, sdk-unsupported | The preset does not build this |
preset-options, step-override | An option or an override does not fit the preset |
install-failed, prebuild-failed, pods-failed, xcodebuild-failed, gradle-failed | That step failed; read its log |
timed-out | The build ran longer than timeoutMs |
signing-refused, signing-failed | The credential may not sign this build, or signing failed |
builds.cancel ends an unfinished build and stops its machine. builds.list lists a project's builds, newest first.
Artifacts
A finished build names its artifacts in artifactIds.
artifacts.list and
artifacts.get return each one's format, size and SHA-256
and, where the build read them, the app's bundle id, version, build number and URL schemes, and how it was
signed.
- Install it. Name the artifact in
installonsessions.create, or inapps.install:{ "artifactId": "art_…" }. See installing an app. - Download it. artifacts.download returns a signed URL that works for 10 minutes.
An artifact is kept for 30 days; its expiresAt says until when. It needs artifacts:read to read or
download.
Limits
| Limit | Value |
|---|---|
| Builds at once | The builds limit: see limits |
| Waiting for the upload, then for a machine | One hour each, then the build is errored |
| Running time | timeoutMs, 3 hours at most |
| Uploaded source | 2 GiB |
| One artifact | 4 GiB |
| Dependency caches | Kept for 14 days |
Build time is metered as build_seconds: see usage.