Build your app in the VibeView cloud from your CI on every push: the same vibeview build --cloud you run on your machine, started by GitHub Actions, GitLab CI, Bitrise, CircleCI or any CI that can run a shell command. Every build shows on the Builds page labelled with its commit and branch, its own page links back to the CI run that made it, and each simulator or emulator build also lands in your app’s Build history, ready to run on a cloud device, share, or test.
There is a ready-made GitHub Actions workflow below. It builds every push to main and makes a signed production build for every version tag, and it can run your tagged test groups on each new build.
Before you start
- Commit a
vibeview.jsonwith a build block for each platform you build. The easiest way to get one is to runvibeview build --cloudonce on your machine: it asks for anything missing and saves it. See Cloud builds for what a build block holds and how to set one up per framework. - For production builds, the build block also needs its production command, and your signing credentials must be on VibeView (see App signing).
- Create an API token for the repository. In Settings > API token, generate a token with the Developer role, CI only access and an expiry. A CI-only token can start, follow, cancel and download cloud builds and run tests, and nothing else (see CI-only tokens). The CLI reads it from the
VIBEVIEW_API_TOKENenvironment variable.
GitHub Actions
- Save the token as the repository secret
VIBEVIEW_API_TOKEN(Settings > Secrets and variables > Actions). - Add the workflow. Download vibeview-build.yml and save it as
.github/workflows/vibeview-build.yml, or copy it from below. - Edit the platform list in the workflow (
platform: [ios, android]) to the platforms you ship:ios,android,tvosorandroidtv. - Optional: test every build. Add the repository variable
VIBEVIEW_TAG(the Variables tab of the same page) with the tag of the test groups to run, and the workflow runs them on each new build. See CI testing for tagging groups and for running tests on pull requests.
What a run does:
- A push to
mainmakes a simulator build (iOS, Apple TV) or emulator build (Android, Android TV) per platform. It goes to the app’s Build history, where you can open it on a cloud device straight away. - A tag starting with
v(for examplev1.4.0) makes a signed production build instead (.ipa, or.aab/.apk), on the Builds page, ready to download or submit. - The job waits for each build and fails if the build fails, with the end of the build log in the job output and the full log in a collapsed group. The job summary links to each build on VibeView.
- A newer push to the same branch cancels the run still going for the older one, and cancelling a run cancels its cloud builds.
# VibeView: build your app in the VibeView cloud on every push to main, and a
# signed production build for every version tag.
#
# Save this file as .github/workflows/vibeview-build.yml. Docs, and the latest
# version of this file: https://vibeview.io/docs/cloud-builds-ci
#
# One-time setup
# 1. Commit a vibeview.json with a build block for each platform this
# workflow builds (run `vibeview build --cloud` once on your machine and
# it writes them for you). Production builds also need their block's
# production command and your signing credentials on VibeView.
# 2. Create an API token for this repository only (VibeView > Settings >
# API token, role Developer, access CI only, with an expiry) and save it
# as the repository secret VIBEVIEW_API_TOKEN (Settings > Secrets and
# variables > Actions).
# 3. Edit the platform list below to the platforms you ship.
#
# Each build shows on the VibeView Builds page labelled with this commit and
# branch, and its own page links back to this run; simulator and emulator
# builds also land in your app's build history. Every build counts
# toward your plan's cloud builds, so keep the triggers to the branches you
# actually want builds of.
name: VibeView build
on:
push:
branches: [main]
# A tag such as v1.4.0 makes a signed production build.
tags: ['v*']
workflow_dispatch:
permissions:
contents: read
defaults:
run:
# bash -eo pipefail: a failing command fails the step, also inside a pipeline.
shell: bash
concurrency:
# A newer push to the same branch replaces a build still running for an older one.
group: vibeview-build-${{ github.ref }}
cancel-in-progress: true
env:
# The VibeView CLI version. Pinned: bump it deliberately.
VIBEVIEW_CLI_VERSION: 0.46.0
jobs:
build:
name: VibeView build (${{ matrix.platform }})
runs-on: ubuntu-latest
# Above the CLI's own longest wait (up to 30 minutes in the queue, the
# build's time limit, then a margin), so the CLI gives up first and a
# slow but healthy release build is never cut off.
timeout-minutes: 200
strategy:
fail-fast: false
matrix:
# ios, android, tvos, androidtv: each needs its build block in vibeview.json.
platform: [ios, android]
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
- name: Install the VibeView CLI
run: npm install --global --no-audit --no-fund "vibeview@${VIBEVIEW_CLI_VERSION}"
- name: Check the setup
env:
VIBEVIEW_API_TOKEN: ${{ secrets.VIBEVIEW_API_TOKEN }}
run: |
if [ -z "${VIBEVIEW_API_TOKEN}" ]; then
echo "::error::The repository secret VIBEVIEW_API_TOKEN is not set."
exit 1
fi
# Uploads the checked-out source (honoring .gitignore), builds it on
# VibeView and waits for the result. On a version tag it is a signed
# production build; otherwise a simulator/emulator build that lands in
# your app's build history, ready to run and test on a cloud device.
- name: Build in the cloud
id: build
env:
VIBEVIEW_API_TOKEN: ${{ secrets.VIBEVIEW_API_TOKEN }}
PLATFORM: ${{ matrix.platform }}
PRODUCTION: ${{ startsWith(github.ref, 'refs/tags/') && 'true' || '' }}
run: |
out="$RUNNER_TEMP/vibeview"
mkdir -p "$out"
args=(build --cloud --platform "$PLATFORM" --json)
[ -n "$PRODUCTION" ] && args+=(--production)
status=0
# The full build log is long: it is collapsed in the job log.
echo "::group::Cloud build log"
vibeview "${args[@]}" \
| tee "$out/build.ndjson" \
| jq --unbuffered -rj 'select(.event == "build_log") | .content' \
|| status=$?
echo "::endgroup::"
finished=$(jq -cs '[.[] | select(.event == "build_finished")] | last // empty' "$out/build.ndjson")
if [ "$status" -ne 0 ] || [ "$(jq -r '.status // empty' <<< "$finished")" != "succeeded" ]; then
echo "The last 100 lines of the build log:"
jq -rj 'select(.event == "build_log") | .content' "$out/build.ndjson" | tail -n 100
jq -r 'select(.event == "error") | "error: \(.message)"' "$out/build.ndjson"
echo "::error::The $PLATFORM cloud build failed. See the build log above (the full log is in the \"Cloud build log\" group)."
exit 1
fi
id=$(jq -r '.id' <<< "$finished")
echo "cloud_build_id=$id" >> "$GITHUB_OUTPUT"
# For a following test step: the app build to run tests against
# (empty for a production build, which goes to the stores instead).
echo "app_build_id=$(jq -r '.app_build_id // empty' <<< "$finished")" >> "$GITHUB_OUTPUT"
echo "### $PLATFORM: [build $id](https://vibeview.io/builds/$id) succeeded" >> "$GITHUB_STEP_SUMMARY"
# A cancelled or timed-out job doesn't stop a cloud build by itself.
- name: Cancel the cloud build
if: cancelled()
env:
VIBEVIEW_API_TOKEN: ${{ secrets.VIBEVIEW_API_TOKEN }}
run: |
log="$RUNNER_TEMP/vibeview/build.ndjson"
[ -s "$log" ] || exit 0
for id in $(jq -r 'select(.event == "build_created") | .id' "$log"); do
if ! jq -e --arg id "$id" 'select(.event == "build_finished" and .id == $id)' "$log" > /dev/null; then
vibeview builds cancel "$id" || true
fi
done
# Optional: run your tagged VibeView test groups on the new build. Set
# the repository variable VIBEVIEW_TAG to turn this on.
# Exit code: 0 passed, 1 a test failed, 2 the run could not complete.
- name: Test the build
if: ${{ vars.VIBEVIEW_TAG != '' && steps.build.outputs.app_build_id != '' }}
env:
VIBEVIEW_API_TOKEN: ${{ secrets.VIBEVIEW_API_TOKEN }}
BUILD_ID: ${{ steps.build.outputs.app_build_id }}
VV_TAG: ${{ vars.VIBEVIEW_TAG }}
PLATFORM: ${{ matrix.platform }}
# exec: the CLI must be this step's own process, so a cancelled job
# stops the CLI and the CLI cancels the run on VibeView.
run: |
exec vibeview run-suites --tag "$VV_TAG" --platform "$PLATFORM" --build "$BUILD_ID" \
--mode hybrid --commit-sha "$GITHUB_SHA" --timeout 1800
Other CI services
Any CI that can run Node.js can start cloud builds. Install the CLI, set VIBEVIEW_API_TOKEN as a secret (masked) variable, and run the build from the root of the checkout:
npm install --global vibeview
vibeview build --cloud --platform all # every platform with a build block
vibeview build --cloud --platform ios --production
The command uploads the checked-out source (honoring .gitignore), waits for the builds and exits with a non-zero code if any of them fails. Add --json for one machine-readable event per line, including each build’s id and, when it finishes, its status and the id of the app build to run tests against (vibeview run-suites --build <id>). --no-wait starts the builds and exits at once.
For example, in GitLab CI:
vibeview-build:
image: node:22
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
script:
- npm install --global vibeview
- vibeview build --cloud --platform android
Commit, branch and CI run on each build
The CLI labels each build with where its source came from, and the Builds page and the build’s own page show it:
- On GitHub Actions, GitLab CI, Bitrise and CircleCI, it reads the commit, the branch (a pull request’s own branch, not its merge ref) and the link to the CI run from the service.
- Anywhere else, including your own machine, it reads the commit and branch of the local git checkout. When the checkout has uncommitted changes, it leaves the commit out, since the upload is not that commit.
The label is for people: VibeView never fetches anything from your repository. The build is made from the source the CLI uploaded.
What it costs
Each build counts toward your plan’s included cloud builds for the month, and beyond them draws from usage credit, exactly like a build you start by hand (see Cloud builds and Pricing). Trigger the workflow only on the branches you want builds of: building every push of every branch adds up quickly.