# Xcode Cloud (https://hotcodepush.com/docs/ci/xcode-cloud)

An archive workflow with HOTCODEPUSH_TOKEN as a secret, the post-clone script that installs Node.js, and the check that confirms the store build is registered.

The archive workflow on Xcode Cloud holds `HOTCODEPUSH_TOKEN` as a secret environment variable, and a post-clone script installs Node.js for the build step `init` wired. A check after the first archive confirms that the step registered the iOS store build.

Xcode Cloud builds only the iOS app. The Android store build runs elsewhere with its own token. Every merge's over-the-air (OTA) update ships from the CI on `main`: [Azure DevOps](https://hotcodepush.com/docs/ci/azure-devops), [Bitbucket Pipelines](https://hotcodepush.com/docs/ci/bitbucket), [Capawesome Cloud](https://hotcodepush.com/docs/ci/capawesome-cloud), [Gitea Actions](https://hotcodepush.com/docs/ci/gitea), [GitHub Actions](https://hotcodepush.com/docs/ci/github-actions) or [GitLab CI/CD](https://hotcodepush.com/docs/ci/gitlab). A release from Xcode Cloud would need a full archive on every merge and release each commit twice.

[Ship every commit](https://hotcodepush.com/docs/ci/pipeline) has the prerequisites and the three disciplines. This page carries the archive workflow and its script.

## The secret to set [#the-secret-to-set]

`HOTCODEPUSH_TOKEN` holds an API token from the console's API tokens page and lives in the Environment section of the archive workflow alone. Mark it "Keep value redacted" in Xcode or "Secret" in App Store Connect, so the logs show asterisks in its place. The workflow needs no signing key, since [`binary create`](https://hotcodepush.com/docs/cli/commands/binary#create) signs nothing.

The archive workflow starts on Branch Changes for `main` or on Tag Changes, never on Pull Request Changes. A pull request can edit `ci_post_clone.sh`, and the scripts read every variable of their workflow. A pull request workflow with Build or Test actions needs no token, since those builds run [`resource-file write`](https://hotcodepush.com/docs/cli/commands/resource-file#write), which never fails without one.

## The post-clone script [#the-post-clone-script]

Xcode Cloud runs `ci_post_clone.sh` from the `ci_scripts` folder beside the Xcode project, right after the clone. The image carries Homebrew and CocoaPods but no Node.js, so the script installs one from the nodejs.org tarball.

```sh title="ios/App/ci_scripts/ci_post_clone.sh"
#!/bin/sh
set -e

NODE_VERSION=24.22.1
NODE_ARCH=$([ "$(uname -m)" = arm64 ] && echo arm64 || echo x64)
NODE_DIR="$CI_DERIVED_DATA_PATH/node-v$NODE_VERSION-darwin-$NODE_ARCH"

mkdir -p "$CI_DERIVED_DATA_PATH"
curl -fsSLo "$CI_DERIVED_DATA_PATH/node.tar.xz" "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-darwin-$NODE_ARCH.tar.xz"
tar -xJf "$CI_DERIVED_DATA_PATH/node.tar.xz" -C "$CI_DERIVED_DATA_PATH"
export PATH="$NODE_DIR/bin:$PATH"

cd "$CI_PRIMARY_REPOSITORY_PATH"
echo "export NODE_BINARY=$NODE_DIR/bin/node" > ios/App/.xcode.env.local
echo "export CI=TRUE" >> ios/App/.xcode.env.local

npm ci
npm run build
npx cap sync ios
```

Make the script executable with `chmod +x ios/App/ci_scripts/ci_post_clone.sh` before you commit it.

- A script's working directory is `ci_scripts`, so it changes into `$CI_PRIMARY_REPOSITORY_PATH` before the install.
- The build step does not inherit the script's environment, so the script writes `NODE_BINARY` and `CI` into `ios/App/.xcode.env.local`, which the step sources before it runs.
- A project on Swift Package Manager commits `ios/App/App.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved`, and Xcode Cloud resolves the packages from that file.
- Every action runs the scripts once, so a workflow with a Build and an Archive action installs Node.js and builds the web app twice.

React Native and Expo keep the scripts in `ios/ci_scripts`, write `ios/.xcode.env.local` instead and replace the last two lines:

| Framework | After `npm ci` |
|---|---|
| React Native | `cd ios && pod install` |
| Expo | `npx expo prebuild --platform ios`, then `cd ios && pod install` |

An Expo project commits the Xcode project the prebuild fills, since Apple requires a project continuously present in the repository. Cordova is not covered for the same reason: `cordova platform add` and `prepare` write its iOS project.

## Check the first archive [#check-the-first-archive]

After the first archive, run `npx hotcodepush binary list`: the iOS binary is listed with the version and build number TestFlight shows for that build. Apple documents the workflow's variables for the custom scripts alone, so the first archive shows whether the token reached the build step.

A missing token fails the archive, and the [error catalog](https://hotcodepush.com/docs/cli/errors#not_logged_in) names its code. Any failed archive carries the CLI's exit code, and the [error catalog](https://hotcodepush.com/docs/cli/errors) names the fix.

## Next steps [#next-steps]

- [Ship every commit](https://hotcodepush.com/docs/ci/pipeline) has the release line the CI on `main` runs and the three disciplines it keeps.
- [GitHub Actions](https://hotcodepush.com/docs/ci/github-actions) has the workflow that releases every merge. The other recipes share its shape.
- [Compatibility](https://hotcodepush.com/docs/concepts/compatibility) explains the fingerprint the store build carries, read from the lockfile, never from `ios/`.
