# CI/CD — Bundle Drop Docs

> Automate Bundle Drop OTA uploads from CI/CD using a Personal Access Token with the --token flag in GitHub Actions, GitLab, or Bitrise.

Canonical: https://bundledrop.app/docs/ci-cd

## CI/CD Overview

For CI/CD, the main difference is authentication.

Instead of relying on a local interactive CLI session, you usually pass a Personal Access Token with `--token`.

## Why `--token` Exists

CI environments do not go through `bundle-drop login`, so token-based auth is the normal approach for automated uploads.

## Typical CI Upload

For [Native Fingerprinting](/docs/native-fingerprinting), install the same locked dependencies and use the same native configuration and relevant environment as the binary build. Upload calculates compatibility automatically; no fingerprint override flag is required. Explicit platform values can remain as [hybrid runtime boundaries](/docs/native-fingerprinting#hybrid-fingerprints-and-explicit-runtime-versions).

**ci-upload.sh**

```bash
npx bundle-drop upload android \
  --version 1.2.3 \
  --channel develop \
  --token $BUNDLE_DROP_PAT \
  --author "CI Pipeline"
```

For checked-in multiline Markdown notes, add `--release-notes-file ./release-notes.md`. The path is relative to the invoking project directory. Do not pass `--release-notes` in the same command. The CLI rejects unreadable or empty files before bundling and warns before truncating notes beyond 2,000 characters.

## Using `--token` with Other Commands

You can also authenticate setup with a token instead of browser login. Guided setup can still require context-sharing consent and interactive review:

**Terminal**

```bash
npx bundle-drop init --token bdp_pat_xxx
```

## Artifacts and the Result File

When you pass `--artifact-dir`, Bundle Drop writes a `bundle-drop-result.json` file into that directory after a successful upload. A failed upload can also write a diagnostic result with `success: false`. Always check the command's exit status before using the result as a successful release.

A successful result contains metadata about the uploaded bundle:

**bundle-drop-result.json**

```json
{
  "success": true,
  "projectId": "8e71cf16-4a8e-44d2-93aa-d2a822cb5fa1",
  "channelId": "c58eaab7-2225-4da9-b379-c8d753f9460a",
  "serverUrl": "https://api.bundledrop.app",
  "platform": "android",
  "appVersion": "4.7.3",
  "channel": "develop",
  "orgSlug": "my-org",
  "projectSlug": "my-app",
  "runtimeVersion": "4.0.9",
  "packageVersion": "0.12.0",
  "bundleId": "...",
  "bundleVersion": 339,
  "bundleVersionLabel": "v339",
  "hash": "a970a7a5f65b9fa5ff645386d4b3cbc1034a8d7d...",
  "releaseNotes": "Fix login screen crash",
  "author": "CI Pipeline",
  "zipSizeBytes": 1834201,
  "bundlingAndPackagingMs": 12435,
  "uploadMs": 1832,
  "totalMs": 14892,
  "dashboardUrl": "https://developer.bundledrop.app/organizations/my-org/projects/my-app",
  "bundlePath": "/artifacts/bundledrop/android/main.jsbundle",
  "sourceMapPath": "/artifacts/bundledrop/android/main.jsbundle.map"
}
```

Note that `packageVersion` is the installed `@gfean/react-native-bundle-drop` SDK version, while `appVersion` is your app's version — they are different values.

SDK 0.12.0 adds `success`, `projectId`, `channelId`, and `serverUrl` to the saved upload result. Use `projectId`, `channelId`, and `hash` to identify the uploaded release in subsequent [Public API](https://developer.bundledrop.app/public-api/) calls. `serverUrl` is the API base URL actually used for the upload. Project and channel IDs can be `null` when an older server does not return them; validate that they are present before making API calls. If you use an older SDK, you can supply fixed project and channel IDs from your CI secret store alongside the PAT instead of reading them from the result file.

`zipSizeBytes` is measured from the uploaded archive. The timing fields report elapsed milliseconds for bundling and packaging together, the upload request, and the whole command. `dashboardUrl` links to the project page for the exact production or staging API; it is `null` for custom servers. The console summary shows these values after upload and does not claim the bundle is already eligible for delivery. If local artifact reporting fails after the server confirms upload, the CLI warns; inspect the server before retrying to avoid a duplicate upload.

You can read any field from this file with `jq` to use in later pipeline steps — setting variables, uploading source maps, posting notifications, tagging releases, etc.

## Full CI Example with Artifact Variables

This Bash function uploads a bare React Native bundle, retains artifacts, and extracts values for later steps. Supply the variables from your CI environment and secret store. It works with GitHub Actions, Azure Pipelines, GitLab CI, Bitrise, or another runner with Node.js and `jq`. Expo projects use the option-free platform commands shown in [Uploading](/docs/uploading#expo-uploads).

**deploy-bundledrop.sh**

```bash
deploy_bundledrop() {
  PLATFORM_NAME=$1
  PLATFORM_LC=$(echo "$PLATFORM_NAME" | tr '[:upper:]' '[:lower:]')
  ARTIFACT_DIR="$STAGING_DIR/bundledrop/$PLATFORM_LC"

  mkdir -p "$ARTIFACT_DIR" || return 1

  echo "Releasing OTA for $PLATFORM_NAME to channel $CHANNEL..."

  if [ "$PLATFORM_LC" = "ios" ]; then
    npx bundle-drop upload ios \
      --plist-file ios/MyApp/Info.plist \
      --channel "$CHANNEL" \
      --token "$BUNDLE_DROP_TOKEN" \
      --release-notes "$RELEASE_NOTES" \
      --author "$BUILD_REQUESTER" \
      --sourcemap \
      --artifact-dir "$ARTIFACT_DIR" || return $?
  elif [ "$PLATFORM_LC" = "android" ]; then
    npx bundle-drop upload android \
      --buildgradle-path android/app/build.gradle \
      --channel "$CHANNEL" \
      --token "$BUNDLE_DROP_TOKEN" \
      --release-notes "$RELEASE_NOTES" \
      --author "$BUILD_REQUESTER" \
      --sourcemap \
      --artifact-dir "$ARTIFACT_DIR" || return $?
  else
    echo "Expected ios or android, got: $PLATFORM_NAME" >&2
    return 1
  fi

  BD_RESULT="$ARTIFACT_DIR/bundle-drop-result.json"

  if [ ! -f "$BD_RESULT" ]; then
    echo "Result file not found at $BD_RESULT"
    return 1
  fi

  if ! jq -e '.success != false and .status != "failed" and (.hash | type == "string" and length > 0) and (.bundleVersion | type == "number")' "$BD_RESULT" > /dev/null; then
    echo "Upload result does not confirm a successful bundle" >&2
    return 1
  fi

  # Extract values for downstream steps
  BD_PROJECT_ID=$(jq -r '.projectId // empty' "$BD_RESULT")
  BD_CHANNEL_ID=$(jq -r '.channelId // empty' "$BD_RESULT")
  BD_SERVER_URL=$(jq -r '.serverUrl // empty' "$BD_RESULT")
  BD_HASH=$(jq -r '.hash // empty' "$BD_RESULT")
  BD_VERSION=$(jq -r '.bundleVersion // empty' "$BD_RESULT")
  BD_LABEL=$(jq -r '.bundleVersionLabel // empty' "$BD_RESULT")
  BD_APP_VERSION=$(jq -r '.appVersion // empty' "$BD_RESULT")
  BD_RUNTIME=$(jq -r '.runtimeVersion // empty' "$BD_RESULT")
  BD_BUNDLE=$(jq -r '.bundlePath // empty' "$BD_RESULT")
  BD_SOURCEMAP=$(jq -r '.sourceMapPath // empty' "$BD_RESULT")

  echo "Uploaded $PLATFORM_NAME bundle v$BD_VERSION ($BD_LABEL) — hash: $BD_HASH"
}

# Stop the pipeline if upload fails; do not run downstream artifact steps.
deploy_bundledrop android || exit $?
```

## Source Map Upload for Error Tracking

After extracting the hash, bundle path, and source-map path from the result
file, upload the matching artifacts to your error tracking provider using the
hash as the `dist` identifier:

**upload-sourcemaps.sh**

```bash
# Sentry
sentry-cli sourcemaps upload \
  --release "$RELEASE" \
  --dist "$BD_HASH" \
  --bundle "$BD_BUNDLE" \
  --bundle-sourcemap "$BD_SOURCEMAP" \
  --validate

# Bugsnag
bugsnag-sourcemaps upload \
  --source-map "$BD_SOURCEMAP" \
  --code-bundle-id "$BD_HASH" \
  --app-version "$BD_APP_VERSION"

# Datadog
datadog-ci sourcemaps upload "$BD_SOURCEMAP" \
  --service my-app \
  --release-version "$BD_APP_VERSION" \
  --minified-path-prefix /
```

Without `--sourcemap`, the upload works exactly as before — no source map is generated, and artifacts are cleaned up after upload. See [Observability](/docs/observability) for runtime integration details.

If the app uses `@sentry/react-native` 7.8.x with Hermes, apply the one-time
[Sentry and Hermes OTA Metro configuration](/docs/observability#sentry-and-hermes-ota-builds)
so content-derived Debug IDs do not destabilize binary patch sizes.

## What the Client Needs to Know

- use `bundle-drop login` for local development
- use `--token` for automated environments
- keep the token in your CI secret store, not in source control

Expo projects use the same token flow and option-free platform uploads. If the project opts into strict Expo fingerprint authority, import the exact local or EAS build receipt before uploading. See [Expo](/docs/expo#strict-expo-fingerprint-authority).

## Related Docs

- For general CLI setup, see [CLI](/docs/cli).
- For upload command details, see [Uploading](/docs/uploading).
- For error tracking integration, see [Observability](/docs/observability).
