Skip to content

CircleCI

This guide releases to Aether from CircleCI. Staging deploys automatically; Production waits on a manual approval job.

Before you start, create a deploy-scoped API key as described in Prerequisites.

CircleCI exposes the API key through an organization context, not per-project environment variables. A context groups secrets and can be shared across projects in the org.

  1. Open Organization Settings → Contexts and create a context named aether-deploy.
  2. Add an environment variable AETHER_API_KEY with the value of an API key created in the dashboard.
  3. Optionally add AETHER_API_URL to target a non-production server.

The jobs below pull the context in by name with context: aether-deploy. You can limit which jobs may use a context with context restrictions, which keeps the key away from unrelated jobs.

Save this as .circleci/config.yml. It pins @aetherpush/cli through the cli-version parameter (0.6.0) and checks the installed version.

version: 2.1
executors:
node:
docker:
- image: cimg/node:22.22
jobs:
deploy:
executor: node
parameters:
deployment:
type: string
rollout:
type: string
default: "100%"
app-name:
type: string
default: "my-react-native-app"
platform:
type: string
default: "android"
cli-version:
type: string
default: "0.6.0"
steps:
- checkout
- run:
name: Install project dependencies
command: npm ci
- run:
name: Install Aether CLI
command: |
set -eu
npm install -g "@aetherpush/cli@<< parameters.cli-version >>"
INSTALLED=$(aether --version | tr -d ' \r\n')
if [ "$INSTALLED" != "<< parameters.cli-version >>" ]; then
echo "Installed Aether CLI '$INSTALLED' does not match pinned '<< parameters.cli-version >>'."
exit 1
fi
- run:
name: Log in to Aether
command: |
set -eu
if [ -z "${AETHER_API_KEY:-}" ]; then
echo "AETHER_API_KEY is not set. Add it to the CircleCI context used by this job."
exit 1
fi
if [ -n "${AETHER_API_URL:-}" ]; then
aether login --accessKey "$AETHER_API_KEY" --serverUrl "$AETHER_API_URL"
else
aether login --accessKey "$AETHER_API_KEY"
fi
- run:
name: Release React Native bundle
command: |
set -eu
aether release-react "<< parameters.app-name >>" "<< parameters.platform >>" \
--deploymentName "<< parameters.deployment >>" \
--rollout "<< parameters.rollout >>" \
--json > release.json
- run:
name: Filter release artifact
command: |
set -eu
node --input-type=commonjs -e '
const fs = require("fs");
const raw = fs.readFileSync("release.json", "utf8").trim();
const lines = raw.split(String.fromCharCode(10));
const r = JSON.parse(lines[lines.length - 1]);
const out = {
label: r.label,
packageHash: r.packageHash,
size: r.size,
appVersion: r.appVersion,
releaseMethod: r.releaseMethod,
rollout: r.rollout,
isMandatory: r.isMandatory,
isDisabled: r.isDisabled
};
Object.keys(out).forEach(function (k) {
if (out[k] === undefined || out[k] === null) delete out[k];
});
fs.writeFileSync("release.json.tmp", JSON.stringify(out));
fs.renameSync("release.json.tmp", "release.json");
'
- store_artifacts:
path: release.json
destination: release.json
workflows:
release:
jobs:
- deploy:
name: deploy-staging
context: aether-deploy
deployment: "Staging"
rollout: "100%"
filters:
branches:
only: main
- hold-production:
type: approval
requires:
- deploy-staging
filters:
branches:
only: main
- deploy:
name: deploy-production
context: aether-deploy
deployment: "Production"
rollout: "25%"
requires:
- hold-production
filters:
branches:
only: main

The release workflow runs deploy-staging on main at 100%. hold-production is an approval job: the workflow stops there until someone approves it in the CircleCI UI. Once approved, deploy-production runs at 25%.

Set your app’s app-name and platform as the parameter defaults on the deploy job, or pass them from each workflow job.

After --json writes release.json, a filter step keeps eight fields: label, packageHash, size, appVersion, releaseMethod, rollout, isMandatory, and isDisabled. store_artifacts uploads that file.

--json still prints the full server response, including signed download URLs. Those URLs are not in the artifact. Recopying this template over an older one drops blobUrl, manifestBlobUrl, description, releasedBy, and uploadTime from the uploaded file. Pipelines that still have the older YAML keep uploading the full response until they recopy.

This template is maintained at aether-cli/examples/ci/circleci-config.yml. When it changes, update this guide to match. The template is the source of truth.