Full Code of axelmarciano/expo-open-ota for AI

main 2a506a8dd74f cached
348 files
9.9 MB
2.6M tokens
16156 symbols
1 requests
Copy disabled (too large) Download .txt
Showing preview only (10,470K chars total). Download the full file to get everything.
Repository: axelmarciano/expo-open-ota
Branch: main
Commit: 2a506a8dd74f
Files: 348
Total size: 9.9 MB

Directory structure:
gitextract_u7h5vna8/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── push.yml
│       └── release.yml
├── .gitignore
├── Dockerfile
├── Dockerfile-ci
├── Dockerfile-dev
├── LICENSE.md
├── Makefile
├── README.md
├── apps/
│   ├── dashboard/
│   │   ├── .gitignore
│   │   ├── .prettierrc
│   │   ├── README.md
│   │   ├── components.json
│   │   ├── eslint.config.js
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── public/
│   │   │   └── env.js
│   │   ├── src/
│   │   │   ├── App.tsx
│   │   │   ├── components/
│   │   │   │   ├── APIError/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── Combobox/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── DataTable/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── UpdateDetailsSheet/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── app-sidebar.tsx
│   │   │   │   └── ui/
│   │   │   │       ├── alert.tsx
│   │   │   │       ├── badge.tsx
│   │   │   │       ├── breadcrumb.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── command.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── form.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── label.tsx
│   │   │   │       ├── popover.tsx
│   │   │   │       ├── progress.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       ├── sheet.tsx
│   │   │   │       ├── sidebar.tsx
│   │   │   │       ├── skeleton.tsx
│   │   │   │       ├── table.tsx
│   │   │   │       ├── toast.tsx
│   │   │   │       ├── toaster.tsx
│   │   │   │       └── tooltip.tsx
│   │   │   ├── containers/
│   │   │   │   └── Layout/
│   │   │   │       └── index.tsx
│   │   │   ├── hooks/
│   │   │   │   ├── use-mobile.tsx
│   │   │   │   └── use-toast.ts
│   │   │   ├── index.css
│   │   │   ├── lib/
│   │   │   │   ├── api.ts
│   │   │   │   ├── auth.ts
│   │   │   │   └── utils.ts
│   │   │   ├── main.tsx
│   │   │   ├── pages/
│   │   │   │   ├── Channels/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   └── SelectBranch/
│   │   │   │   │   │       └── index.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── Login/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── Logout/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── Settings/
│   │   │   │   │   └── index.tsx
│   │   │   │   └── Updates/
│   │   │   │       ├── components/
│   │   │   │       │   ├── BranchesTable/
│   │   │   │       │   │   └── index.tsx
│   │   │   │       │   ├── RuntimeVersionsTable/
│   │   │   │       │   │   └── index.tsx
│   │   │   │       │   └── UpdatesTable/
│   │   │   │       │       └── index.tsx
│   │   │   │       └── index.tsx
│   │   │   └── vite-env.d.ts
│   │   ├── tailwind.config.js
│   │   ├── tsconfig.app.json
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   └── vite.config.ts
│   ├── docs/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── docs/
│   │   │   ├── advanced/
│   │   │   │   ├── _category_.json
│   │   │   │   └── prometheus.mdx
│   │   │   ├── dashboard.mdx
│   │   │   ├── deployment/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── custom.mdx
│   │   │   │   ├── helm.mdx
│   │   │   │   ├── railway.mdx
│   │   │   │   └── testing.mdx
│   │   │   ├── eoas/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── configure.mdx
│   │   │   │   ├── intro.mdx
│   │   │   │   ├── publish.mdx
│   │   │   │   ├── republish.mdx
│   │   │   │   └── rollback.mdx
│   │   │   ├── getting-started/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── introduction.mdx
│   │   │   │   ├── prerequisites.mdx
│   │   │   │   └── quick-start.mdx
│   │   │   ├── reference/
│   │   │   │   ├── _category_.json
│   │   │   │   └── environment.mdx
│   │   │   └── server-configuration/
│   │   │       ├── _category_.json
│   │   │       ├── cache.mdx
│   │   │       ├── cdn/
│   │   │       │   ├── _category_.json
│   │   │       │   ├── cloudfront.mdx
│   │   │       │   ├── generic.mdx
│   │   │       │   └── intro.mdx
│   │   │       ├── key-store.mdx
│   │   │       └── storage.mdx
│   │   ├── docusaurus.config.ts
│   │   ├── package.json
│   │   ├── sidebars.ts
│   │   ├── src/
│   │   │   ├── components/
│   │   │   │   ├── BrowserWindow/
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── styles.module.css
│   │   │   │   └── HomepageFeatures/
│   │   │   │       ├── index.tsx
│   │   │   │       └── styles.module.css
│   │   │   ├── css/
│   │   │   │   └── custom.css
│   │   │   └── pages/
│   │   │       ├── index.module.css
│   │   │       ├── index.tsx
│   │   │       └── markdown-page.md
│   │   ├── static/
│   │   │   └── .nojekyll
│   │   └── tsconfig.json
│   ├── eoas/
│   │   ├── .eslintignore
│   │   ├── .eslintrc.js
│   │   ├── .gitignore
│   │   ├── .prettierrc
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── commands/
│   │   │   │   ├── generate-certs.ts
│   │   │   │   ├── init.ts
│   │   │   │   ├── publish.ts
│   │   │   │   ├── republish.ts
│   │   │   │   └── rollback.ts
│   │   │   ├── index.d.ts
│   │   │   └── lib/
│   │   │       ├── assets.ts
│   │   │       ├── auth.ts
│   │   │       ├── channel.ts
│   │   │       ├── expoConfig.ts
│   │   │       ├── fetch.ts
│   │   │       ├── log.ts
│   │   │       ├── ora.ts
│   │   │       ├── package.ts
│   │   │       ├── packageRunner.ts
│   │   │       ├── prompts.ts
│   │   │       ├── repo.ts
│   │   │       ├── runtimeVersion.ts
│   │   │       ├── utils.ts
│   │   │       ├── vcs/
│   │   │       │   ├── README.md
│   │   │       │   ├── clients/
│   │   │       │   │   ├── git.ts
│   │   │       │   │   ├── gitNoCommit.ts
│   │   │       │   │   └── noVcs.ts
│   │   │       │   ├── git.ts
│   │   │       │   ├── index.ts
│   │   │       │   ├── local.ts
│   │   │       │   └── vcs.ts
│   │   │       └── workflow.ts
│   │   └── tsconfig.json
│   ├── example-app/
│   │   ├── .eslintrc.json
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── +not-found.tsx
│   │   │   ├── _layout.tsx
│   │   │   └── index.tsx
│   │   ├── app.config.ts
│   │   ├── app.json
│   │   ├── components/
│   │   │   ├── LogViewer.tsx
│   │   │   ├── ThemedText.tsx
│   │   │   ├── ThemedView.tsx
│   │   │   ├── __tests__/
│   │   │   │   ├── ThemedText-test.tsx
│   │   │   │   └── __snapshots__/
│   │   │   │       └── ThemedText-test.tsx.snap
│   │   │   └── ui/
│   │   │       ├── IconSymbol.ios.tsx
│   │   │       ├── IconSymbol.tsx
│   │   │       ├── TabBarBackground.ios.tsx
│   │   │       └── TabBarBackground.tsx
│   │   ├── constants/
│   │   │   └── Colors.ts
│   │   ├── hooks/
│   │   │   ├── useColorScheme.ts
│   │   │   ├── useColorScheme.web.ts
│   │   │   └── useThemeColor.ts
│   │   ├── package.json
│   │   ├── scripts/
│   │   │   ├── network_security_config.xml
│   │   │   ├── reset-project.js
│   │   │   └── trust_local_certs.js
│   │   └── tsconfig.json
│   └── example-app-runtime-switch/
│       ├── .eslintrc.json
│       ├── .gitignore
│       ├── .prettierignore
│       ├── .prettierrc
│       ├── README.md
│       ├── app/
│       │   ├── +not-found.tsx
│       │   ├── _layout.tsx
│       │   └── index.tsx
│       ├── app.config.ts
│       ├── app.json
│       ├── components/
│       │   ├── LogViewer.tsx
│       │   ├── ThemedText.tsx
│       │   ├── ThemedView.tsx
│       │   ├── __tests__/
│       │   │   ├── ThemedText-test.tsx
│       │   │   └── __snapshots__/
│       │   │       └── ThemedText-test.tsx.snap
│       │   └── ui/
│       │       ├── IconSymbol.ios.tsx
│       │       ├── IconSymbol.tsx
│       │       ├── TabBarBackground.ios.tsx
│       │       └── TabBarBackground.tsx
│       ├── constants/
│       │   └── Colors.ts
│       ├── hooks/
│       │   ├── useColorScheme.ts
│       │   ├── useColorScheme.web.ts
│       │   └── useThemeColor.ts
│       ├── package.json
│       ├── scripts/
│       │   ├── network_security_config.xml
│       │   ├── reset-project.js
│       │   └── trust_local_certs.js
│       └── tsconfig.json
├── cmd/
│   └── api/
│       └── main.go
├── config/
│   ├── config.go
│   └── config_test.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── grafana/
│   └── dashboard.json
├── grafana-dashboard.json
├── helm/
│   ├── .helmignore
│   ├── Chart.yaml
│   ├── helm_template_test.go
│   ├── templates/
│   │   ├── NOTES.txt
│   │   ├── _helpers.tpl
│   │   ├── deployment.yaml
│   │   ├── hpa.yaml
│   │   ├── ingress.yaml
│   │   ├── service.yaml
│   │   ├── serviceaccount.yaml
│   │   └── tests/
│   │       └── test-connection.yaml
│   └── values.yaml
├── internal/
│   ├── assets/
│   │   └── assets.go
│   ├── auth/
│   │   └── auth.go
│   ├── branch/
│   │   ├── branch.go
│   │   └── branch_test.go
│   ├── bucket/
│   │   ├── bucket.go
│   │   ├── bucket_test.go
│   │   ├── gcsBucket.go
│   │   ├── localBucket.go
│   │   ├── localBucket_test.go
│   │   └── s3Bucket.go
│   ├── cache/
│   │   ├── cache.go
│   │   ├── cache_test.go
│   │   ├── localCache.go
│   │   └── redisCache.go
│   ├── cdn/
│   │   ├── cdn.go
│   │   ├── cdn_test.go
│   │   ├── cloudfront.go
│   │   ├── gcs_direct.go
│   │   └── generic.go
│   ├── compression/
│   │   └── compression.go
│   ├── crypto/
│   │   ├── crypto.go
│   │   └── crypto_test.go
│   ├── dashboard/
│   │   └── dashboard.go
│   ├── handlers/
│   │   ├── assets_handler.go
│   │   ├── auth_handler.go
│   │   ├── dashboard_handler.go
│   │   ├── manifest_handler.go
│   │   ├── republish_handler.go
│   │   ├── rollback_handler.go
│   │   └── upload_handler.go
│   ├── helpers/
│   │   ├── auth.go
│   │   ├── headers.go
│   │   ├── string.go
│   │   └── url.go
│   ├── keyStore/
│   │   ├── awsSMKeyStorage.go
│   │   ├── environmentKeyStorage.go
│   │   ├── keyStore.go
│   │   └── localKeyStorage.go
│   ├── metrics/
│   │   ├── metrics.go
│   │   └── metrics_test.go
│   ├── middleware/
│   │   ├── auth_middleware.go
│   │   ├── cors_middleware.go
│   │   └── logging_middleware.go
│   ├── migration/
│   │   ├── base.go
│   │   ├── migration.go
│   │   ├── registry.go
│   │   └── runner.go
│   ├── migrations/
│   │   ├── 20250417_persist_uuid/
│   │   │   └── 20250417_persist_uuid.go
│   │   └── migrations.go
│   ├── router/
│   │   └── router.go
│   ├── services/
│   │   ├── aws.go
│   │   ├── aws_test.go
│   │   ├── expo.go
│   │   ├── gcp.go
│   │   └── jwt.go
│   ├── types/
│   │   └── types.go
│   ├── update/
│   │   ├── prewarm.go
│   │   └── updates.go
│   └── version/
│       └── version.go
├── prometheus.yml
└── test/
    ├── assets_test.go
    ├── channel_mapping_cache_test.go
    ├── dashboard_path_traversal_test.go
    ├── dashboard_test.go
    ├── expo_multipart_parser.go
    ├── helpers.go
    ├── manifest_test.go
    ├── migrations_test.go
    ├── republish_test.go
    ├── requestUpload_test.go
    ├── rollback_test.go
    ├── test-updates/
    │   ├── branch-1/
    │   │   └── 1/
    │   │       └── 1674170951/
    │   │           ├── .check
    │   │           ├── assets/
    │   │           │   └── 4f1cb2cac2370cd5050681232e8575a8
    │   │           ├── bundles/
    │   │           │   ├── android-82adadb1fb6e489d04ad95fd79670deb.js
    │   │           │   └── ios-9d01842d6ee1224f7188971c5d397115.js
    │   │           ├── expoConfig.json
    │   │           ├── metadata.json
    │   │           └── update-metadata.json
    │   ├── branch-2/
    │   │   └── 1/
    │   │       ├── 1666304169/
    │   │       │   ├── .check
    │   │       │   ├── rollback
    │   │       │   └── update-metadata.json
    │   │       ├── 1666629107/
    │   │       │   ├── .check
    │   │       │   ├── bundles/
    │   │       │   │   ├── android-b00c4b050fca5b0ca395c7c183a2aed3.js
    │   │       │   │   └── ios-673cd0555c467df47093f49cc1b6d00f.js
    │   │       │   ├── metadata.json
    │   │       │   └── update-metadata.json
    │   │       ├── 1666629141/
    │   │       │   ├── .check
    │   │       │   ├── rollback
    │   │       │   └── update-metadata.json
    │   │       ├── 1674170951/
    │   │       │   ├── .check
    │   │       │   ├── assets/
    │   │       │   │   └── 4f1cb2cac2370cd5050681232e8575a8
    │   │       │   ├── bundles/
    │   │       │   │   ├── android-82adadb1fb6e489d04ad95fd79670deb.js
    │   │       │   │   └── ios-9d01842d6ee1224f7188971c5d397115.js
    │   │       │   ├── expoConfig.json
    │   │       │   ├── metadata.json
    │   │       │   └── update-metadata.json
    │   │       └── 1737455526/
    │   │           ├── .check
    │   │           ├── _expo/
    │   │           │   └── static/
    │   │           │       └── js/
    │   │           │           ├── android/
    │   │           │           │   └── AppEntry-3aa3d3f85ad7a30a3c33dba2de772e4f.hbc
    │   │           │           └── ios/
    │   │           │               └── AppEntry-546b83fc2035b34c5f2dbd9bb04a2478.hbc
    │   │           ├── assets/
    │   │           │   └── 4f1cb2cac2370cd5050681232e8575a8
    │   │           ├── expoConfig.json
    │   │           ├── metadata.json
    │   │           └── update-metadata.json
    │   ├── branch-3/
    │   │   └── 1/
    │   │       ├── 1666304168/
    │   │       │   ├── .check
    │   │       │   ├── assets/
    │   │       │   │   └── 4f1cb2cac2370cd5050681232e8575a8
    │   │       │   ├── bundles/
    │   │       │   │   ├── android-82adadb1fb6e489d04ad95fd79670deb.js
    │   │       │   │   └── ios-9d01842d6ee1224f7188971c5d397115.js
    │   │       │   ├── expoConfig.json
    │   │       │   ├── metadata.json
    │   │       │   └── update-metadata.json
    │   │       └── 1666304169/
    │   │           ├── .check
    │   │           ├── rollback
    │   │           └── update-metadata.json
    │   └── branch-4/
    │       └── 1/
    │           ├── 1674170951/
    │           │   ├── .check
    │           │   ├── assets/
    │           │   │   └── 4f1cb2cac2370cd5050681232e8575a8
    │           │   ├── bundles/
    │           │   │   ├── android-82adadb1fb6e489d04ad95fd79670deb.js
    │           │   │   └── ios-9d01842d6ee1224f7188971c5d397115.js
    │           │   ├── expoConfig.json
    │           │   ├── metadata.json
    │           │   └── update-metadata.json
    │           └── 1674170952/
    │               ├── assets/
    │               │   └── 4f1cb2cac2370cd5050681232e8575a8
    │               ├── bundles/
    │               │   └── android-82adadb1fb6e489d04ad95fd79670deb.js
    │               ├── expoConfig.json
    │               ├── metadata.json
    │               └── update-metadata.json
    └── url_encoding_test.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms

github: [https://github.com/sponsors/axelmarciano]


================================================
FILE: .github/workflows/push.yml
================================================
name: Push workflow

on:
  push:
    branches:
      - '**'

permissions:
  contents: write

jobs:
  test:
    runs-on: ubuntu-latest

    container:
      image: ghcr.io/axelmarciano/expo-open-ota-ci:latest
      credentials:
        username: axelmarciano
        password: ${{  secrets.DOCKER_GITHUB_CONTAINER_REGISTRY_TOKEN }}

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - uses: actions/setup-go@v5
        with:
          go-version: 1.24

      - name: Check if .env exists or create it
        run: |
          if [ ! -f .env ]; then
            touch .env
          fi

      - name: Cache Go modules
        uses: actions/cache@v4
        with:
          path: |
            ~/.cache/go-build
            ~/.go/pkg/mod
          key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }}
          restore-keys: |
            ${{ runner.os }}-go-mod-

      - name: Install Go dependencies
        run: |
          go mod tidy 
          go mod download

      - name: Run tests
        run: make test_app html

      - name: Upload coverage artifact
        if: ${{ success() }}
        uses: actions/upload-artifact@v4
        with:
          name: coverage
          path: coverage.html
          retention-days: 1


================================================
FILE: .github/workflows/release.yml
================================================
name: Release Workflow

on:
  push:
    tags:
      - "v*"

permissions:
  id-token: write
  contents: write
  packages: write

jobs:
  docker:
    runs-on: ubuntu-latest
    env:
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Set up QEMU
        uses: docker/setup-qemu-action@v3

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin

      - name: Build and push multi-platform Docker image
        run: |
          IMAGE_TAG="${GITHUB_REF#refs/tags/}"
          REPO="ghcr.io/${{ github.repository_owner }}/expo-open-ota"

          echo "Building Docker image with tag: $IMAGE_TAG"
          docker buildx build \
            --platform linux/amd64,linux/arm64 \
            -t "$REPO:$IMAGE_TAG" \
            --push .

      - name: Check latest version and update latest tag
        env:
          IMAGE_TAG: ${{ github.ref_name }}
        run: |
          REPO="ghcr.io/${{ github.repository_owner }}/expo-open-ota"

          echo "Fetching the latest version tag from GHCR..."
          LATEST_TAG=$(gh api "https://api.github.com/users/${{ github.repository_owner }}/packages/container/expo-open-ota/versions?per_page=100" \
            --jq '[.[].metadata.container.tags[]] | map(select(test("^v[0-9]+\\.[0-9]+\\.[0-9]+$"))) | sort_by(ltrimstr("v") | split(".") | map(tonumber)) | last' | tr -d '"')

          echo "Latest found version: $LATEST_TAG"
          echo "Current version: $IMAGE_TAG"

          if [ "$LATEST_TAG" = "$IMAGE_TAG" ]; then
            echo "$IMAGE_TAG is the latest version. Updating latest tag..."
            docker buildx imagetools create -t "$REPO:latest" "$REPO:$IMAGE_TAG"
          else
            echo "$IMAGE_TAG is not the latest version. Skipping latest tag update."
          fi

  helm:
    runs-on: ubuntu-latest
    needs: docker
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Update Helm values.yaml
        run: |
          IMAGE_TAG="${GITHUB_REF#refs/tags/}"
          sed -i "s/tag: .*/tag: ${IMAGE_TAG}/" helm/values.yaml

      - name: Package Helm chart
        run: |
          IMAGE_TAG="${GITHUB_REF#refs/tags/}"
          helm package ./helm -d ./charts
          mv ./charts/expo-open-ota-*.tgz ./charts/expo-open-ota-helm-charts-${IMAGE_TAG}.tgz

  npm:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: "24.x"
          registry-url: "https://registry.npmjs.org"

      - name: Publish NPM package
        run: |
          cd apps/eoas
          VERSION=$(echo "${{ github.ref_name }}" | sed 's/^v//')
          npm ci
          npm run build
          npm version "$VERSION" --no-git-tag-version

          if echo "$VERSION" | grep -qE '\-(alpha|beta|rc)'; then
            NPM_TAG=$(echo "$VERSION" | grep -oE '(alpha|beta|rc)')
            echo "Pre-release detected, publishing with --tag $NPM_TAG"
            npm publish --access public --provenance --tag "$NPM_TAG"
          else
            npm publish --access public --provenance
          fi

  github-release:
    runs-on: ubuntu-latest
    needs: [helm, npm]
    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Create GitHub Release
        uses: softprops/action-gh-release@v2
        with:
          files: ./charts/*.tgz
          prerelease: ${{ contains(github.ref_name, '-alpha') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-rc') }}
          body: |
            ## Changes
            - Docker image: `ghcr.io/${{ github.repository_owner }}/expo-open-ota:${{ github.ref_name }}`
            - Helm chart version updated
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}


================================================
FILE: .gitignore
================================================
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
.idea/
keys/

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
.env
# Logs
*.log

# Temporary files
*.tmp

# Dependency directories
vendor/

# Go modules

# IDE/editor specific files
.vscode/
.idea/
*.iml

# GoLand plugin files
*.local
.claude

# MacOS specific files
.DS_Store

# Node.js specific files (if the project includes frontend code)
node_modules/

# Build directories
bin/
build/

# Cover profile generated by 'go test -coverprofile'
coverage.out
coverage.html

# Configuration files
.env

# Temporary backup files
*~
updates/**/*
test/keys/**/*


================================================
FILE: Dockerfile
================================================
FROM --platform=$BUILDPLATFORM node:24-alpine AS dashboard-builder
WORKDIR /app/apps/dashboard
COPY apps/dashboard/package.json apps/dashboard/package-lock.json ./
RUN npm ci
COPY apps/dashboard ./
RUN npm run build

FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
ARG TARGETARCH
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY cmd ./cmd
COPY internal ./internal
COPY keys ./keys
COPY config ./config
COPY updates ./updates
RUN GOOS=linux GOARCH=${TARGETARCH} go build -o main ./cmd/api

FROM alpine:latest
RUN apk add --no-cache bash
WORKDIR /app
COPY --from=builder /app/main /app/main
COPY --from=dashboard-builder /app/apps/dashboard/dist /app/apps/dashboard/dist
EXPOSE 3000
CMD ["/app/main"]


================================================
FILE: Dockerfile-ci
================================================
FROM node:18-alpine AS dashboard-builder

WORKDIR /app/apps/dashboard

COPY apps/dashboard/package.json apps/dashboard/package-lock.json ./
RUN npm ci

COPY apps/dashboard ./
RUN npm run build

FROM golang:1.24-alpine

RUN apk add --no-cache git bash curl unzip entr make tar

RUN go install github.com/cespare/reflex@latest

ENV PATH="/go/bin:${PATH}"

COPY --from=dashboard-builder /app/apps/dashboard/dist /app/apps/dashboard/dist

CMD ["bash"]


================================================
FILE: Dockerfile-dev
================================================
# Start with the official Golang base image
FROM golang:1.24-alpine

# Install necessary packages
RUN apk add --no-cache git bash curl unzip entr

# Install Reflex for hot reloading
RUN go install github.com/cespare/reflex@latest

# Ensure go binaries and AWS CLI are available in the PATH
ENV PATH="/go/bin:${PATH}"

# Set the Current Working Directory inside the container
WORKDIR /app

# Copy go mod and sum files
COPY go.mod go.sum ./

# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed
RUN go mod download

# Copy the source from the current directory to the Working Directory inside the container
COPY cmd ./cmd
COPY internal ./internal
COPY keys ./keys
COPY config ./config
COPY updates ./updates
COPY test ./test
RUN if [ -f .env ]; then cp .env /app/.env; fi


# Install dependencies
RUN go get ./...

# Command to run the application with Reflex
CMD ["reflex", "-r", "\\.go", "-s", "--", "sh", "-c", "go run cmd/api/main.go"]


================================================
FILE: LICENSE.md
================================================
MIT License

Copyright (c) [2025] [Axel Marciano]

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: Makefile
================================================
DOCKER_FLAG := $(findstring docker, $(MAKECMDGOALS))
HTML_FLAG := $(findstring html, $(MAKECMDGOALS))
MAKEFLAGS += --silent

build:
ifeq ($(DOCKER_FLAG),docker)
	docker-compose build
else
	go build ./...
endif

up:
ifeq ($(DOCKER_FLAG),docker)
	docker-compose up -d
else
	reflex -r '\.go$$' -s -- sh -c "go run cmd/api/main.go"
endif

down:
ifeq ($(DOCKER_FLAG),docker)
	docker-compose down
else
	echo "Not applicable locally. Stop the application manually."
endif

test_app:
ifeq ($(DOCKER_FLAG),docker)
	docker-compose --profile test run --rm -e "" ota-server-test go test -v -coverprofile=coverage.out ./...
else
	$(MAKE_COVERAGE_CMD)
endif

test_app_watch:
	find . -name '*.go' | entr -n -c $(MAKE) test_app $(DOCKER_FLAG) $(HTML_FLAG)


define MAKE_COVERAGE_CMD
	go test -v -coverprofile=coverage.out ./... && \
	$(call CLEAN_COVERAGE) && \
	$(call GENERATE_HTML)
endef

define CLEAN_COVERAGE
	if [ "$(shell uname -s)" = "Darwin" ]; then \
		sed -i '' -e '/test/d' -e '/cmd/d' coverage.out; \
	else \
		sed -i '/test/d;/cmd/d;' coverage.out; \
	fi
endef

define GENERATE_HTML
	if [ "$(HTML_FLAG)" = "html" ]; then \
		go tool cover -html=coverage.out -o coverage.html && \
		echo 'Coverage report generated: coverage.html'; \
	fi
endef

.PHONY: docker html


================================================
FILE: README.md
================================================
<p align="center">
  <img src="apps/docs/static/img/social_card.png" alt="Expo Open OTA" />
  <img src="apps/docs/static/img/dashboard_screenshot.png" alt="Expo Open OTA - Dashboard" />
</p>


<h3 align="center">Self-hosted OTA updates for Expo — multi-cloud, production-ready.</h3>

<p align="center">
  An open-source Go server implementing the <a href="https://docs.expo.dev/technical-specs/expo-updates-1/">Expo Updates protocol</a>.<br/>
  Deploy on AWS, GCP, or locally.
</p>

<p align="center">
  <a href="https://axelmarciano.github.io/expo-open-ota/">Documentation</a> · <a href="https://github.com/axelmarciano/expo-open-ota/issues">Issues</a> · <a href="mailto:expoopenota@gmail.com">Contact</a>
</p>

---

## Why Expo Open OTA?

- **Cut costs** — Expo's OTA pricing scales with MAUs. Self-hosting gives you unlimited updates at infrastructure cost only.
- **Own your infrastructure** — Store updates on your cloud, behind your VPN, with your security policies.
- **No vendor lock-in** — Works with AWS, GCP, and any S3-compatible provider. Switch anytime.

## Features

| Feature | Description |
|---------|-------------|
| **Multi-cloud storage** | AWS S3, Google Cloud Storage, S3-compatible (Cloudflare R2, MinIO, DigitalOcean Spaces), local file system |
| **Fast asset delivery** | CloudFront CDN, GCS signed URLs, or direct serving — your choice |
| **One-command publishing** | `npx eoas publish` from your CI/CD pipeline |
| **Secure key management** | AWS Secrets Manager, environment variables, or local key files |
| **Dashboard** | Built-in web UI for monitoring updates, branches, and runtime versions |
| **Prometheus metrics** | Production observability out of the box |
| **No database required** | Zero external dependencies beyond your storage provider |
| **Helm chart** | Ready for Kubernetes deployments |

## Quick Start

[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/MGW3k1?referralCode=OEHlEK&utm_medium=integration&utm_source=template&utm_campaign=generic)

And follow the [Quick Start guide](https://axelmarciano.github.io/expo-open-ota/docs/getting-started/quick-start) to get up and running in minutes.

## Storage Options

| Provider | Mode | Asset Delivery |
|----------|------|----------------|
| **Amazon S3** | `STORAGE_MODE=s3` | Direct or CloudFront CDN |
| **Google Cloud Storage** | `STORAGE_MODE=gcs` | GCS signed URLs |
| **S3-compatible** (R2, MinIO, etc.) | `STORAGE_MODE=s3` + `AWS_BASE_ENDPOINT` | Direct |
| **Local file system** | `STORAGE_MODE=local` | Direct (dev only) |

## Disclaimer

Expo Open OTA is **not officially supported or affiliated with [Expo](https://expo.dev/)**. This is an independent open-source project.

## License

MIT — see [LICENSE](./LICENSE.md).

## Contact

[expoopenota@gmail.com](mailto:expoopenota@gmail.com)


================================================
FILE: apps/dashboard/.gitignore
================================================
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?


================================================
FILE: apps/dashboard/.prettierrc
================================================
{
  "printWidth": 100,
  "tabWidth": 2,
  "singleQuote": true,
  "bracketSameLine": true,
  "trailingComma": "es5",
  "arrowParens": "avoid",
  "endOfLine": "auto"
}


================================================
FILE: apps/dashboard/README.md
================================================
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:

- Configure the top-level `parserOptions` property like this:

```js
export default tseslint.config({
  languageOptions: {
    // other options...
    parserOptions: {
      project: ['./tsconfig.node.json', './tsconfig.app.json'],
      tsconfigRootDir: import.meta.dirname,
    },
  },
})
```

- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
- Optionally add `...tseslint.configs.stylisticTypeChecked`
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:

```js
// eslint.config.js
import react from 'eslint-plugin-react'

export default tseslint.config({
  // Set the react version
  settings: { react: { version: '18.3' } },
  plugins: {
    // Add the react plugin
    react,
  },
  rules: {
    // other rules...
    // Enable its recommended rules
    ...react.configs.recommended.rules,
    ...react.configs['jsx-runtime'].rules,
  },
})
```


================================================
FILE: apps/dashboard/components.json
================================================
{
  "$schema": "https://ui.shadcn.com/schema.json",
  "style": "new-york",
  "rsc": false,
  "tsx": true,
  "tailwind": {
    "config": "tailwind.config.js",
    "css": "src/index.css",
    "baseColor": "zinc",
    "cssVariables": true,
    "prefix": ""
  },
  "aliases": {
    "components": "@/components",
    "utils": "@/lib/utils",
    "ui": "@/components/ui",
    "lib": "@/lib",
    "hooks": "@/hooks"
  },
  "iconLibrary": "lucide"
}

================================================
FILE: apps/dashboard/eslint.config.js
================================================
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'

export default tseslint.config(
  { ignores: ['dist'] },
  {
    extends: [js.configs.recommended, ...tseslint.configs.recommended],
    files: ['**/*.{ts,tsx}'],
    languageOptions: {
      ecmaVersion: 2020,
      globals: globals.browser,
    },
    plugins: {
      'react-hooks': reactHooks,
      'react-refresh': reactRefresh,
    },
    rules: {
      ...reactHooks.configs.recommended.rules,
      'react-refresh/only-export-components': [
        'warn',
        { allowConstantExport: true },
      ],
    },
  },
)


================================================
FILE: apps/dashboard/index.html
================================================
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/dashboard.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <script type="module" src="/env.js"></script>
    <title>Expo Open OTA</title>
  </head>
  <body>
    <div id="root"></div>

    <script type="module" src="/src/main.tsx"></script>
  </body>
</html>


================================================
FILE: apps/dashboard/package.json
================================================
{
  "name": "dashboard",
  "private": true,
  "version": "0.0.0",
  "type": "module",
  "scripts": {
    "dev": "VITE_OTA_API_URL=http://localhost:3000 vite",
    "build": "tsc -b && vite build",
    "lint": "eslint .",
    "preview": "vite preview"
  },
  "dependencies": {
    "@hookform/resolvers": "^3.10.0",
    "@radix-ui/react-dialog": "^1.1.6",
    "@radix-ui/react-label": "^2.1.2",
    "@radix-ui/react-popover": "^1.1.6",
    "@radix-ui/react-progress": "^1.1.2",
    "@radix-ui/react-separator": "^1.1.2",
    "@radix-ui/react-slot": "^1.1.2",
    "@radix-ui/react-toast": "^1.2.6",
    "@radix-ui/react-tooltip": "^1.1.8",
    "@tanstack/react-query": "^5.66.0",
    "@tanstack/react-table": "^8.20.6",
    "class-variance-authority": "^0.7.1",
    "clsx": "^2.1.1",
    "cmdk": "^1.0.0",
    "lucide-react": "^0.475.0",
    "react": "^18.3.1",
    "react-dom": "^18.3.1",
    "react-hook-form": "^7.54.2",
    "react-router": "^7.1.5",
    "tailwind-merge": "^3.0.1",
    "tailwindcss-animate": "^1.0.7",
    "zod": "^3.24.1"
  },
  "devDependencies": {
    "@eslint/js": "^9.19.0",
    "@types/node": "^22.13.1",
    "@types/react": "^19.0.8",
    "@types/react-dom": "^19.0.3",
    "@vitejs/plugin-react-swc": "^3.5.0",
    "autoprefixer": "^10.4.20",
    "eslint": "^9.19.0",
    "eslint-plugin-react-hooks": "^5.0.0",
    "eslint-plugin-react-refresh": "^0.4.18",
    "globals": "^15.14.0",
    "postcss": "^8.5.1",
    "prettier": "^3.1.1",
    "tailwindcss": "^3.4.17",
    "typescript": "~5.7.2",
    "typescript-eslint": "^8.22.0",
    "vite": "^6.1.0"
  }
}


================================================
FILE: apps/dashboard/postcss.config.js
================================================
export default {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  },
}


================================================
FILE: apps/dashboard/public/env.js
================================================
// Will be overwritten by the server


================================================
FILE: apps/dashboard/src/App.tsx
================================================
import { Layout } from '@/containers/Layout';
import { Route, Routes, useNavigate } from 'react-router';
import { isAuthenticated } from '@/lib/auth.ts';
import { useEffect, ReactNode } from 'react';
import { Login } from '@/pages/Login';
import { Toaster } from '@/components/ui/toaster.tsx';
import { Updates } from '@/pages/Updates';
import { Settings } from '@/pages/Settings';
import { Logout } from '@/pages/Logout';
import { Channels } from '@/pages/Channels';

function withLayout(children: ReactNode) {
  return <Layout>{children}</Layout>;
}

export const App = () => {
  const isLoggedIn = isAuthenticated();
  const navigate = useNavigate();

  useEffect(() => {
    if (!isLoggedIn) {
      navigate('/login');
    }
  }, [isLoggedIn, navigate]);

  return (
    <>
      <Toaster />
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route path="/" element={withLayout(<Updates />)} />
        <Route path="/settings" element={withLayout(<Settings />)} />
        <Route path="/channels" element={withLayout(<Channels />)} />
        <Route path="/logout" element={withLayout(<Logout />)} />
      </Routes>
    </>
  );
};


================================================
FILE: apps/dashboard/src/components/APIError/index.tsx
================================================
import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert.tsx';
import { AlertCircle } from 'lucide-react';

export const ApiError = ({ error }: { error: Error }) => {
  return (
    <Alert variant="destructive" className="w-max">
      <AlertCircle className="h-4 w-4" />
      <AlertTitle>An error occurred while fetching data</AlertTitle>
      <AlertDescription>{error.message}</AlertDescription>
    </Alert>
  );
};


================================================
FILE: apps/dashboard/src/components/Combobox/index.tsx
================================================
'use client';

import * as React from 'react';
import { Check, ChevronsUpDown } from 'lucide-react';

import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
  Command,
  CommandEmpty,
  CommandGroup,
  CommandInput,
  CommandItem,
  CommandList,
} from '@/components/ui/command';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';

interface ComboboxProps {
  options: { value: string; label: string }[];
  value: string;
  onChange: (value: string) => void;
  loading?: boolean;
  label?: string;
}

export function Combobox(props: ComboboxProps) {
  const [open, setOpen] = React.useState(false);
  const { options, value, onChange, loading, label } = props;
  return (
    <Popover open={open} onOpenChange={setOpen}>
      <PopoverTrigger asChild>
        <Button
          variant="outline"
          role="combobox"
          aria-expanded={open}
          className="w-max justify-between">
          {value ? options.find(opt => opt.value === value)?.label : label || 'Select option'}
          <ChevronsUpDown className="ml-2 h-4 w-4 shrink-0 opacity-50" />
        </Button>
      </PopoverTrigger>
      <PopoverContent className="w-max p-0">
        <Command>
          <CommandInput placeholder="Search..." />
          <CommandList>
            <CommandEmpty>No option found.</CommandEmpty>
            <CommandGroup>
              {options.map(opt => (
                <CommandItem
                  key={opt.value}
                  value={opt.value}
                  onSelect={currentValue => {
                    onChange(currentValue === value ? '' : currentValue);
                    setOpen(false);
                  }}>
                  <Check
                    className={cn(
                      'mr-2 h-4 w-4',
                      value === opt.value ? 'opacity-100' : 'opacity-0'
                    )}
                  />
                  {opt.label}
                </CommandItem>
              ))}
              {loading && <CommandItem disabled>Loading...</CommandItem>}
            </CommandGroup>
          </CommandList>
        </Command>
      </PopoverContent>
    </Popover>
  );
}


================================================
FILE: apps/dashboard/src/components/DataTable/index.tsx
================================================
import {
  ColumnDef,
  flexRender,
  getCoreRowModel,
  getSortedRowModel,
  SortingState,
  useReactTable,
} from '@tanstack/react-table';

import {
  Table,
  TableBody,
  TableCell,
  TableHead,
  TableHeader,
  TableRow,
} from '@/components/ui/table';
import { Skeleton } from '@/components/ui/skeleton.tsx';
import { useState } from 'react';
import { ArrowDown, ArrowUp, ArrowUpDown } from 'lucide-react';

interface DataTableProps<TData, TValue> {
  columns: ColumnDef<TData, TValue>[];
  data: TData[];
  loading?: boolean;
  onRowClick?: (row: TData) => void;
  defaultSorting?: SortingState;
}

export function DataTable<TData, TValue>({
  columns,
  data,
  loading,
  onRowClick = (_row) => {},
  defaultSorting = [],
}: DataTableProps<TData, TValue>) {
  const [sorting, setSorting] = useState<SortingState>(defaultSorting);

  const table = useReactTable({
    data,
    columns,
    getCoreRowModel: getCoreRowModel(),
    getSortedRowModel: getSortedRowModel(),
    onSortingChange: setSorting,
    state: {
      sorting,
    },
  });

  return (
    <div className="rounded-md border w-full">
      <Table className="w-full">
        <TableHeader className="w-full">
          {table.getHeaderGroups().map(headerGroup => (
            <TableRow key={headerGroup.id}>
              {headerGroup.headers.map(header => {
                return (
                  <TableHead
                    key={header.id}
                    className={header.column.getCanSort() ? 'cursor-pointer select-none' : ''}
                    onClick={header.column.getToggleSortingHandler()}>
                    <div className="flex items-center gap-1">
                      {header.isPlaceholder
                        ? null
                        : flexRender(header.column.columnDef.header, header.getContext())}
                      {header.column.getCanSort() && (
                        <>
                          {header.column.getIsSorted() === 'asc' ? (
                            <ArrowUp className="w-3.5 h-3.5" />
                          ) : header.column.getIsSorted() === 'desc' ? (
                            <ArrowDown className="w-3.5 h-3.5" />
                          ) : (
                            <ArrowUpDown className="w-3.5 h-3.5 opacity-40" />
                          )}
                        </>
                      )}
                    </div>
                  </TableHead>
                );
              })}
            </TableRow>
          ))}
        </TableHeader>
        <TableBody className="w-full">
          {loading &&
            Array.from({ length: 5 }).map(() => (
              <TableRow key={Math.random()}>
                {columns.map((_, i) => (
                  <TableCell key={i}>
                    <Skeleton className="h-4 w-full" />
                  </TableCell>
                ))}
              </TableRow>
            ))}
          {table.getRowModel().rows?.length
            ? table.getRowModel().rows.map(row => (
                <TableRow key={row.id} data-state={row.getIsSelected() && 'selected'}
                          onClick={() => onRowClick(row.original)}
                >
                  {row.getVisibleCells().map(cell => (
                    <TableCell key={cell.id}>
                      {flexRender(cell.column.columnDef.cell, cell.getContext())}
                    </TableCell>
                  ))}
                </TableRow>
              ))
            : null}
          {!loading && !table.getRowModel().rows?.length && (
            <TableRow>
              <TableCell colSpan={columns.length} className="h-24 text-center">
                No results.
              </TableCell>
            </TableRow>
          )}
        </TableBody>
      </Table>
    </div>
  );
}


================================================
FILE: apps/dashboard/src/components/UpdateDetailsSheet/index.tsx
================================================
import { forwardRef, useImperativeHandle, useState } from 'react';
import {
  Sheet,
  SheetContent,
  SheetDescription,
  SheetHeader,
  SheetTitle,
} from '@/components/ui/sheet.tsx';
import { Label } from '@/components/ui/label.tsx';
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api.ts';
import { Skeleton } from '@/components/ui/skeleton.tsx';
import { ApiError } from '@/components/APIError';
import { Badge } from '@/components/ui/badge.tsx';

interface Update {
  updateUUID: string;
  createdAt: string;
  updateId: string;
  platform: string;
  commitHash: string;
}

export type UpdateDetailsRef = {
  openSheet: (update: Update) => void;
  closeSheet: () => void;
};

const UpdateDetails = ({
  update,
  branch,
  runtimeVersion,
}: {
  update: Update | null;
  branch: string;
  runtimeVersion: string;
}) => {
  const { data, isLoading, error } = useQuery({
    queryKey: [`update-details-${update?.updateUUID}`],
    enabled: !!update?.updateId,
    queryFn: () => api.getUpdateDetails(branch, runtimeVersion, update?.updateId as string),
  });
  const updateDetails = data;
  if (!update) {
    return (
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Update details</SheetTitle>
        </SheetHeader>
        <Skeleton className="h-full w-full" />
      </SheetContent>
    );
  }
  if (isLoading) {
    return (
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Update details</SheetTitle>
          <SheetDescription>{update.updateId}</SheetDescription>
        </SheetHeader>
        <Skeleton className="h-full w-full" />
      </SheetContent>
    );
  }
  if (error) {
    return (
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Update details</SheetTitle>
          <SheetDescription>{update.updateId}</SheetDescription>
        </SheetHeader>
        <div className="flex flex-col items-center justify-center h-full">
          <ApiError error={error} />
        </div>
      </SheetContent>
    );
  }
  if (!updateDetails) {
    return (
      <SheetContent>
        <SheetHeader>
          <SheetTitle>Update details</SheetTitle>
          <SheetDescription>{update.updateId}</SheetDescription>
        </SheetHeader>
        <Skeleton className="h-full w-full" />
      </SheetContent>
    );
  }
  return (
    <SheetContent style={{ maxWidth: 'none' }} className="w-[800px] overflow-scroll">
      <SheetHeader>
        <SheetTitle>Update details</SheetTitle>
        <SheetDescription>{updateDetails.updateId}</SheetDescription>
      </SheetHeader>
      <div className="grid gap-4 py-4 overflow-scroll">
        <div className="grid grid-cols-4 items-center gap-4">
          <Label>Update ID</Label>
          <Badge variant="outline" className="col-span-3">
            {updateDetails.updateId}
          </Badge>
        </div>
        <div className="grid grid-cols-4 items-center gap-4">
          <Label>Branch</Label>
          <Badge variant="outline" className="col-span-3">
            {branch}
          </Badge>
        </div>
        <div className="grid grid-cols-4 items-center gap-4">
          <Label>Runtime version</Label>
          <Badge variant="outline" className="col-span-3">
            {runtimeVersion}
          </Badge>
        </div>
        <div className="grid grid-cols-4 items-center gap-4">
          <Label>Created At</Label>
          <Badge variant="outline" className="col-span-3">
            {new Date(updateDetails.createdAt).toLocaleDateString('en-GB', {
              year: 'numeric',
              month: 'long',
              day: 'numeric',
              hour: 'numeric',
              minute: 'numeric',
              second: 'numeric',
            })}
          </Badge>
        </div>
        <div className="grid grid-cols-4 items-center gap-4">
          <Label>UUID</Label>
          <Badge variant="outline" className="col-span-3">
            {updateDetails.updateUUID}
          </Badge>
        </div>
        <div className="grid grid-cols-4 items-center gap-4">
          <Label>Commit</Label>
          <Badge variant="outline" className="col-span-3 break-all">
            {updateDetails.commitHash}
          </Badge>
        </div>
        {updateDetails.message && (
          <div className="grid grid-cols-4 items-center gap-4">
            <Label>Message</Label>
            <Badge variant="outline" className="col-span-3 break-all">
              {updateDetails.message}
            </Badge>
          </div>
        )}
        <div className="grid grid-cols-4 items-center gap-4">
          <Label>Platform</Label>
          <Badge variant="outline" className="col-span-3 break-all">
            {updateDetails.platform}
          </Badge>
        </div>
        <div className="grid grid-cols-4 items-center gap-4">
          <Label>Type</Label>
          <Badge variant="outline" className="col-span-3 break-all">
            {updateDetails.type === 0 ? 'Normal update' : 'Rollback'}
          </Badge>
        </div>
      </div>
    </SheetContent>
  );
};

type Props = {
  branch: string;
  runtimeVersion: string;
};

export const UpdateDetailsSheet = forwardRef<UpdateDetailsRef, Props>(
  (
    {
      branch,
      runtimeVersion,
    }: {
      branch: string;
      runtimeVersion: string;
    },
    ref
  ) => {
    const [currentUpdate, setCurrentUpdate] = useState<Update | null>(null);
    useImperativeHandle(ref, () => ({
      openSheet: update => {
        setCurrentUpdate(update);
      },
      closeSheet: () => {
        setCurrentUpdate(null);
      },
    }));
    return (
      <Sheet
        open={!!currentUpdate}
        defaultOpen={false}
        onOpenChange={o => {
          if (!o) {
            setCurrentUpdate(null);
          }
        }}>
        <UpdateDetails branch={branch} runtimeVersion={runtimeVersion} update={currentUpdate} />
      </Sheet>
    );
  }
);


================================================
FILE: apps/dashboard/src/components/app-sidebar.tsx
================================================
import { Link, useLocation } from 'react-router';
import {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarHeader,
  SidebarGroupContent,
  SidebarMenu,
  SidebarMenuButton,
  SidebarMenuItem,
} from '@/components/ui/sidebar';
import { Box, HardDriveDownload, PowerOff, Settings } from 'lucide-react';
import clsx from 'clsx';

const items = [
  {
    title: 'Updates',
    url: '/',
    icon: HardDriveDownload,
  },
  {
    title: 'Channels',
    url: '/channels',
    icon: Box,
  },
  {
    title: 'Settings',
    url: '/settings',
    icon: Settings,
  },
  {
    title: 'Logout',
    url: '/logout',
    icon: PowerOff,
  },
];

export function AppSidebar() {
  const location = useLocation();
  const currentPath = location.pathname;

  return (
    <Sidebar className="w-64 bg-white border-r border-gray-200">
      <SidebarHeader className="p-4 border-b">
        <h1 className="text-lg font-semibold">Expo Open OTA</h1>
      </SidebarHeader>
      <SidebarContent className="p-2">
        <SidebarGroup>
          <SidebarGroupContent>
            <SidebarMenu>
              {items.map(item => {
                const isActive = currentPath === item.url;
                return (
                  <SidebarMenuItem key={item.title}>
                    <SidebarMenuButton asChild disabled={isActive}>
                      <Link
                        to={item.url}
                        onClick={e => {
                          if (isActive) {
                            e.preventDefault();
                          }
                        }}
                        className={clsx(
                          'flex items-center gap-2 px-4 py-2 rounded-lg transition',
                          isActive ? 'bg-gray-200 text-black' : 'text-gray-500 hover:bg-gray-100'
                        )}>
                        <item.icon className="w-5 h-5" />
                        <span>{item.title}</span>
                      </Link>
                    </SidebarMenuButton>
                  </SidebarMenuItem>
                );
              })}
            </SidebarMenu>
          </SidebarGroupContent>
        </SidebarGroup>
      </SidebarContent>
      <SidebarFooter />
    </Sidebar>
  );
}


================================================
FILE: apps/dashboard/src/components/ui/alert.tsx
================================================
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const alertVariants = cva(
  "relative w-full rounded-lg border px-4 py-3 text-sm [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground [&>svg~*]:pl-7",
  {
    variants: {
      variant: {
        default: "bg-background text-foreground",
        destructive:
          "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

const Alert = React.forwardRef<
  HTMLDivElement,
  React.HTMLAttributes<HTMLDivElement> & VariantProps<typeof alertVariants>
>(({ className, variant, ...props }, ref) => (
  <div
    ref={ref}
    role="alert"
    className={cn(alertVariants({ variant }), className)}
    {...props}
  />
))
Alert.displayName = "Alert"

const AlertTitle = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
  <h5
    ref={ref}
    className={cn("mb-1 font-medium leading-none tracking-tight", className)}
    {...props}
  />
))
AlertTitle.displayName = "AlertTitle"

const AlertDescription = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
  <div
    ref={ref}
    className={cn("text-sm [&_p]:leading-relaxed", className)}
    {...props}
  />
))
AlertDescription.displayName = "AlertDescription"

export { Alert, AlertTitle, AlertDescription }


================================================
FILE: apps/dashboard/src/components/ui/badge.tsx
================================================
import * as React from "react"
import { cva, type VariantProps } from "class-variance-authority"

import { cn } from "@/lib/utils"

const badgeVariants = cva(
  "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
  {
    variants: {
      variant: {
        default:
          "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
        secondary:
          "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
        destructive:
          "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
        outline: "text-foreground",
      },
    },
    defaultVariants: {
      variant: "default",
    },
  }
)

export interface BadgeProps
  extends React.HTMLAttributes<HTMLDivElement>,
    VariantProps<typeof badgeVariants> {}

function Badge({ className, variant, ...props }: BadgeProps) {
  return (
    <div className={cn(badgeVariants({ variant }), className)} {...props} />
  )
}

export { Badge, badgeVariants }


================================================
FILE: apps/dashboard/src/components/ui/breadcrumb.tsx
================================================
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { ChevronRight, MoreHorizontal } from "lucide-react"

import { cn } from "@/lib/utils"

const Breadcrumb = React.forwardRef<
  HTMLElement,
  React.ComponentPropsWithoutRef<"nav"> & {
    separator?: React.ReactNode
  }
>(({ ...props }, ref) => <nav ref={ref} aria-label="breadcrumb" {...props} />)
Breadcrumb.displayName = "Breadcrumb"

const BreadcrumbList = React.forwardRef<
  HTMLOListElement,
  React.ComponentPropsWithoutRef<"ol">
>(({ className, ...props }, ref) => (
  <ol
    ref={ref}
    className={cn(
      "flex flex-wrap items-center gap-1.5 break-words text-sm text-muted-foreground sm:gap-2.5",
      className
    )}
    {...props}
  />
))
BreadcrumbList.displayName = "BreadcrumbList"

const BreadcrumbItem = React.forwardRef<
  HTMLLIElement,
  React.ComponentPropsWithoutRef<"li">
>(({ className, ...props }, ref) => (
  <li
    ref={ref}
    className={cn("inline-flex items-center gap-1.5", className)}
    {...props}
  />
))
BreadcrumbItem.displayName = "BreadcrumbItem"

const BreadcrumbLink = React.forwardRef<
  HTMLAnchorElement,
  React.ComponentPropsWithoutRef<"a"> & {
    asChild?: boolean
  }
>(({ asChild, className, ...props }, ref) => {
  const Comp = asChild ? Slot : "a"

  return (
    <Comp
      ref={ref}
      className={cn("transition-colors hover:text-foreground", className)}
      {...props}
    />
  )
})
BreadcrumbLink.displayName = "BreadcrumbLink"

const BreadcrumbPage = React.forwardRef<
  HTMLSpanElement,
  React.ComponentPropsWithoutRef<"span">
>(({ className, ...props }, ref) => (
  <span
    ref={ref}
    role="link"
    aria-disabled="true"
    aria-current="page"
    className={cn("font-normal text-foreground", className)}
    {...props}
  />
))
BreadcrumbPage.displayName = "BreadcrumbPage"

const BreadcrumbSeparator = ({
  children,
  className,
  ...props
}: React.ComponentProps<"li">) => (
  <li
    role="presentation"
    aria-hidden="true"
    className={cn("[&>svg]:w-3.5 [&>svg]:h-3.5", className)}
    {...props}
  >
    {children ?? <ChevronRight />}
  </li>
)
BreadcrumbSeparator.displayName = "BreadcrumbSeparator"

const BreadcrumbEllipsis = ({
  className,
  ...props
}: React.ComponentProps<"span">) => (
  <span
    role="presentation"
    aria-hidden="true"
    className={cn("flex h-9 w-9 items-center justify-center", className)}
    {...props}
  >
    <MoreHorizontal className="h-4 w-4" />
    <span className="sr-only">More</span>
  </span>
)
BreadcrumbEllipsis.displayName = "BreadcrumbElipssis"

export {
  Breadcrumb,
  BreadcrumbList,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbPage,
  BreadcrumbSeparator,
  BreadcrumbEllipsis,
}


================================================
FILE: apps/dashboard/src/components/ui/button.tsx
================================================
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { cva, type VariantProps } from 'class-variance-authority';

import { cn } from '@/lib/utils';

const buttonVariants = cva(
  'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
  {
    variants: {
      variant: {
        default: 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
        destructive: 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
        outline:
          'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
        secondary: 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
        ghost: 'hover:bg-accent hover:text-accent-foreground',
        link: 'text-primary underline-offset-4 hover:underline',
      },
      size: {
        default: 'h-9 px-4 py-2',
        sm: 'h-8 rounded-md px-3 text-xs',
        lg: 'h-10 rounded-md px-8',
        icon: 'h-9 w-9',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

export interface ButtonProps
  extends React.ButtonHTMLAttributes<HTMLButtonElement>,
    VariantProps<typeof buttonVariants> {
  asChild?: boolean;
}

const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
  ({ className, variant, size, asChild = false, ...props }, ref) => {
    const Comp = asChild ? Slot : 'button';
    return (
      <Comp className={cn(buttonVariants({ variant, size, className }))} ref={ref} {...props} />
    );
  }
);
Button.displayName = 'Button';

export { Button, buttonVariants };


================================================
FILE: apps/dashboard/src/components/ui/card.tsx
================================================
import * as React from 'react';

import { cn } from '@/lib/utils';

const Card = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('rounded-xl border bg-card text-card-foreground shadow', className)}
      {...props}
    />
  )
);
Card.displayName = 'Card';

const CardHeader = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div ref={ref} className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />
  )
);
CardHeader.displayName = 'CardHeader';

const CardTitle = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      className={cn('font-semibold leading-none tracking-tight', className)}
      {...props}
    />
  )
);
CardTitle.displayName = 'CardTitle';

const CardDescription = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div ref={ref} className={cn('text-sm text-muted-foreground', className)} {...props} />
  )
);
CardDescription.displayName = 'CardDescription';

const CardContent = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
  )
);
CardContent.displayName = 'CardContent';

const CardFooter = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => (
    <div ref={ref} className={cn('flex items-center p-6 pt-0', className)} {...props} />
  )
);
CardFooter.displayName = 'CardFooter';

export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };


================================================
FILE: apps/dashboard/src/components/ui/command.tsx
================================================
import * as React from 'react';
import { type DialogProps } from '@radix-ui/react-dialog';
import { Command as CommandPrimitive } from 'cmdk';
import { Search } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Dialog, DialogContent } from '@/components/ui/dialog';

const Command = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive>
>(({ className, ...props }, ref) => (
  <CommandPrimitive
    ref={ref}
    className={cn(
      'flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground',
      className
    )}
    {...props}
  />
));
Command.displayName = CommandPrimitive.displayName;

const CommandDialog = ({ children, ...props }: DialogProps) => {
  return (
    <Dialog {...props}>
      <DialogContent className="overflow-hidden p-0">
        <Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5">
          {children}
        </Command>
      </DialogContent>
    </Dialog>
  );
};

const CommandInput = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Input>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
>(({ className, ...props }, ref) => (
  <div className="flex items-center border-b px-3" cmdk-input-wrapper="">
    <Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
    <CommandPrimitive.Input
      ref={ref}
      className={cn(
        'flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50',
        className
      )}
      {...props}
    />
  </div>
));

CommandInput.displayName = CommandPrimitive.Input.displayName;

const CommandList = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.List>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.List
    ref={ref}
    className={cn('max-h-[300px] overflow-y-auto overflow-x-hidden', className)}
    {...props}
  />
));

CommandList.displayName = CommandPrimitive.List.displayName;

const CommandEmpty = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Empty>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
>((props, ref) => (
  <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />
));

CommandEmpty.displayName = CommandPrimitive.Empty.displayName;

const CommandGroup = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Group>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.Group
    ref={ref}
    className={cn(
      'overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground',
      className
    )}
    {...props}
  />
));

CommandGroup.displayName = CommandPrimitive.Group.displayName;

const CommandSeparator = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Separator>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.Separator
    ref={ref}
    className={cn('-mx-1 h-px bg-border', className)}
    {...props}
  />
));
CommandSeparator.displayName = CommandPrimitive.Separator.displayName;

const CommandItem = React.forwardRef<
  React.ElementRef<typeof CommandPrimitive.Item>,
  React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
>(({ className, ...props }, ref) => (
  <CommandPrimitive.Item
    ref={ref}
    className={cn(
      'relative flex cursor-default gap-2 select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none data-[disabled=true]:pointer-events-none data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
      className
    )}
    {...props}
  />
));

CommandItem.displayName = CommandPrimitive.Item.displayName;

const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
  return (
    <span
      className={cn('ml-auto text-xs tracking-widest text-muted-foreground', className)}
      {...props}
    />
  );
};
CommandShortcut.displayName = 'CommandShortcut';

export {
  Command,
  CommandDialog,
  CommandInput,
  CommandList,
  CommandEmpty,
  CommandGroup,
  CommandItem,
  CommandShortcut,
  CommandSeparator,
};


================================================
FILE: apps/dashboard/src/components/ui/dialog.tsx
================================================
"use client"

import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"

import { cn } from "@/lib/utils"

const Dialog = DialogPrimitive.Root

const DialogTrigger = DialogPrimitive.Trigger

const DialogPortal = DialogPrimitive.Portal

const DialogClose = DialogPrimitive.Close

const DialogOverlay = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Overlay
    ref={ref}
    className={cn(
      "fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
      className
    )}
    {...props}
  />
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName

const DialogContent = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
  <DialogPortal>
    <DialogOverlay />
    <DialogPrimitive.Content
      ref={ref}
      className={cn(
        "fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
        className
      )}
      {...props}
    >
      {children}
      <DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
        <X className="h-4 w-4" />
        <span className="sr-only">Close</span>
      </DialogPrimitive.Close>
    </DialogPrimitive.Content>
  </DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName

const DialogHeader = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col space-y-1.5 text-center sm:text-left",
      className
    )}
    {...props}
  />
)
DialogHeader.displayName = "DialogHeader"

const DialogFooter = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
      className
    )}
    {...props}
  />
)
DialogFooter.displayName = "DialogFooter"

const DialogTitle = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Title
    ref={ref}
    className={cn(
      "text-lg font-semibold leading-none tracking-tight",
      className
    )}
    {...props}
  />
))
DialogTitle.displayName = DialogPrimitive.Title.displayName

const DialogDescription = React.forwardRef<
  React.ElementRef<typeof DialogPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
  <DialogPrimitive.Description
    ref={ref}
    className={cn("text-sm text-muted-foreground", className)}
    {...props}
  />
))
DialogDescription.displayName = DialogPrimitive.Description.displayName

export {
  Dialog,
  DialogPortal,
  DialogOverlay,
  DialogTrigger,
  DialogClose,
  DialogContent,
  DialogHeader,
  DialogFooter,
  DialogTitle,
  DialogDescription,
}


================================================
FILE: apps/dashboard/src/components/ui/form.tsx
================================================
import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { Slot } from '@radix-ui/react-slot';
import {
  Controller,
  ControllerProps,
  FieldPath,
  FieldValues,
  FormProvider,
  useFormContext,
} from 'react-hook-form';

import { cn } from '@/lib/utils';
import { Label } from '@/components/ui/label';

const Form = FormProvider;

type FormFieldContextValue<
  TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
> = {
  name: TName;
};

const FormFieldContext = React.createContext<FormFieldContextValue>({} as FormFieldContextValue);

const FormField = <
  TFieldValues extends FieldValues = FieldValues,
  TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
>({
  ...props
}: ControllerProps<TFieldValues, TName>) => {
  return (
    <FormFieldContext.Provider value={{ name: props.name }}>
      <Controller {...props} />
    </FormFieldContext.Provider>
  );
};

const useFormField = () => {
  const fieldContext = React.useContext(FormFieldContext);
  const itemContext = React.useContext(FormItemContext);
  const { getFieldState, formState } = useFormContext();

  const fieldState = getFieldState(fieldContext.name, formState);

  if (!fieldContext) {
    throw new Error('useFormField should be used within <FormField>');
  }

  const { id } = itemContext;

  return {
    id,
    name: fieldContext.name,
    formItemId: `${id}-form-item`,
    formDescriptionId: `${id}-form-item-description`,
    formMessageId: `${id}-form-item-message`,
    ...fieldState,
  };
};

type FormItemContextValue = {
  id: string;
};

const FormItemContext = React.createContext<FormItemContextValue>({} as FormItemContextValue);

const FormItem = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(
  ({ className, ...props }, ref) => {
    const id = React.useId();

    return (
      <FormItemContext.Provider value={{ id }}>
        <div ref={ref} className={cn('space-y-2', className)} {...props} />
      </FormItemContext.Provider>
    );
  }
);
FormItem.displayName = 'FormItem';

const FormLabel = React.forwardRef<
  React.ElementRef<typeof LabelPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
  const { error, formItemId } = useFormField();

  return (
    <Label
      ref={ref}
      className={cn(error && 'text-destructive', className)}
      htmlFor={formItemId}
      {...props}
    />
  );
});
FormLabel.displayName = 'FormLabel';

const FormControl = React.forwardRef<
  React.ElementRef<typeof Slot>,
  React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
  const { error, formItemId, formDescriptionId, formMessageId } = useFormField();

  return (
    <Slot
      ref={ref}
      id={formItemId}
      aria-describedby={!error ? `${formDescriptionId}` : `${formDescriptionId} ${formMessageId}`}
      aria-invalid={!!error}
      {...props}
    />
  );
});
FormControl.displayName = 'FormControl';

const FormDescription = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
  const { formDescriptionId } = useFormField();

  return (
    <p
      ref={ref}
      id={formDescriptionId}
      className={cn('text-[0.8rem] text-muted-foreground', className)}
      {...props}
    />
  );
});
FormDescription.displayName = 'FormDescription';

const FormMessage = React.forwardRef<
  HTMLParagraphElement,
  React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
  const { error, formMessageId } = useFormField();
  const body = error ? String(error?.message) : children;

  if (!body) {
    return null;
  }

  return (
    <p
      ref={ref}
      id={formMessageId}
      className={cn('text-[0.8rem] font-medium text-destructive', className)}
      {...props}>
      {body}
    </p>
  );
});
FormMessage.displayName = 'FormMessage';

export {
  useFormField,
  Form,
  FormItem,
  FormLabel,
  FormControl,
  FormDescription,
  FormMessage,
  FormField,
};


================================================
FILE: apps/dashboard/src/components/ui/input.tsx
================================================
import * as React from 'react';

import { cn } from '@/lib/utils';

const Input = React.forwardRef<HTMLInputElement, React.ComponentProps<'input'>>(
  ({ className, type, ...props }, ref) => {
    return (
      <input
        type={type}
        className={cn(
          'flex h-9 w-full rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-sm transition-colors file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',
          className
        )}
        ref={ref}
        {...props}
      />
    );
  }
);
Input.displayName = 'Input';

export { Input };


================================================
FILE: apps/dashboard/src/components/ui/label.tsx
================================================
'use client';

import * as React from 'react';
import * as LabelPrimitive from '@radix-ui/react-label';
import { cva, type VariantProps } from 'class-variance-authority';

import { cn } from '@/lib/utils';

const labelVariants = cva(
  'text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70'
);

const Label = React.forwardRef<
  React.ElementRef<typeof LabelPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> & VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
  <LabelPrimitive.Root ref={ref} className={cn(labelVariants(), className)} {...props} />
));
Label.displayName = LabelPrimitive.Root.displayName;

export { Label };


================================================
FILE: apps/dashboard/src/components/ui/popover.tsx
================================================
import * as React from 'react';
import * as PopoverPrimitive from '@radix-ui/react-popover';

import { cn } from '@/lib/utils';

const Popover = PopoverPrimitive.Root;

const PopoverTrigger = PopoverPrimitive.Trigger;

const PopoverAnchor = PopoverPrimitive.Anchor;

const PopoverContent = React.forwardRef<
  React.ElementRef<typeof PopoverPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof PopoverPrimitive.Content>
>(({ className, align = 'center', sideOffset = 4, ...props }, ref) => (
  <PopoverPrimitive.Portal>
    <PopoverPrimitive.Content
      ref={ref}
      align={align}
      sideOffset={sideOffset}
      className={cn(
        'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
        className
      )}
      {...props}
    />
  </PopoverPrimitive.Portal>
));
PopoverContent.displayName = PopoverPrimitive.Content.displayName;

export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor };


================================================
FILE: apps/dashboard/src/components/ui/progress.tsx
================================================
import * as React from 'react';
import * as ProgressPrimitive from '@radix-ui/react-progress';

import { cn } from '@/lib/utils';

const Progress = React.forwardRef<
  React.ElementRef<typeof ProgressPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
>(({ className, value, ...props }, ref) => (
  <ProgressPrimitive.Root
    ref={ref}
    className={cn('relative h-2 w-full overflow-hidden rounded-full bg-primary/20', className)}
    {...props}>
    <ProgressPrimitive.Indicator
      className="h-full w-full flex-1 bg-primary transition-all"
      style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
    />
  </ProgressPrimitive.Root>
));
Progress.displayName = ProgressPrimitive.Root.displayName;

export { Progress };


================================================
FILE: apps/dashboard/src/components/ui/separator.tsx
================================================
import * as React from 'react';
import * as SeparatorPrimitive from '@radix-ui/react-separator';

import { cn } from '@/lib/utils';

const Separator = React.forwardRef<
  React.ElementRef<typeof SeparatorPrimitive.Root>,
  React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(({ className, orientation = 'horizontal', decorative = true, ...props }, ref) => (
  <SeparatorPrimitive.Root
    ref={ref}
    decorative={decorative}
    orientation={orientation}
    className={cn(
      'shrink-0 bg-border',
      orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
      className
    )}
    {...props}
  />
));
Separator.displayName = SeparatorPrimitive.Root.displayName;

export { Separator };


================================================
FILE: apps/dashboard/src/components/ui/sheet.tsx
================================================
'use client';

import * as React from 'react';
import * as SheetPrimitive from '@radix-ui/react-dialog';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';

import { cn } from '@/lib/utils';

const Sheet = SheetPrimitive.Root;

const SheetTrigger = SheetPrimitive.Trigger;

const SheetClose = SheetPrimitive.Close;

const SheetPortal = SheetPrimitive.Portal;

const SheetOverlay = React.forwardRef<
  React.ElementRef<typeof SheetPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
  <SheetPrimitive.Overlay
    className={cn(
      'fixed inset-0 z-50 bg-black/80  data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
      className
    )}
    {...props}
    ref={ref}
  />
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;

const sheetVariants = cva(
  'fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=closed]:duration-300 data-[state=open]:duration-500 data-[state=open]:animate-in data-[state=closed]:animate-out',
  {
    variants: {
      side: {
        top: 'inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top',
        bottom:
          'inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom',
        left: 'inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm',
        right:
          'inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm',
      },
    },
    defaultVariants: {
      side: 'right',
    },
  }
);

interface SheetContentProps
  extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
    VariantProps<typeof sheetVariants> {}

const SheetContent = React.forwardRef<
  React.ElementRef<typeof SheetPrimitive.Content>,
  SheetContentProps
>(({ side = 'right', className, children, ...props }, ref) => (
  <SheetPortal>
    <SheetOverlay />
    <SheetPrimitive.Content ref={ref} className={cn(sheetVariants({ side }), className)} {...props}>
      <SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
        <X className="h-4 w-4" />
        <span className="sr-only">Close</span>
      </SheetPrimitive.Close>
      {children}
    </SheetPrimitive.Content>
  </SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;

const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
  <div className={cn('flex flex-col space-y-2 text-center sm:text-left', className)} {...props} />
);
SheetHeader.displayName = 'SheetHeader';

const SheetFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn('flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2', className)}
    {...props}
  />
);
SheetFooter.displayName = 'SheetFooter';

const SheetTitle = React.forwardRef<
  React.ElementRef<typeof SheetPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
  <SheetPrimitive.Title
    ref={ref}
    className={cn('text-lg font-semibold text-foreground', className)}
    {...props}
  />
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;

const SheetDescription = React.forwardRef<
  React.ElementRef<typeof SheetPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
  <SheetPrimitive.Description
    ref={ref}
    className={cn('text-sm text-muted-foreground', className)}
    {...props}
  />
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;

export {
  Sheet,
  SheetPortal,
  SheetOverlay,
  SheetTrigger,
  SheetClose,
  SheetContent,
  SheetHeader,
  SheetFooter,
  SheetTitle,
  SheetDescription,
};


================================================
FILE: apps/dashboard/src/components/ui/sidebar.tsx
================================================
import * as React from 'react';
import { Slot } from '@radix-ui/react-slot';
import { VariantProps, cva } from 'class-variance-authority';
import { PanelLeft } from 'lucide-react';

import { useIsMobile } from '@/hooks/use-mobile';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { Sheet, SheetContent } from '@/components/ui/sheet';
import { Skeleton } from '@/components/ui/skeleton';
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';

const SIDEBAR_COOKIE_NAME = 'sidebar_state';
const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
const SIDEBAR_WIDTH = '16rem';
const SIDEBAR_WIDTH_MOBILE = '18rem';
const SIDEBAR_WIDTH_ICON = '3rem';
const SIDEBAR_KEYBOARD_SHORTCUT = 'b';

type SidebarContext = {
  state: 'expanded' | 'collapsed';
  open: boolean;
  setOpen: (open: boolean) => void;
  openMobile: boolean;
  setOpenMobile: (open: boolean) => void;
  isMobile: boolean;
  toggleSidebar: () => void;
};

const SidebarContext = React.createContext<SidebarContext | null>(null);

function useSidebar() {
  const context = React.useContext(SidebarContext);
  if (!context) {
    throw new Error('useSidebar must be used within a SidebarProvider.');
  }

  return context;
}

const SidebarProvider = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<'div'> & {
    defaultOpen?: boolean;
    open?: boolean;
    onOpenChange?: (open: boolean) => void;
  }
>(
  (
    {
      defaultOpen = true,
      open: openProp,
      onOpenChange: setOpenProp,
      className,
      style,
      children,
      ...props
    },
    ref
  ) => {
    const isMobile = useIsMobile();
    const [openMobile, setOpenMobile] = React.useState(false);

    // This is the internal state of the sidebar.
    // We use openProp and setOpenProp for control from outside the component.
    const [_open, _setOpen] = React.useState(defaultOpen);
    const open = openProp ?? _open;
    const setOpen = React.useCallback(
      (value: boolean | ((value: boolean) => boolean)) => {
        const openState = typeof value === 'function' ? value(open) : value;
        if (setOpenProp) {
          setOpenProp(openState);
        } else {
          _setOpen(openState);
        }

        // This sets the cookie to keep the sidebar state.
        document.cookie = `${SIDEBAR_COOKIE_NAME}=${openState}; path=/; max-age=${SIDEBAR_COOKIE_MAX_AGE}`;
      },
      [setOpenProp, open]
    );

    // Helper to toggle the sidebar.
    const toggleSidebar = React.useCallback(() => {
      return isMobile ? setOpenMobile(open => !open) : setOpen(open => !open);
    }, [isMobile, setOpen, setOpenMobile]);

    // Adds a keyboard shortcut to toggle the sidebar.
    React.useEffect(() => {
      const handleKeyDown = (event: KeyboardEvent) => {
        if (event.key === SIDEBAR_KEYBOARD_SHORTCUT && (event.metaKey || event.ctrlKey)) {
          event.preventDefault();
          toggleSidebar();
        }
      };

      window.addEventListener('keydown', handleKeyDown);
      return () => window.removeEventListener('keydown', handleKeyDown);
    }, [toggleSidebar]);

    // We add a state so that we can do data-state="expanded" or "collapsed".
    // This makes it easier to style the sidebar with Tailwind classes.
    const state = open ? 'expanded' : 'collapsed';

    const contextValue = React.useMemo<SidebarContext>(
      () => ({
        state,
        open,
        setOpen,
        isMobile,
        openMobile,
        setOpenMobile,
        toggleSidebar,
      }),
      [state, open, setOpen, isMobile, openMobile, setOpenMobile, toggleSidebar]
    );

    return (
      <SidebarContext.Provider value={contextValue}>
        <TooltipProvider delayDuration={0}>
          <div
            style={
              {
                '--sidebar-width': SIDEBAR_WIDTH,
                '--sidebar-width-icon': SIDEBAR_WIDTH_ICON,
                ...style,
              } as React.CSSProperties
            }
            className={cn(
              'group/sidebar-wrapper flex min-h-svh w-full has-[[data-variant=inset]]:bg-sidebar',
              className
            )}
            ref={ref}
            {...props}>
            {children}
          </div>
        </TooltipProvider>
      </SidebarContext.Provider>
    );
  }
);
SidebarProvider.displayName = 'SidebarProvider';

const Sidebar = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<'div'> & {
    side?: 'left' | 'right';
    variant?: 'sidebar' | 'floating' | 'inset';
    collapsible?: 'offcanvas' | 'icon' | 'none';
  }
>(
  (
    {
      side = 'left',
      variant = 'sidebar',
      collapsible = 'offcanvas',
      className,
      children,
      ...props
    },
    ref
  ) => {
    const { isMobile, state, openMobile, setOpenMobile } = useSidebar();

    if (collapsible === 'none') {
      return (
        <div
          className={cn(
            'flex h-full w-[--sidebar-width] flex-col bg-sidebar text-sidebar-foreground',
            className
          )}
          ref={ref}
          {...props}>
          {children}
        </div>
      );
    }

    if (isMobile) {
      return (
        <Sheet open={openMobile} onOpenChange={setOpenMobile} {...props}>
          <SheetContent
            data-sidebar="sidebar"
            data-mobile="true"
            className="w-[--sidebar-width] bg-sidebar p-0 text-sidebar-foreground [&>button]:hidden"
            style={
              {
                '--sidebar-width': SIDEBAR_WIDTH_MOBILE,
              } as React.CSSProperties
            }
            side={side}>
            <div className="flex h-full w-full flex-col">{children}</div>
          </SheetContent>
        </Sheet>
      );
    }

    return (
      <div
        ref={ref}
        className="group peer hidden text-sidebar-foreground md:block"
        data-state={state}
        data-collapsible={state === 'collapsed' ? collapsible : ''}
        data-variant={variant}
        data-side={side}>
        {/* This is what handles the sidebar gap on desktop */}
        <div
          className={cn(
            'relative h-svh w-[--sidebar-width] bg-transparent transition-[width] duration-200 ease-linear',
            'group-data-[collapsible=offcanvas]:w-0',
            'group-data-[side=right]:rotate-180',
            variant === 'floating' || variant === 'inset'
              ? 'group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4))]'
              : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon]'
          )}
        />
        <div
          className={cn(
            'fixed inset-y-0 z-10 hidden h-svh w-[--sidebar-width] transition-[left,right,width] duration-200 ease-linear md:flex',
            side === 'left'
              ? 'left-0 group-data-[collapsible=offcanvas]:left-[calc(var(--sidebar-width)*-1)]'
              : 'right-0 group-data-[collapsible=offcanvas]:right-[calc(var(--sidebar-width)*-1)]',
            // Adjust the padding for floating and inset variants.
            variant === 'floating' || variant === 'inset'
              ? 'p-2 group-data-[collapsible=icon]:w-[calc(var(--sidebar-width-icon)_+_theme(spacing.4)_+2px)]'
              : 'group-data-[collapsible=icon]:w-[--sidebar-width-icon] group-data-[side=left]:border-r group-data-[side=right]:border-l',
            className
          )}
          {...props}>
          <div
            data-sidebar="sidebar"
            className="flex h-full w-full flex-col bg-sidebar group-data-[variant=floating]:rounded-lg group-data-[variant=floating]:border group-data-[variant=floating]:border-sidebar-border group-data-[variant=floating]:shadow">
            {children}
          </div>
        </div>
      </div>
    );
  }
);
Sidebar.displayName = 'Sidebar';

const SidebarTrigger = React.forwardRef<
  React.ElementRef<typeof Button>,
  React.ComponentProps<typeof Button>
>(({ className, onClick, ...props }, ref) => {
  const { toggleSidebar } = useSidebar();

  return (
    <Button
      ref={ref}
      data-sidebar="trigger"
      variant="ghost"
      size="icon"
      className={cn('h-7 w-7', className)}
      onClick={event => {
        onClick?.(event);
        toggleSidebar();
      }}
      {...props}>
      <PanelLeft />
      <span className="sr-only">Toggle Sidebar</span>
    </Button>
  );
});
SidebarTrigger.displayName = 'SidebarTrigger';

const SidebarRail = React.forwardRef<HTMLButtonElement, React.ComponentProps<'button'>>(
  ({ className, ...props }, ref) => {
    const { toggleSidebar } = useSidebar();

    return (
      <button
        ref={ref}
        data-sidebar="rail"
        aria-label="Toggle Sidebar"
        tabIndex={-1}
        onClick={toggleSidebar}
        title="Toggle Sidebar"
        className={cn(
          'absolute inset-y-0 z-20 hidden w-4 -translate-x-1/2 transition-all ease-linear after:absolute after:inset-y-0 after:left-1/2 after:w-[2px] hover:after:bg-sidebar-border group-data-[side=left]:-right-4 group-data-[side=right]:left-0 sm:flex',
          '[[data-side=left]_&]:cursor-w-resize [[data-side=right]_&]:cursor-e-resize',
          '[[data-side=left][data-state=collapsed]_&]:cursor-e-resize [[data-side=right][data-state=collapsed]_&]:cursor-w-resize',
          'group-data-[collapsible=offcanvas]:translate-x-0 group-data-[collapsible=offcanvas]:after:left-full group-data-[collapsible=offcanvas]:hover:bg-sidebar',
          '[[data-side=left][data-collapsible=offcanvas]_&]:-right-2',
          '[[data-side=right][data-collapsible=offcanvas]_&]:-left-2',
          className
        )}
        {...props}
      />
    );
  }
);
SidebarRail.displayName = 'SidebarRail';

const SidebarInset = React.forwardRef<HTMLDivElement, React.ComponentProps<'main'>>(
  ({ className, ...props }, ref) => {
    return (
      <main
        ref={ref}
        className={cn(
          'relative flex min-h-svh flex-1 flex-col bg-background',
          'peer-data-[variant=inset]:min-h-[calc(100svh-theme(spacing.4))] md:peer-data-[variant=inset]:m-2 md:peer-data-[state=collapsed]:peer-data-[variant=inset]:ml-2 md:peer-data-[variant=inset]:ml-0 md:peer-data-[variant=inset]:rounded-xl md:peer-data-[variant=inset]:shadow',
          className
        )}
        {...props}
      />
    );
  }
);
SidebarInset.displayName = 'SidebarInset';

const SidebarInput = React.forwardRef<
  React.ElementRef<typeof Input>,
  React.ComponentProps<typeof Input>
>(({ className, ...props }, ref) => {
  return (
    <Input
      ref={ref}
      data-sidebar="input"
      className={cn(
        'h-8 w-full bg-background shadow-none focus-visible:ring-2 focus-visible:ring-sidebar-ring',
        className
      )}
      {...props}
    />
  );
});
SidebarInput.displayName = 'SidebarInput';

const SidebarHeader = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => {
    return (
      <div
        ref={ref}
        data-sidebar="header"
        className={cn('flex flex-col gap-2 p-2', className)}
        {...props}
      />
    );
  }
);
SidebarHeader.displayName = 'SidebarHeader';

const SidebarFooter = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => {
    return (
      <div
        ref={ref}
        data-sidebar="footer"
        className={cn('flex flex-col gap-2 p-2', className)}
        {...props}
      />
    );
  }
);
SidebarFooter.displayName = 'SidebarFooter';

const SidebarSeparator = React.forwardRef<
  React.ElementRef<typeof Separator>,
  React.ComponentProps<typeof Separator>
>(({ className, ...props }, ref) => {
  return (
    <Separator
      ref={ref}
      data-sidebar="separator"
      className={cn('mx-2 w-auto bg-sidebar-border', className)}
      {...props}
    />
  );
});
SidebarSeparator.displayName = 'SidebarSeparator';

const SidebarContent = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => {
    return (
      <div
        ref={ref}
        data-sidebar="content"
        className={cn(
          'flex min-h-0 flex-1 flex-col gap-2 overflow-auto group-data-[collapsible=icon]:overflow-hidden',
          className
        )}
        {...props}
      />
    );
  }
);
SidebarContent.displayName = 'SidebarContent';

const SidebarGroup = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => {
    return (
      <div
        ref={ref}
        data-sidebar="group"
        className={cn('relative flex w-full min-w-0 flex-col p-2', className)}
        {...props}
      />
    );
  }
);
SidebarGroup.displayName = 'SidebarGroup';

const SidebarGroupLabel = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<'div'> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
  const Comp = asChild ? Slot : 'div';

  return (
    <Comp
      ref={ref}
      data-sidebar="group-label"
      className={cn(
        'flex h-8 shrink-0 items-center rounded-md px-2 text-xs font-medium text-sidebar-foreground/70 outline-none ring-sidebar-ring transition-[margin,opa] duration-200 ease-linear focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
        'group-data-[collapsible=icon]:-mt-8 group-data-[collapsible=icon]:opacity-0',
        className
      )}
      {...props}
    />
  );
});
SidebarGroupLabel.displayName = 'SidebarGroupLabel';

const SidebarGroupAction = React.forwardRef<
  HTMLButtonElement,
  React.ComponentProps<'button'> & { asChild?: boolean }
>(({ className, asChild = false, ...props }, ref) => {
  const Comp = asChild ? Slot : 'button';

  return (
    <Comp
      ref={ref}
      data-sidebar="group-action"
      className={cn(
        'absolute right-3 top-3.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 [&>svg]:size-4 [&>svg]:shrink-0',
        // Increases the hit area of the button on mobile.
        'after:absolute after:-inset-2 after:md:hidden',
        'group-data-[collapsible=icon]:hidden',
        className
      )}
      {...props}
    />
  );
});
SidebarGroupAction.displayName = 'SidebarGroupAction';

const SidebarGroupContent = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      data-sidebar="group-content"
      className={cn('w-full text-sm', className)}
      {...props}
    />
  )
);
SidebarGroupContent.displayName = 'SidebarGroupContent';

const SidebarMenu = React.forwardRef<HTMLUListElement, React.ComponentProps<'ul'>>(
  ({ className, ...props }, ref) => (
    <ul
      ref={ref}
      data-sidebar="menu"
      className={cn('flex w-full min-w-0 flex-col gap-1', className)}
      {...props}
    />
  )
);
SidebarMenu.displayName = 'SidebarMenu';

const SidebarMenuItem = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(
  ({ className, ...props }, ref) => (
    <li
      ref={ref}
      data-sidebar="menu-item"
      className={cn('group/menu-item relative', className)}
      {...props}
    />
  )
);
SidebarMenuItem.displayName = 'SidebarMenuItem';

const sidebarMenuButtonVariants = cva(
  'peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-none ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-[[data-sidebar=menu-action]]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:!size-8 group-data-[collapsible=icon]:!p-2 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0',
  {
    variants: {
      variant: {
        default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
        outline:
          'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
      },
      size: {
        default: 'h-8 text-sm',
        sm: 'h-7 text-xs',
        lg: 'h-12 text-sm group-data-[collapsible=icon]:!p-0',
      },
    },
    defaultVariants: {
      variant: 'default',
      size: 'default',
    },
  }
);

const SidebarMenuButton = React.forwardRef<
  HTMLButtonElement,
  React.ComponentProps<'button'> & {
    asChild?: boolean;
    isActive?: boolean;
    tooltip?: string | React.ComponentProps<typeof TooltipContent>;
  } & VariantProps<typeof sidebarMenuButtonVariants>
>(
  (
    {
      asChild = false,
      isActive = false,
      variant = 'default',
      size = 'default',
      tooltip,
      className,
      ...props
    },
    ref
  ) => {
    const Comp = asChild ? Slot : 'button';
    const { isMobile, state } = useSidebar();

    const button = (
      <Comp
        ref={ref}
        data-sidebar="menu-button"
        data-size={size}
        data-active={isActive}
        className={cn(sidebarMenuButtonVariants({ variant, size }), className)}
        {...props}
      />
    );

    if (!tooltip) {
      return button;
    }

    if (typeof tooltip === 'string') {
      tooltip = {
        children: tooltip,
      };
    }

    return (
      <Tooltip>
        <TooltipTrigger asChild>{button}</TooltipTrigger>
        <TooltipContent
          side="right"
          align="center"
          hidden={state !== 'collapsed' || isMobile}
          {...tooltip}
        />
      </Tooltip>
    );
  }
);
SidebarMenuButton.displayName = 'SidebarMenuButton';

const SidebarMenuAction = React.forwardRef<
  HTMLButtonElement,
  React.ComponentProps<'button'> & {
    asChild?: boolean;
    showOnHover?: boolean;
  }
>(({ className, asChild = false, showOnHover = false, ...props }, ref) => {
  const Comp = asChild ? Slot : 'button';

  return (
    <Comp
      ref={ref}
      data-sidebar="menu-action"
      className={cn(
        'absolute right-1 top-1.5 flex aspect-square w-5 items-center justify-center rounded-md p-0 text-sidebar-foreground outline-none ring-sidebar-ring transition-transform hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 peer-hover/menu-button:text-sidebar-accent-foreground [&>svg]:size-4 [&>svg]:shrink-0',
        // Increases the hit area of the button on mobile.
        'after:absolute after:-inset-2 after:md:hidden',
        'peer-data-[size=sm]/menu-button:top-1',
        'peer-data-[size=default]/menu-button:top-1.5',
        'peer-data-[size=lg]/menu-button:top-2.5',
        'group-data-[collapsible=icon]:hidden',
        showOnHover &&
          'group-focus-within/menu-item:opacity-100 group-hover/menu-item:opacity-100 data-[state=open]:opacity-100 peer-data-[active=true]/menu-button:text-sidebar-accent-foreground md:opacity-0',
        className
      )}
      {...props}
    />
  );
});
SidebarMenuAction.displayName = 'SidebarMenuAction';

const SidebarMenuBadge = React.forwardRef<HTMLDivElement, React.ComponentProps<'div'>>(
  ({ className, ...props }, ref) => (
    <div
      ref={ref}
      data-sidebar="menu-badge"
      className={cn(
        'pointer-events-none absolute right-1 flex h-5 min-w-5 select-none items-center justify-center rounded-md px-1 text-xs font-medium tabular-nums text-sidebar-foreground',
        'peer-hover/menu-button:text-sidebar-accent-foreground peer-data-[active=true]/menu-button:text-sidebar-accent-foreground',
        'peer-data-[size=sm]/menu-button:top-1',
        'peer-data-[size=default]/menu-button:top-1.5',
        'peer-data-[size=lg]/menu-button:top-2.5',
        'group-data-[collapsible=icon]:hidden',
        className
      )}
      {...props}
    />
  )
);
SidebarMenuBadge.displayName = 'SidebarMenuBadge';

const SidebarMenuSkeleton = React.forwardRef<
  HTMLDivElement,
  React.ComponentProps<'div'> & {
    showIcon?: boolean;
  }
>(({ className, showIcon = false, ...props }, ref) => {
  // Random width between 50 to 90%.
  const width = React.useMemo(() => {
    return `${Math.floor(Math.random() * 40) + 50}%`;
  }, []);

  return (
    <div
      ref={ref}
      data-sidebar="menu-skeleton"
      className={cn('flex h-8 items-center gap-2 rounded-md px-2', className)}
      {...props}>
      {showIcon && <Skeleton className="size-4 rounded-md" data-sidebar="menu-skeleton-icon" />}
      <Skeleton
        className="h-4 max-w-[--skeleton-width] flex-1"
        data-sidebar="menu-skeleton-text"
        style={
          {
            '--skeleton-width': width,
          } as React.CSSProperties
        }
      />
    </div>
  );
});
SidebarMenuSkeleton.displayName = 'SidebarMenuSkeleton';

const SidebarMenuSub = React.forwardRef<HTMLUListElement, React.ComponentProps<'ul'>>(
  ({ className, ...props }, ref) => (
    <ul
      ref={ref}
      data-sidebar="menu-sub"
      className={cn(
        'mx-3.5 flex min-w-0 translate-x-px flex-col gap-1 border-l border-sidebar-border px-2.5 py-0.5',
        'group-data-[collapsible=icon]:hidden',
        className
      )}
      {...props}
    />
  )
);
SidebarMenuSub.displayName = 'SidebarMenuSub';

const SidebarMenuSubItem = React.forwardRef<HTMLLIElement, React.ComponentProps<'li'>>(
  ({ ...props }, ref) => <li ref={ref} {...props} />
);
SidebarMenuSubItem.displayName = 'SidebarMenuSubItem';

const SidebarMenuSubButton = React.forwardRef<
  HTMLAnchorElement,
  React.ComponentProps<'a'> & {
    asChild?: boolean;
    size?: 'sm' | 'md';
    isActive?: boolean;
  }
>(({ asChild = false, size = 'md', isActive, className, ...props }, ref) => {
  const Comp = asChild ? Slot : 'a';

  return (
    <Comp
      ref={ref}
      data-sidebar="menu-sub-button"
      data-size={size}
      data-active={isActive}
      className={cn(
        'flex h-7 min-w-0 -translate-x-px items-center gap-2 overflow-hidden rounded-md px-2 text-sidebar-foreground outline-none ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0 [&>svg]:text-sidebar-accent-foreground',
        'data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground',
        size === 'sm' && 'text-xs',
        size === 'md' && 'text-sm',
        'group-data-[collapsible=icon]:hidden',
        className
      )}
      {...props}
    />
  );
});
SidebarMenuSubButton.displayName = 'SidebarMenuSubButton';

export {
  Sidebar,
  SidebarContent,
  SidebarFooter,
  SidebarGroup,
  SidebarGroupAction,
  SidebarGroupContent,
  SidebarGroupLabel,
  SidebarHeader,
  SidebarInput,
  SidebarInset,
  SidebarMenu,
  SidebarMenuAction,
  SidebarMenuBadge,
  SidebarMenuButton,
  SidebarMenuItem,
  SidebarMenuSkeleton,
  SidebarMenuSub,
  SidebarMenuSubButton,
  SidebarMenuSubItem,
  SidebarProvider,
  SidebarRail,
  SidebarSeparator,
  SidebarTrigger,
  useSidebar,
};


================================================
FILE: apps/dashboard/src/components/ui/skeleton.tsx
================================================
import { cn } from '@/lib/utils';

function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
  return <div className={cn('animate-pulse rounded-md bg-primary/10', className)} {...props} />;
}

export { Skeleton };


================================================
FILE: apps/dashboard/src/components/ui/table.tsx
================================================
import * as React from 'react';

import { cn } from '@/lib/utils';

const Table = React.forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTableElement>>(
  ({ className, ...props }, ref) => (
    <div className="relative w-full overflow-auto">
      <table ref={ref} className={cn('w-full caption-bottom text-sm', className)} {...props} />
    </div>
  )
);
Table.displayName = 'Table';

const TableHeader = React.forwardRef<
  HTMLTableSectionElement,
  React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
  <thead ref={ref} className={cn('[&_tr]:border-b', className)} {...props} />
));
TableHeader.displayName = 'TableHeader';

const TableBody = React.forwardRef<
  HTMLTableSectionElement,
  React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
  <tbody ref={ref} className={cn('[&_tr:last-child]:border-0', className)} {...props} />
));
TableBody.displayName = 'TableBody';

const TableFooter = React.forwardRef<
  HTMLTableSectionElement,
  React.HTMLAttributes<HTMLTableSectionElement>
>(({ className, ...props }, ref) => (
  <tfoot
    ref={ref}
    className={cn('border-t bg-muted/50 font-medium [&>tr]:last:border-b-0', className)}
    {...props}
  />
));
TableFooter.displayName = 'TableFooter';

const TableRow = React.forwardRef<HTMLTableRowElement, React.HTMLAttributes<HTMLTableRowElement>>(
  ({ className, ...props }, ref) => (
    <tr
      ref={ref}
      className={cn(
        'border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted',
        className
      )}
      {...props}
    />
  )
);
TableRow.displayName = 'TableRow';

const TableHead = React.forwardRef<
  HTMLTableCellElement,
  React.ThHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
  <th
    ref={ref}
    className={cn(
      'h-10 px-2 text-left align-middle font-medium text-muted-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
      className
    )}
    {...props}
  />
));
TableHead.displayName = 'TableHead';

const TableCell = React.forwardRef<
  HTMLTableCellElement,
  React.TdHTMLAttributes<HTMLTableCellElement>
>(({ className, ...props }, ref) => (
  <td
    ref={ref}
    className={cn(
      'p-2 align-middle [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',
      className
    )}
    {...props}
  />
));
TableCell.displayName = 'TableCell';

const TableCaption = React.forwardRef<
  HTMLTableCaptionElement,
  React.HTMLAttributes<HTMLTableCaptionElement>
>(({ className, ...props }, ref) => (
  <caption ref={ref} className={cn('mt-4 text-sm text-muted-foreground', className)} {...props} />
));
TableCaption.displayName = 'TableCaption';

export { Table, TableHeader, TableBody, TableFooter, TableHead, TableRow, TableCell, TableCaption };


================================================
FILE: apps/dashboard/src/components/ui/toast.tsx
================================================
import * as React from 'react';
import * as ToastPrimitives from '@radix-ui/react-toast';
import { cva, type VariantProps } from 'class-variance-authority';
import { X } from 'lucide-react';

import { cn } from '@/lib/utils';

const ToastProvider = ToastPrimitives.Provider;

const ToastViewport = React.forwardRef<
  React.ElementRef<typeof ToastPrimitives.Viewport>,
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Viewport>
>(({ className, ...props }, ref) => (
  <ToastPrimitives.Viewport
    ref={ref}
    className={cn(
      'fixed top-0 z-[100] flex max-h-screen w-full flex-col-reverse p-4 sm:bottom-0 sm:right-0 sm:top-auto sm:flex-col md:max-w-[420px]',
      className
    )}
    {...props}
  />
));
ToastViewport.displayName = ToastPrimitives.Viewport.displayName;

const toastVariants = cva(
  'group pointer-events-auto relative flex w-full items-center justify-between space-x-2 overflow-hidden rounded-md border p-4 pr-6 shadow-lg transition-all data-[swipe=cancel]:translate-x-0 data-[swipe=end]:translate-x-[var(--radix-toast-swipe-end-x)] data-[swipe=move]:translate-x-[var(--radix-toast-swipe-move-x)] data-[swipe=move]:transition-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[swipe=end]:animate-out data-[state=closed]:fade-out-80 data-[state=closed]:slide-out-to-right-full data-[state=open]:slide-in-from-top-full data-[state=open]:sm:slide-in-from-bottom-full',
  {
    variants: {
      variant: {
        default: 'border bg-background text-foreground',
        destructive:
          'destructive group border-destructive bg-destructive text-destructive-foreground',
      },
    },
    defaultVariants: {
      variant: 'default',
    },
  }
);

const Toast = React.forwardRef<
  React.ElementRef<typeof ToastPrimitives.Root>,
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Root> & VariantProps<typeof toastVariants>
>(({ className, variant, ...props }, ref) => {
  return (
    <ToastPrimitives.Root
      ref={ref}
      className={cn(toastVariants({ variant }), className)}
      {...props}
    />
  );
});
Toast.displayName = ToastPrimitives.Root.displayName;

const ToastAction = React.forwardRef<
  React.ElementRef<typeof ToastPrimitives.Action>,
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Action>
>(({ className, ...props }, ref) => (
  <ToastPrimitives.Action
    ref={ref}
    className={cn(
      'inline-flex h-8 shrink-0 items-center justify-center rounded-md border bg-transparent px-3 text-sm font-medium transition-colors hover:bg-secondary focus:outline-none focus:ring-1 focus:ring-ring disabled:pointer-events-none disabled:opacity-50 group-[.destructive]:border-muted/40 group-[.destructive]:hover:border-destructive/30 group-[.destructive]:hover:bg-destructive group-[.destructive]:hover:text-destructive-foreground group-[.destructive]:focus:ring-destructive',
      className
    )}
    {...props}
  />
));
ToastAction.displayName = ToastPrimitives.Action.displayName;

const ToastClose = React.forwardRef<
  React.ElementRef<typeof ToastPrimitives.Close>,
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Close>
>(({ className, ...props }, ref) => (
  <ToastPrimitives.Close
    ref={ref}
    className={cn(
      'absolute right-1 top-1 rounded-md p-1 text-foreground/50 opacity-0 transition-opacity hover:text-foreground focus:opacity-100 focus:outline-none focus:ring-1 group-hover:opacity-100 group-[.destructive]:text-red-300 group-[.destructive]:hover:text-red-50 group-[.destructive]:focus:ring-red-400 group-[.destructive]:focus:ring-offset-red-600',
      className
    )}
    toast-close=""
    {...props}>
    <X className="h-4 w-4" />
  </ToastPrimitives.Close>
));
ToastClose.displayName = ToastPrimitives.Close.displayName;

const ToastTitle = React.forwardRef<
  React.ElementRef<typeof ToastPrimitives.Title>,
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Title>
>(({ className, ...props }, ref) => (
  <ToastPrimitives.Title
    ref={ref}
    className={cn('text-sm font-semibold [&+div]:text-xs', className)}
    {...props}
  />
));
ToastTitle.displayName = ToastPrimitives.Title.displayName;

const ToastDescription = React.forwardRef<
  React.ElementRef<typeof ToastPrimitives.Description>,
  React.ComponentPropsWithoutRef<typeof ToastPrimitives.Description>
>(({ className, ...props }, ref) => (
  <ToastPrimitives.Description
    ref={ref}
    className={cn('text-sm opacity-90', className)}
    {...props}
  />
));
ToastDescription.displayName = ToastPrimitives.Description.displayName;

type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;

type ToastActionElement = React.ReactElement<typeof ToastAction>;

export {
  type ToastProps,
  type ToastActionElement,
  ToastProvider,
  ToastViewport,
  Toast,
  ToastTitle,
  ToastDescription,
  ToastClose,
  ToastAction,
};


================================================
FILE: apps/dashboard/src/components/ui/toaster.tsx
================================================
import { useToast } from '@/hooks/use-toast';
import {
  Toast,
  ToastClose,
  ToastDescription,
  ToastProvider,
  ToastTitle,
  ToastViewport,
} from '@/components/ui/toast';

export function Toaster() {
  const { toasts } = useToast();

  return (
    <ToastProvider>
      {toasts.map(function ({ id, title, description, action, ...props }) {
        return (
          <Toast key={id} {...props}>
            <div className="grid gap-1">
              {title && <ToastTitle>{title}</ToastTitle>}
              {description && <ToastDescription>{description}</ToastDescription>}
            </div>
            {action}
            <ToastClose />
          </Toast>
        );
      })}
      <ToastViewport />
    </ToastProvider>
  );
}


================================================
FILE: apps/dashboard/src/components/ui/tooltip.tsx
================================================
import * as React from 'react';
import * as TooltipPrimitive from '@radix-ui/react-tooltip';

import { cn } from '@/lib/utils';

const TooltipProvider = TooltipPrimitive.Provider;

const Tooltip = TooltipPrimitive.Root;

const TooltipTrigger = TooltipPrimitive.Trigger;

const TooltipContent = React.forwardRef<
  React.ElementRef<typeof TooltipPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
  <TooltipPrimitive.Portal>
    <TooltipPrimitive.Content
      ref={ref}
      sideOffset={sideOffset}
      className={cn(
        'z-50 overflow-hidden rounded-md bg-primary px-3 py-1.5 text-xs text-primary-foreground animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
        className
      )}
      {...props}
    />
  </TooltipPrimitive.Portal>
));
TooltipContent.displayName = TooltipPrimitive.Content.displayName;

export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };


================================================
FILE: apps/dashboard/src/containers/Layout/index.tsx
================================================
import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';
import { AppSidebar } from '@/components/app-sidebar';

export const Layout = ({ children }: { children: React.ReactNode }) => {
  return (
    <SidebarProvider>
      <AppSidebar />
      <main className="w-full">
        <SidebarTrigger />
        <div className="flex-1 p-4 max-w-screen-2xl m-auto">{children}</div>
      </main>
    </SidebarProvider>
  );
};


================================================
FILE: apps/dashboard/src/hooks/use-mobile.tsx
================================================
import * as React from "react"

const MOBILE_BREAKPOINT = 768

export function useIsMobile() {
  const [isMobile, setIsMobile] = React.useState<boolean | undefined>(undefined)

  React.useEffect(() => {
    const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`)
    const onChange = () => {
      setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
    }
    mql.addEventListener("change", onChange)
    setIsMobile(window.innerWidth < MOBILE_BREAKPOINT)
    return () => mql.removeEventListener("change", onChange)
  }, [])

  return !!isMobile
}


================================================
FILE: apps/dashboard/src/hooks/use-toast.ts
================================================
'use client';

// Inspired by react-hot-toast library
import * as React from 'react';

import type { ToastActionElement, ToastProps } from '@/components/ui/toast';

const TOAST_LIMIT = 1;
const TOAST_REMOVE_DELAY = 1000000;

type ToasterToast = ToastProps & {
  id: string;
  title?: React.ReactNode;
  description?: React.ReactNode;
  action?: ToastActionElement;
};

// eslint-disable-next-line
const actionTypes = {
  ADD_TOAST: 'ADD_TOAST',
  UPDATE_TOAST: 'UPDATE_TOAST',
  DISMISS_TOAST: 'DISMISS_TOAST',
  REMOVE_TOAST: 'REMOVE_TOAST',
} as const;

let count = 0;

function genId() {
  count = (count + 1) % Number.MAX_SAFE_INTEGER;
  return count.toString();
}

type ActionType = typeof actionTypes;

type Action =
  | {
      type: ActionType['ADD_TOAST'];
      toast: ToasterToast;
    }
  | {
      type: ActionType['UPDATE_TOAST'];
      toast: Partial<ToasterToast>;
    }
  | {
      type: ActionType['DISMISS_TOAST'];
      toastId?: ToasterToast['id'];
    }
  | {
      type: ActionType['REMOVE_TOAST'];
      toastId?: ToasterToast['id'];
    };

interface State {
  toasts: ToasterToast[];
}

const toastTimeouts = new Map<string, ReturnType<typeof setTimeout>>();

const addToRemoveQueue = (toastId: string) => {
  if (toastTimeouts.has(toastId)) {
    return;
  }

  const timeout = setTimeout(() => {
    toastTimeouts.delete(toastId);
    dispatch({
      type: 'REMOVE_TOAST',
      toastId: toastId,
    });
  }, TOAST_REMOVE_DELAY);

  toastTimeouts.set(toastId, timeout);
};

export const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'ADD_TOAST':
      return {
        ...state,
        toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT),
      };

    case 'UPDATE_TOAST':
      return {
        ...state,
        toasts: state.toasts.map(t => (t.id === action.toast.id ? { ...t, ...action.toast } : t)),
      };

    case 'DISMISS_TOAST': {
      const { toastId } = action;

      // ! Side effects ! - This could be extracted into a dismissToast() action,
      // but I'll keep it here for simplicity
      if (toastId) {
        addToRemoveQueue(toastId);
      } else {
        state.toasts.forEach(toast => {
          addToRemoveQueue(toast.id);
        });
      }

      return {
        ...state,
        toasts: state.toasts.map(t =>
          t.id === toastId || toastId === undefined
            ? {
                ...t,
                open: false,
              }
            : t
        ),
      };
    }
    case 'REMOVE_TOAST':
      if (action.toastId === undefined) {
        return {
          ...state,
          toasts: [],
        };
      }
      return {
        ...state,
        toasts: state.toasts.filter(t => t.id !== action.toastId),
      };
  }
};

const listeners: Array<(state: State) => void> = [];

let memoryState: State = { toasts: [] };

function dispatch(action: Action) {
  memoryState = reducer(memoryState, action);
  listeners.forEach(listener => {
    listener(memoryState);
  });
}

type Toast = Omit<ToasterToast, 'id'>;

function toast({ ...props }: Toast) {
  const id = genId();

  const update = (props: ToasterToast) =>
    dispatch({
      type: 'UPDATE_TOAST',
      toast: { ...props, id },
    });
  const dismiss = () => dispatch({ type: 'DISMISS_TOAST', toastId: id });

  dispatch({
    type: 'ADD_TOAST',
    toast: {
      ...props,
      id,
      open: true,
      onOpenChange: open => {
        if (!open) dismiss();
      },
    },
  });

  return {
    id: id,
    dismiss,
    update,
  };
}

function useToast() {
  const [state, setState] = React.useState<State>(memoryState);

  React.useEffect(() => {
    listeners.push(setState);
    return () => {
      const index = listeners.indexOf(setState);
      if (index > -1) {
        listeners.splice(index, 1);
      }
    };
  }, [state]);

  return {
    ...state,
    toast,
    dismiss: (toastId?: string) => dispatch({ type: 'DISMISS_TOAST', toastId }),
  };
}

export { useToast, toast };


================================================
FILE: apps/dashboard/src/index.css
================================================
@tailwind base;
@tailwind components;
@tailwind utilities;

@layer base {
  :root {
    --radius: 0.5rem;
    --background: 0 0% 100%;
    --foreground: 240 10% 3.9%;
    --card: 0 0% 100%;
    --card-foreground: 240 10% 3.9%;
    --popover: 0 0% 100%;
    --popover-foreground: 240 10% 3.9%;
    --primary: 240 5.9% 10%;
    --primary-foreground: 0 0% 98%;
    --secondary: 240 4.8% 95.9%;
    --secondary-foreground: 240 5.9% 10%;
    --muted: 240 4.8% 95.9%;
    --muted-foreground: 240 3.8% 46.1%;
    --accent: 240 4.8% 95.9%;
    --accent-foreground: 240 5.9% 10%;
    --destructive: 0 84.2% 60.2%;
    --destructive-foreground: 0 0% 98%;
    --border: 240 5.9% 90%;
    --input: 240 5.9% 90%;
    --ring: 240 10% 3.9%;
    --chart-1: 12 76% 61%;
    --chart-2: 173 58% 39%;
    --chart-3: 197 37% 24%;
    --chart-4: 43 74% 66%;
    --chart-5: 27 87% 67%;
    --sidebar-background: 0 0% 98%;
    --sidebar-foreground: 240 5.3% 26.1%;
    --sidebar-primary: 240 5.9% 10%;
    --sidebar-primary-foreground: 0 0% 98%;
    --sidebar-accent: 240 4.8% 95.9%;
    --sidebar-accent-foreground: 240 5.9% 10%;
    --sidebar-border: 220 13% 91%;
    --sidebar-ring: 217.2 91.2% 59.8%}
  .dark {
    --background: 240 10% 3.9%;
    --foreground: 0 0% 98%;
    --card: 240 10% 3.9%;
    --card-foreground: 0 0% 98%;
    --popover: 240 10% 3.9%;
    --popover-foreground: 0 0% 98%;
    --primary: 0 0% 98%;
    --primary-foreground: 240 5.9% 10%;
    --secondary: 240 3.7% 15.9%;
    --secondary-foreground: 0 0% 98%;
    --muted: 240 3.7% 15.9%;
    --muted-foreground: 240 5% 64.9%;
    --accent: 240 3.7% 15.9%;
    --accent-foreground: 0 0% 98%;
    --destructive: 0 62.8% 30.6%;
    --destructive-foreground: 0 0% 98%;
    --border: 240 3.7% 15.9%;
    --input: 240 3.7% 15.9%;
    --ring: 240 4.9% 83.9%;
    --chart-1: 220 70% 50%;
    --chart-2: 160 60% 45%;
    --chart-3: 30 80% 55%;
    --chart-4: 280 65% 60%;
    --chart-5: 340 75% 55%
  ;
    --sidebar-background: 240 5.9% 10%;
    --sidebar-foreground: 240 4.8% 95.9%;
    --sidebar-primary: 224.3 76.3% 48%;
    --sidebar-primary-foreground: 0 0% 100%;
    --sidebar-accent: 240 3.7% 15.9%;
    --sidebar-accent-foreground: 240 4.8% 95.9%;
    --sidebar-border: 240 3.7% 15.9%;
    --sidebar-ring: 217.2 91.2% 59.8%}
}
@layer base {
  * {
    @apply border-border;
  }
  body {
    @apply bg-background text-foreground;
  }
}


================================================
FILE: apps/dashboard/src/lib/api.ts
================================================
import { getRefreshToken, getToken, logout, setTokens } from '@/lib/auth.ts';

export class ApiClient {
  private baseUrl: string;

  constructor() {
    // @ts-ignore using window.env for vite
    this.baseUrl = window?.env?.VITE_OTA_API_URL || import.meta.env.VITE_OTA_API_URL;
    if (!this.baseUrl) {
      throw new Error('Missing VITE_OTA_API_URL environment variable');
    }
  }

  private populateHeaders(headers: Headers) {
    const token = getToken();
    if (token) {
      headers.set('Authorization', `Bearer ${token}`);
    }
  }
  private async request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
    const url = `${this.baseUrl}${endpoint}`;
    const headers = new Headers(options.headers);
    this.populateHeaders(headers);

    const response = await fetch(url, { ...options, headers });
    const refreshToken = getRefreshToken();
    if (response.status === 401 && refreshToken) {
      await this.refreshTokens(refreshToken);
      return this.request<T>(endpoint, options);
    }

    if (!response.ok) {
      throw new Error(`HTTP error! Status: ${response.status}`);
    }

    return response.json() as Promise<T>;
  }

  private async refreshTokens(refreshToken: string) {
    try {
      const form = new URLSearchParams();
      form.append('refreshToken', refreshToken);
      const response = await fetch(`${this.baseUrl}/auth/refreshToken`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
        body: form.toString(),
      });

      if (!response.ok) {
        throw new Error('Failed to refresh token');
      }

      const data = await response.json();
      setTokens(data.token, data.refreshToken);

      localStorage.setItem('accessToken', data.token);
      localStorage.setItem('refreshToken', data.refreshToken);
    } catch (error) {
      console.error('Failed to refresh token:', error);
      logout();
    }
  }

  public async login(password: string) {
    const form = new URLSearchParams();
    form.append('password', password);
    return this.request<{ token: string; refreshToken: string }>(`/auth/login`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
      body: form.toString(),
    });
  }
  public async updateChannelBranchMapping(
    branchName: string,
    payload: {
      releaseChannel: string;
    }
  ) {
    return this.request(`/api/branch/${branchName}/updateChannelBranchMapping`, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload),
    });
  }
  public async getChannels() {
    return this.request<
      {
        releaseChannelId: string;
        releaseChannelName: string;
        branchName?: string | null;
        branchId?: string | null;
      }[]
    >('/api/channels', {
      method: 'GET',
    });
  }
  public async getBranches() {
    return this.request<
      {
        branchName: string;
        branchId: string;
        releaseChannel?: string | null;
      }[]
    >('/api/branches', {
      method: 'GET',
    });
  }
  public async getRuntimeVersions(branch: string) {
    return this.request<
      {
        runtimeVersion: string;
        lastUpdatedAt: string;
        createdAt: string;
        numberOfUpdates: number;
      }[]
    >(`/api/branch/${branch}/runtimeVersions`, {
      method: 'GET',
    });
  }
  public async getUpdates(branch: string, runtimeVersion: string) {
    return this.request<
      {
        updateUUID: string;
        createdAt: string;
        updateId: string;
        platform: string;
        commitHash: string;
        message?: string;
      }[]
    >(`/api/branch/${branch}/runtimeVersion/${runtimeVersion}/updates`, {
      method: 'GET',
    });
  }
  public async getUpdateDetails(branch: string, runtimeVersion: string, updateId: string) {
    return this.request<{
      updateUUID: string;
      createdAt: string;
      updateId: string;
      platform: string;
      commitHash: string;
      message?: string;
      type: number;
      expoConfig: string;
    }>(`/api/branch/${branch}/runtimeVersion/${runtimeVersion}/updates/${updateId}`, {
      method: 'GET',
    });
  }
  public async getSettings() {
    return this.request<{
      BASE_URL: string;
      EXPO_APP_ID: string;
      EXPO_ACCESS_TOKEN: string;
      CACHE_MODE: string;
      REDIS_HOST: string;
      REDIS_PORT: string;
      STORAGE_MODE: string;
      S3_BUCKET_NAME: string;
      LOCAL_BUCKET_BASE_PATH: string;
      KEYS_STORAGE_TYPE: string;
      AWSSM_EXPO_PUBLIC_KEY_SECRET_ID: string;
      AWSSM_EXPO_PRIVATE_KEY_SECRET_ID: string;
      PUBLIC_EXPO_KEY_B64: string;
      PUBLIC_LOCAL_EXPO_KEY_PATH: string;
      PRIVATE_LOCAL_EXPO_KEY_PATH: string;
      AWS_REGION: string;
      AWS_BASE_ENDPOINT: string;
      AWS_S3_FORCE_PATH_STYLE: string;
      AWS_ACCESS_KEY_ID: string;
      S3_CDN_PREFIX: string;
      CLOUDFRONT_DOMAIN: string;
      CLOUDFRONT_KEY_PAIR_ID: string;
      CLOUDFRONT_PRIVATE_KEY_B64: string;
      AWSSM_CLOUDFRONT_PRIVATE_KEY_SECRET_ID: string;
      PRIVATE_LOCAL_CLOUDFRONT_KEY_PATH: string;
      PROMETHEUS_ENABLED: string;
    }>(`/api/settings`, {
      method: 'GET',
    });
  }
}

export const api = new ApiClient();


================================================
FILE: apps/dashboard/src/lib/auth.ts
================================================
export const isAuthenticated = () => {
  return !!localStorage.getItem('token') && !!localStorage.getItem('refreshToken');
};

export const getToken = () => {
  return localStorage.getItem('token');
};

export const getRefreshToken = () => {
  return localStorage.getItem('refreshToken');
};

export const setTokens = (token: string, refreshToken: string) => {
  localStorage.setItem('token', token);
  localStorage.setItem('refreshToken', refreshToken);
};

export const logout = () => {
  localStorage.removeItem('token');
  localStorage.removeItem('refreshToken');
};


================================================
FILE: apps/dashboard/src/lib/utils.ts
================================================
import { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}


================================================
FILE: apps/dashboard/src/main.tsx
================================================
import { BrowserRouter } from 'react-router';
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { App } from '@/App.tsx';
import './index.css';

import { QueryClient, QueryClientProvider } from '@tanstack/react-query';

const queryClient = new QueryClient();

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <QueryClientProvider client={queryClient}>
      <BrowserRouter basename="/dashboard">
        <App />
      </BrowserRouter>
    </QueryClientProvider>
  </StrictMode>
);


================================================
FILE: apps/dashboard/src/pages/Channels/components/SelectBranch/index.tsx
================================================
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api.ts';
import { ApiError } from '@/components/APIError';
import { Combobox } from '@/components/Combobox';

export const SelectBranch = ({
  currentBranch,
  onChange,
  loading,
}: {
  onChange: (branchId?: string | null) => void;
  loading?: boolean;
  currentBranch?: string | null;
}) => {
  const { data, isLoading, error } = useQuery({
    queryKey: [`branches`],
    enabled: true,
    queryFn: () => api.getBranches(),
  });
  const allBranches =
    data
      ?.filter(d => !!d.branchId)
      ?.map(d => {
        return {
          branchName: d.branchName,
          id: d.branchId,
        };
      }) ?? [];
  if (error) {
    return <ApiError error={error} />;
  }
  return (
    <Combobox
      loading={isLoading || loading}
      options={allBranches.map(b => {
        return {
          label: b.branchName,
          value: b.id,
        };
      })}
      value={currentBranch || ''}
      onChange={onChange}
    />
  );
};


================================================
FILE: apps/dashboard/src/pages/Channels/index.tsx
================================================
import { useMutation, useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api.ts';
import { ApiError } from '@/components/APIError';
import { DataTable } from '@/components/DataTable';
import { SelectBranch } from '@/pages/Channels/components/SelectBranch';
import { useCallback, useState } from 'react';
import { useToast } from '@/hooks/use-toast.ts';

export const Channels = () => {
  const { data, isLoading, error, refetch } = useQuery({
    queryKey: [`channels`],
    enabled: true,
    queryFn: () => api.getChannels(),
  });
  const { toast } = useToast();
  const [loading, setLoading] = useState(false);

  const mutation = useMutation({
    mutationKey: ['update-branch'],
    mutationFn: async ({
      branchName,
      releaseChannelId,
    }: {
      branchName: string;
      releaseChannelId: string;
    }) => {
      return api.updateChannelBranchMapping(branchName, {
        releaseChannel: releaseChannelId,
      });
    },
  });

  const onBranchChange = useCallback(
    (channelId: string) => async (branchName?: string | null) => {
      if (!branchName) return;
      setLoading(true);
      try {
        await mutation.mutateAsync({
          branchName,
          releaseChannelId: channelId,
        });
        await refetch();
        toast({
          title: 'Branch updated',
          description: `Branch updated to ${branchName}`,
          duration: 2000,
        });
      } catch (error) {
        toast({
          title: 'Error updating branch',
          description: (error as { message: string }).message,
          variant: 'destructive',
        });
      } finally {
        setLoading(false);
      }
    },
    [mutation, toast]
  );
  return (
    <div className="w-full h-screen flex-1 p-5">
      <h1 className="text-2xl font-medium mb-4">Channels</h1>
      {!!error && <ApiError error={error} />}
      <DataTable
        loading={isLoading}
        columns={[
          {
            header: 'Channel name',
            accessorKey: 'releaseChannelId',
            cell: value => {
              return (
                <span className="flex flex-row gap-2 items-center w-full">
                  {value.row.original.releaseChannelName}
                </span>
              );
            },
          },
          {
            header: 'Branch',
            accessorKey: 'releaseChannelName',
            cell: ({ row }) => {
              console.log(row);
              return (
                <SelectBranch
                  currentBranch={row.original.branchId || ''}
                  loading={isLoading || loading}
                  onChange={onBranchChange(row.original.releaseChannelId)}
                />
              );
            },
          },
        ]}
        data={data ?? []}
      />
    </div>
  );
};


================================================
FILE: apps/dashboard/src/pages/Login/index.tsx
================================================
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card.tsx';
import { Input } from '@/components/ui/input.tsx';
import { Button } from '@/components/ui/button.tsx';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import { zodResolver } from '@hookform/resolvers/zod';
import { Form, FormControl, FormField, FormItem, FormMessage } from '@/components/ui/form.tsx';
import { useCallback } from 'react';
import { setTokens } from '@/lib/auth.ts';
import { useNavigate } from 'react-router';
import { api } from '@/lib/api.ts';

const FormSchema = z.object({
  password: z.string().min(1, {
    message: 'Password is required',
  }),
});

export const Login = () => {
  const form = useForm<z.infer<typeof FormSchema>>({
    resolver: zodResolver(FormSchema),
    defaultValues: {
      password: '',
    },
  });
  const navigate = useNavigate();

  const onSubmit = useCallback(
    async (data: z.infer<typeof FormSchema>) => {
      try {
        const response = await api.login(data.password);
        setTokens(response.token, response.refreshToken);
        navigate('/');
      } catch {
        form.setError('password', {
          type: 'server',
          message: 'Error logging in',
        });
      }
    },
    [form]
  );

  return (
    <div className="flex-1 w-full h-screen flex items-center justify-center">
      <Card className="w-[350px]">
        <CardHeader>
          <CardTitle>Admin password</CardTitle>
        </CardHeader>
        <CardContent>
          <Form {...form}>
            <form onSubmit={form.handleSubmit(onSubmit)} className="w-full gap-5 flex flex-col">
              <FormField
                control={form.control}
                name="password"
                render={({ field, fieldState }) => {
                  return (
                    <FormItem>
                      <FormControl>
                        <Input type={'password'} {...field} />
                      </FormControl>
                      <FormMessage>{fieldState.error?.message}</FormMessage>
                    </FormItem>
                  );
                }}
              />
              <Button type="submit">Submit</Button>
            </form>
          </Form>
        </CardContent>
      </Card>
    </div>
  );
};


================================================
FILE: apps/dashboard/src/pages/Logout/index.tsx
================================================
import { useEffect } from 'react';
import { logout } from '@/lib/auth.ts';
import { useNavigate } from 'react-router';

export const Logout = () => {
  const navigate = useNavigate();

  useEffect(() => {
    logout();
    navigate('/login');
  }, [navigate]);

  return null;
};


================================================
FILE: apps/dashboard/src/pages/Settings/index.tsx
================================================
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api.ts';
import { DataTable } from '@/components/DataTable';
import { ApiError } from '@/components/APIError';

export const Settings = () => {
  const { data, isLoading, error } = useQuery({
    queryKey: ['settings'],
    queryFn: () => api.getSettings(),
  });
  return (
    <div className="w-full h-screen flex-1 p-5">
      <h1 className="text-2xl font-medium mb-4">Settings</h1>
      {!!error && <ApiError error={error} />}
      <DataTable
        columns={[
          {
            header: 'Key',
            accessorKey: 'key',
          },
          {
            header: 'Value',
            accessorKey: 'value',
          },
        ]}
        data={Object.entries(data || {}).map(([key, value]) => ({
          key,
          value,
        }))}
        loading={isLoading}
      />
    </div>
  );
};


================================================
FILE: apps/dashboard/src/pages/Updates/components/BranchesTable/index.tsx
================================================
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api.ts';
import { ApiError } from '@/components/APIError';
import { DataTable } from '@/components/DataTable';
import { Box, GitBranch } from 'lucide-react';
import { useSearchParams } from 'react-router';

export const BranchesTable = () => {
  const [, setSearchParams] = useSearchParams();
  const { data, isLoading, error } = useQuery({
    queryKey: ['branches'],
    queryFn: () => api.getBranches(),
  });

  return (
    <div className="w-full flex-1">
      {!!error && <ApiError error={error} />}
      <DataTable
        loading={isLoading}
        columns={[
          {
            header: 'Branch name',
            accessorKey: 'branchName',
            cell: value => {
              return (
                <button
                  className="flex flex-row gap-2 items-center cursor-pointer w-full"
                  onClick={() => {
                    setSearchParams({ branch: value.row.original.branchName });
                  }}>
                  <GitBranch className="w-4" />
                  <span className="underline">{value.row.original.branchName}</span>
                </button>
              );
            },
          },
          {
            header: 'Release channel',
            size: 10,
            maxSize: 10,
            accessorKey: 'releaseChannel',
            cell: value => {
              const releaseChannel = value.row.original.releaseChannel;
              if (!releaseChannel) return <span>N/A</span>;
              return (
                <div className="flex flex-row gap-2 items-center">
                  <Box className="w-4" />
                  <span>{value.row.original.releaseChannel}</span>
                </div>
              );
            },
          },
        ]}
        data={data ?? []}
      />
    </div>
  );
};


================================================
FILE: apps/dashboard/src/pages/Updates/components/RuntimeVersionsTable/index.tsx
================================================
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api.ts';
import { ApiError } from '@/components/APIError';
import { DataTable } from '@/components/DataTable';
import { GitBranch, Milestone } from 'lucide-react';
import { useSearchParams } from 'react-router';
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { Badge } from '@/components/ui/badge.tsx';

export const RuntimeVersionsTable = ({ branch }: { branch: string }) => {
  const [, setSearchParams] = useSearchParams();
  const { data, isLoading, error } = useQuery({
    queryKey: ['runtimeVersions'],
    queryFn: () => api.getRuntimeVersions(branch),
  });

  return (
    <div className="w-full flex-1">
      <Breadcrumb className="mb-2">
        <BreadcrumbList>
          <BreadcrumbItem>
            <BreadcrumbLink href="/dashboard" className="flex items-center gap-2">
              <GitBranch className="w-4" />
            </BreadcrumbLink>
          </BreadcrumbItem>
          <BreadcrumbSeparator />
          <BreadcrumbItem>
            <BreadcrumbPage>{branch}</BreadcrumbPage>
          </BreadcrumbItem>
        </BreadcrumbList>
      </Breadcrumb>
      {!!error && <ApiError error={error} />}
      <DataTable
        loading={isLoading}
        columns={[
          {
            header: 'Runtime version',
            accessorKey: 'runtimeVersion',
            cell: value => {
              return (
                <button
                  className="flex flex-row gap-2 items-center cursor-pointer w-full underline"
                  onClick={() => {
                    setSearchParams({
                      branch,
                      runtimeVersion: value.row.original.runtimeVersion,
                    });
                  }}>
                  <Milestone className="w-4" />
                  {value.row.original.runtimeVersion}
                </button>
              );
            },
          },
          {
            header: 'Created at',
            accessorKey: 'createdAt',
            cell: ({ row }) => {
              const date = new Date(row.original.createdAt);
              return (
                <Badge variant="outline">
                  {date.toLocaleDateString('en-GB', {
                    year: 'numeric',
                    month: 'long',
                    day: 'numeric',
                    hour: 'numeric',
                    minute: 'numeric',
                    second: 'numeric',
                  })}
                </Badge>
              );
            },
          },
          {
            header: 'Last update',
            accessorKey: 'lastUpdatedAt',
            cell: ({ row }) => {
              const date = new Date(row.original.lastUpdatedAt);
              return (
                <Badge variant="outline">
                  {date.toLocaleDateString('en-GB', {
                    year: 'numeric',
                    month: 'long',
                    day: 'numeric',
                    hour: 'numeric',
                    minute: 'numeric',
                    second: 'numeric',
                  })}
                </Badge>
              );
            },
          },
          {
            header: '# Updates',
            accessorKey: 'numberOfUpdates',
            cell: ({ row }) => {
              return <Badge variant="secondary">{row.original.numberOfUpdates}</Badge>;
            },
          },
        ]}
        data={data ?? []}
        defaultSorting={[{ id: 'createdAt', desc: true }]}
      />
    </div>
  );
};


================================================
FILE: apps/dashboard/src/pages/Updates/components/UpdatesTable/index.tsx
================================================
import { useQuery } from '@tanstack/react-query';
import { api } from '@/lib/api.ts';
import { ApiError } from '@/components/APIError';
import { DataTable } from '@/components/DataTable';
import { GitBranch, Milestone, Rss } from 'lucide-react';
import {
  Breadcrumb,
  BreadcrumbItem,
  BreadcrumbLink,
  BreadcrumbList,
  BreadcrumbPage,
  BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { Badge } from '@/components/ui/badge.tsx';
import apple from '@/assets/apple.svg';
import android from '@/assets/android.svg';
import { UpdateDetailsRef, UpdateDetailsSheet } from '@/components/UpdateDetailsSheet';
import { useRef } from 'react';

export const UpdatesTable = ({
  branch,
  runtimeVersion,
}: {
  branch: string;
  runtimeVersion: string;
}) => {
  const sheetRef = useRef<UpdateDetailsRef>(null);
  const { data, isLoading, error } = useQuery({
    queryKey: ['updates'],
    queryFn: () => api.getUpdates(branch, runtimeVersion),
  });

  return (
    <div className="w-full flex-1">
      <Breadcrumb className="mb-2">
        <BreadcrumbList>
          <BreadcrumbItem>
            <BreadcrumbLink href="/dashboard" className="flex items-center gap-2 underline">
              <GitBranch className="w-4" />
            </BreadcrumbLink>
          </BreadcrumbItem>
          <BreadcrumbSeparator />
          <BreadcrumbItem>
            <BreadcrumbPage>{branch}</BreadcrumbPage>
          </BreadcrumbItem>
          <BreadcrumbSeparator />
          <BreadcrumbItem>
            <BreadcrumbLink
              href={`/dashboard?branch=${branch}`}
              className="flex items-center gap-2 underline">
              <Milestone className="w-4" />
            </BreadcrumbLink>
          </BreadcrumbItem>
          <BreadcrumbSeparator />
          <BreadcrumbItem>
            <BreadcrumbPage>{runtimeVersion}</BreadcrumbPage>
          </BreadcrumbItem>
        </BreadcrumbList>
      </Breadcrumb>
      {!!error && <ApiError error={error} />}
      <UpdateDetailsSheet ref={sheetRef} branch={branch} runtimeVersion={runtimeVersion} />
      <DataTable
        loading={isLoading}
        columns={[
          {
            header: 'ID',
            accessorKey: 'updateId',
            cell: value => {
              return (
                <span className="flex flex-row gap-2 items-center w-full">
                  <Rss className="w-4" />
                  {value.row.original.updateId}
                </span>
              );
            },
          },
          {
            header: 'UUID',
            accessorKey: 'updateUUID',
            cell: value => {
              return value.row.original.updateUUID;
            },
          },
          {
            header: 'Platform',
            accessorKey: 'platform',
            cell: value => {
              const isIos = value.row.original.platform === 'ios';
              const isAndroid = value.row.original.platform === 'android';
              return (
                <div className="flex flex-row items-center gap-2">
                  {isIos && <img src={apple} className="w-4" alt="apple" />}
                  {isAndroid && <img src={android} className="w-4" alt="android" />}
                </div>
              );
            },
          },
          {
            header: 'Message',
            accessorKey: 'message',
            cell: value => {
              const msg = value.row.original.message;
              return msg ? (
                <span className="text-sm text-muted-foreground truncate max-w-[200px] block">
                  {msg}
                </span>
              ) : (
                <span className="text-sm text-muted-foreground">-</span>
              );
            },
          },
          {
            header: 'Commit',
            accessorKey: 'commitHash',
            cell: value => {
              return (
                <Badge variant="secondary" className="text-xs">
                  {value.row.original.commitHash.slice(0, 7)}
                </Badge>
              );
            },
          },
          {
            header: 'Published at',
            accessorKey: 'createdAt',
            cell: ({ row }) => {
              const date = new Date(row.original.createdAt);
              return (
                <Badge variant="outline">
                  {date.toLocaleDateString('en-GB', {
                    year: 'numeric',
                    month: 'long',
                    day: 'numeric',
                    hour: 'numeric',
                    minute: 'numeric',
                    second: 'numeric',
                  })}
                </Badge>
              );
            },
          },
        ]}
        data={data ?? []}
        defaultSorting={[{ id: 'createdAt', desc: true }]}
        onRowClick={row => {
          sheetRef?.current?.openSheet(row);
        }}
      />
    </div>
  );
};


================================================
FILE: apps/dashboard/src/pages/Updates/index.tsx
================================================
import { useSearchParams } from 'react-router';
import { useMemo } from 'react';
import { BranchesTable } from '@/pages/Updates/components/BranchesTable';
import { RuntimeVersionsTable } from '@/pages/Updates/components/RuntimeVersionsTable';
import { UpdatesTable } from '@/pages/Updates/components/UpdatesTable';

export const Updates = () => {
  const [searchParams] = useSearchParams();
  const currentBranch = searchParams.get('branch');
  const runtimeVersion = searchParams.get('runtimeVersion');

  const component = useMemo(() => {
    if (!currentBranch) {
      return <BranchesTable />;
    }
    if (!runtimeVersion) {
      return <RuntimeVersionsTable branch={currentBranch} />;
    }
    return <UpdatesTable branch={currentBranch} runtimeVersion={runtimeVersion} />;
  }, [currentBranch, runtimeVersion]);

  return (
    <div className="w-full h-screen flex-1 p-5">
      <h1 className="text-2xl font-medium mb-4">Updates</h1>
      {component}
    </div>
  );
};


================================================
FILE: apps/dashboard/src/vite-env.d.ts
================================================
/// <reference types="vite/client" />


================================================
FILE: apps/dashboard/tailwind.config.js
================================================
/** @type {import('tailwindcss').Config} */
export default {
    darkMode: ["class"],
    content: ["./index.html", "./src/**/*.{ts,tsx,js,jsx}"],
  theme: {
  	extend: {
  		borderRadius: {
  			lg: 'var(--radius)',
  			md: 'calc(var(--radius) - 2px)',
  			sm: 'calc(var(--radius) - 4px)'
  		},
  		colors: {
  			background: 'hsl(var(--background))',
  			foreground: 'hsl(var(--foreground))',
  			card: {
  				DEFAULT: 'hsl(var(--card))',
  				foreground: 'hsl(var(--card-foreground))'
  			},
  			popover: {
  				DEFAULT: 'hsl(var(--popover))',
  				foreground: 'hsl(var(--popover-foreground))'
  			},
  			primary: {
  				DEFAULT: 'hsl(var(--primary))',
  				foreground: 'hsl(var(--primary-foreground))'
  			},
  			secondary: {
  				DEFAULT: 'hsl(var(--secondary))',
  				foreground: 'hsl(var(--secondary-foreground))'
  			},
  			muted: {
  				DEFAULT: 'hsl(var(--muted))',
  				foreground: 'hsl(var(--muted-foreground))'
  			},
  			accent: {
  				DEFAULT: 'hsl(var(--accent))',
  				foreground: 'hsl(var(--accent-foreground))'
  			},
  			destructive: {
  				DEFAULT: 'hsl(var(--destructive))',
  				foreground: 'hsl(var(--destructive-foreground))'
  			},
  			border: 'hsl(var(--border))',
  			input: 'hsl(var(--input))',
  			ring: 'hsl(var(--ring))',
  			chart: {
  				'1': 'hsl(var(--chart-1))',
  				'2': 'hsl(var(--chart-2))',
  				'3': 'hsl(var(--chart-3))',
  				'4': 'hsl(var(--chart-4))',
  				'5': 'hsl(var(--chart-5))'
  			},
  			sidebar: {
  				DEFAULT: 'hsl(var(--sidebar-background))',
  				foreground: 'hsl(var(--sidebar-foreground))',
  				primary: 'hsl(var(--sidebar-primary))',
  				'primary-foreground': 'hsl(var(--sidebar-primary-foreground))',
  				accent: 'hsl(var(--sidebar-accent))',
  				'accent-foreground': 'hsl(var(--sidebar-accent-foreground))',
  				border: 'hsl(var(--sidebar-border))',
  				ring: 'hsl(var(--sidebar-ring))'
  			}
  		}
  	}
  },
  plugins: [require("tailwindcss-animate")],
}



================================================
FILE: apps/dashboard/tsconfig.app.json
================================================
{
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,
    "jsx": "react-jsx",

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true,
    "baseUrl": ".",
    "paths": {
      "@/*": [
        "./src/*"
      ]
    }
  },
  "include": ["src"]
}


================================================
FILE: apps/dashboard/tsconfig.json
================================================
{
  "files": [],
  "references": [
    { "path": "./tsconfig.app.json" },
    { "path": "./tsconfig.node.json" }
  ],
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@/*": ["./src/*"]
    }
  }
}


================================================
FILE: apps/dashboard/tsconfig.node.json
================================================
{
  "compilerOptions": {
    "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
    "target": "ES2022",
    "lib": ["ES2023"],
    "module": "ESNext",
    "skipLibCheck": true,

    /* Bundler mode */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "isolatedModules": true,
    "moduleDetection": "force",
    "noEmit": true,

    /* Linting */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "noUncheckedSideEffectImports": true
  },
  "include": ["vite.config.ts"]
}


================================================
FILE: apps/dashboard/vite.config.ts
================================================
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react-swc';
import * as path from 'node:path';

// https://vite.dev/config/
export default defineConfig({
  plugins: [react()],
  base: '/dashboard',
  resolve: {
    alias: {
      '@': path.resolve(__dirname, './src'),
    },
  },
});


================================================
FILE: apps/docs/.gitignore
================================================
# Dependencies
/node_modules

# Production
/build

# Generated files
.docusaurus
.cache-loader

# Misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local

npm-debug.log*
yarn-debug.log*
yarn-error.log*


================================================
FILE: apps/docs/README.md
================================================
# Website

This website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.

### Installation

```
$ yarn
```

### Local Development

```
$ yarn start
```

This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.

### Build

```
$ yarn build
```

This command generates static content into the `build` directory and can be served using any static contents hosting service.

### Deployment

Using SSH:

```
$ USE_SSH=true yarn deploy
```

Not using SSH:

```
$ GIT_USER=<Your GitHub username> yarn deploy
```

If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch.


================================================
FILE: apps/docs/docs/advanced/_category_.json
================================================
{
  "label": "Advanced",
  "position": 6
}


================================================
FILE: apps/docs/docs/advanced/prometheus.mdx
================================================
---
sidebar_position: 1
---

# Prometheus and Grafana

## Prometheus

You can monitor the **Expo Open OTA** server using Prometheus. The server exposes metrics on the `/metrics` endpoint.
To activate the Prometheus feature, set the `PROMETHEUS_ENABLED` environment variable to `true`.
If you are using our [Helm chart](/docs/deployment/helm), the environment variable will be automatically set for you if `prometheus.io/scrape: "true"` is present in `podAnnotations`.

## Grafana Dashboard

You can use the following dashboard to visualize the metrics exposed by the server:
```json
{
  "annotations": {
    "list": [
      {
        "builtIn": 1,
        "datasource": {
          "type": "datasource",
          "uid": "grafana"
        },
        "enable": true,
        "hide": true,
        "iconColor": "rgba(0,211,255,1)",
        "name": "Annotations & Alerts",
        "target": {
          "limit": 100,
          "matchAny": false,
          "tags": [],
          "type": "dashboard"
        },
        "type": "dashboard"
      }
    ]
  },
  "editable": true,
  "fiscalYearStartMonth": 0,
  "graphTooltip": 0,
  "id": 145,
  "links": [],
  "panels": [
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 0
      },
      "id": 10,
      "panels": [],
      "title": "Users",
      "type": "row"
    },
    {
      "datasource": {
        "uid": "$DS_PROM"
      },
      "fieldConfig": {
        "defaults": {
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "short"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 0,
        "y": 1
      },
      "id": 1,
      "options": {
        "colorMode": "value",
        "graphMode": "area",
        "justifyMode": "auto",
        "orientation": "horizontal",
        "percentChangeColorMode": "standard",
        "reduceOptions": {
          "calcs": [
            "last"
          ],
          "fields": "",
          "values": false
        },
        "showPercentChange": false,
        "textMode": "auto",
        "wideLayout": true
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "uid": "$DS_PROM"
          },
          "editorMode": "code",
          "expr": "max(\n  sum by (instance) (global_active_users_total)\n)",
          "legendFormat": "Total Unique Active Users",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "🟢 Total Unique Active Users (4h)",
      "type": "stat"
    },
    {
      "datasource": {
        "uid": "$DS_PROM"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 8,
        "y": 1
      },
      "id": 5,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "uid": "$DS_PROM"
          },
          "editorMode": "code",
          "expr": "max by (runtime, update) (\n  sum by (platform, runtime, branch, update, instance) (\n    active_users_total{platform=~\"$platform\", runtime=~\"$runtime\", branch=~\"$branch\", update=~\"$update\"}\n  )\n)",
          "legendFormat": "{{platform}}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "📊 Unique Active Users by Runtime And Update",
      "type": "timeseries"
    },
    {
      "datasource": {
        "uid": "$DS_PROM"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 16,
        "y": 1
      },
      "id": 2,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "uid": "$DS_PROM"
          },
          "editorMode": "code",
          "expr": "max by (platform) (\n  sum by (platform, instance) (\n    global_active_users_total{platform=~\"$platform\"}\n  )\n)",
          "legendFormat": "{{platform}}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "📊 Unique Active Users by Platform",
      "type": "timeseries"
    },
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 9
      },
      "id": 9,
      "panels": [],
      "title": "Errors",
      "type": "row"
    },
    {
      "datasource": {
        "uid": "$DS_PROM"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 0,
        "y": 10
      },
      "id": 6,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "uid": "$DS_PROM"
          },
          "editorMode": "code",
          "expr": "sum(update_error_users_total{platform=~\"$platform\", runtime=~\"$runtime\", branch=~\"$branch\", update=~\"$update\"})\n  by (update)",
          "legendFormat": "{{platform}}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "🚨 Fatal error per update",
      "type": "timeseries"
    },
    {
      "datasource": {
        "uid": "$DS_PROM"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "none"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 8,
        "y": 10
      },
      "id": 7,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "uid": "$DS_PROM"
          },
          "editorMode": "code",
          "expr": "sum(update_error_users_total{platform=~\"$platform\", runtime=~\"$runtime\", branch=~\"$branch\", update=~\"$update\"})",
          "legendFormat": "{{platform}}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "🚨 Update error users total",
      "type": "timeseries"
    },
    {
      "collapsed": false,
      "gridPos": {
        "h": 1,
        "w": 24,
        "x": 0,
        "y": 18
      },
      "id": 8,
      "panels": [],
      "title": "Updates",
      "type": "row"
    },
    {
      "datasource": {
        "uid": "$DS_PROM"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "short"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 0,
        "y": 19
      },
      "id": 3,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "table",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "uid": "$DS_PROM"
          },
          "expr": "sum(update_downloads_total{platform=~\"$platform\", runtime=~\"$runtime\", branch=~\"$branch\", update=~\"$update\"}) by (update)",
          "legendFormat": "{{update}}",
          "refId": "A"
        }
      ],
      "title": "⬇️ Total Update Downloads by Update",
      "type": "timeseries"
    },
    {
      "datasource": {
        "uid": "$DS_PROM"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "short"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 8,
        "w": 8,
        "x": 8,
        "y": 19
      },
      "id": 11,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "table",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "uid": "$DS_PROM"
          },
          "editorMode": "code",
          "expr": "sum(update_downloads_total{platform=~\"$platform\", runtime=~\"$runtime\", branch=~\"$branch\", update=~\"$update\"}) by (runtime)",
          "legendFormat": "{{update}}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "⬇️ Total Update Downloads by Runtime",
      "type": "timeseries"
    },
    {
      "datasource": {
        "uid": "$DS_PROM"
      },
      "fieldConfig": {
        "defaults": {
          "color": {
            "mode": "palette-classic"
          },
          "custom": {
            "axisBorderShow": false,
            "axisCenteredZero": false,
            "axisColorMode": "text",
            "axisLabel": "",
            "axisPlacement": "auto",
            "barAlignment": 0,
            "barWidthFactor": 0.6,
            "drawStyle": "line",
            "fillOpacity": 0,
            "gradientMode": "none",
            "hideFrom": {
              "legend": false,
              "tooltip": false,
              "viz": false
            },
            "insertNulls": false,
            "lineInterpolation": "linear",
            "lineWidth": 1,
            "pointSize": 5,
            "scaleDistribution": {
              "type": "linear"
            },
            "showPoints": "auto",
            "spanNulls": false,
            "stacking": {
              "group": "A",
              "mode": "none"
            },
            "thresholdsStyle": {
              "mode": "off"
            }
          },
          "mappings": [],
          "thresholds": {
            "mode": "absolute",
            "steps": [
              {
                "color": "green",
                "value": null
              },
              {
                "color": "red",
                "value": 80
              }
            ]
          },
          "unit": "short"
        },
        "overrides": []
      },
      "gridPos": {
        "h": 12,
        "w": 24,
        "x": 0,
        "y": 27
      },
      "id": 4,
      "options": {
        "legend": {
          "calcs": [],
          "displayMode": "list",
          "placement": "bottom",
          "showLegend": true
        },
        "tooltip": {
          "hideZeros": false,
          "mode": "single",
          "sort": "none"
        }
      },
      "pluginVersion": "11.5.1",
      "targets": [
        {
          "datasource": {
            "uid": "$DS_PROM"
          },
          "editorMode": "code",
          "expr": "sum by (update, runtime) (update_downloads_total{platform=~\"$platform\", runtime=~\"$runtime\", branch=~\"$branch\", update=~\"$update\", updateType=~\"$updateType|.*\"})",
          "legendFormat": "{{updateType}}",
          "range": true,
          "refId": "A"
        }
      ],
      "title": "⚡ Update Downloads Rate Over Time by Runtime & Update",
      "type": "timeseries"
    }
  ],
  "preload": false,
  "refresh": "5s",
  "schemaVersion": 40,
  "tags": [
    "oss",
    "metrics",
    "ota-server"
  ],
  "templating": {
    "list": [
      {
        "current": {
          "text": "All",
          "value": "$__all"
        },
        "includeAll": true,
        "label": "Data Source",
        "name": "DS_PROM",
        "options": [],
        "query": "prometheus",
        "refresh": 1,
        "type": "datasource"
      },
      {
        "allValue": ".*",
        "current": {
          "text": "All",
          "value": "$__all"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "CcrOqjRnk"
        },
        "includeAll": true,
        "label": "Platform",
        "name": "platform",
        "options": [],
        "query": "label_values(active_users_total, platform)",
        "refresh": 1,
        "type": "query"
      },
      {
        "allValue": ".*",
        "current": {
          "text": "2.0.0",
          "value": "2.0.0"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "CcrOqjRnk"
        },
        "includeAll": true,
        "label": "Runtime",
        "name": "runtime",
        "options": [],
        "query": "label_values(active_users_total, runtime)",
        "refresh": 1,
        "type": "query"
      },
      {
        "allValue": ".*",
        "current": {
          "text": "All",
          "value": "$__all"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "CcrOqjRnk"
        },
        "includeAll": true,
        "label": "Branch",
        "name": "branch",
        "options": [],
        "query": "label_values(active_users_total, branch)",
        "refresh": 1,
        "type": "query"
      },
      {
        "allValue": ".*",
        "current": {
          "text": "All",
          "value": "$__all"
        },
        "datasource": {
          "type": "prometheus",
          "uid": "CcrOqjRnk"
        },
        "includeAll": true,
        "label": "Update",
        "name": "update",
        "options": [],
        "query": "label_values(active_users_total, update)",
        "refresh": 1,
        "type": "query"
      }
    ]
  },
  "time": {
    "from": "now-15m",
    "to": "now"
  },
  "timepicker": {},
  "timezone": "",
  "title": "🚀 OTA Server Ultimate Metrics Dashboard",
  "uid": "ota-metrics-dashboard",
  "version": 13,
  "weekStart": ""
}
```




================================================
FILE: apps/docs/docs/dashboard.mdx
================================================
---
sidebar_position: 5
---
import useBaseUrl from '@docusaurus/useBaseUrl';

# Dashboard

The **Expo-Open-OTA Dashboard** is a web interface that allows you to:
- 🔍 View and manage your **Expo branches**
- 🔄 Track **runtime versions**
- 📦 Monitor and manage **OTA updates**
- 🔀 **Switch channel-to-branch mappings in one click** — useful for instant rollbacks, A/B testing, or promoting a staging branch to production without republishing

## Features

### Updates
Browse your branches, drill down into runtime versions, and inspect individual OTA updates with their metadata (commit hash, platform, message, etc.).

### Channels
View all your release channels and **change which branch a channel points to** with a single click. This is especially useful for:
- **Instant rollback**: Point a production channel back to a previous branch
- **Testing**: Temporarily route a channel to a test branch
- **Promotion**: Switch a channel from staging to production without rebuilding

### Settings
View the current server configuration at a glance.

## How to Enable the Dashboard
To activate the dashboard, configure the following environment variables:

### **1️⃣ Enable the Dashboard**
Set the `USE_DASHBOARD` environment variable to `true`:
```sh
USE_DASHBOARD=true
```

### **2️⃣ Set the Admin Password**

Set the `ADMIN_PASSWORD` environment variable to a secure password:
```sh
ADMIN_PASSWORD=your-password
```

Once enabled, the dashboard will be available at:
```sh
http://<your-server>/dashboard
```





================================================
FILE: apps/docs/docs/deployment/_category_.json
================================================
{
  "label": "Deployment",
  "position": 3,
  "link": {
    "type": "generated-index",
    "title": "Deployment",
    "description": "Discover how to deploy your self-hosted Expo Updates Server."
  }
}


================================================
FILE: apps/docs/docs/deployment/custom.mdx
================================================
---
sidebar_position: 3
---
# Custom Deployment
Deploy **Expo Open OTA** on your own infrastructure with docker.

## Pull docker image
```bash
docker pull ghcr.io/axelmarciano/expo-open-ota:latest
```

## Run docker container
You can use a .env file to set the [environment variables required by the server](/docs/reference/environment) and run
```bash
docker run --rm -it --env-file .env --platform linux/amd64 ghcr.io/axelmarciano/expo-open-ota:latest
```

Or you can pass the environment variables directly to the docker run command
```bash
docker run --rm -it -e PORT=3000 -e ENV_KEY=value ... --platform linux/amd64 ghcr.io/axelmarciano/expo-open-ota:latest
```

The server is now running on port **3000**.

:::warning
A public HTTPS endpoint is required for the expo client to fetch the updates. You can use a reverse proxy like Nginx or Traefik to expose the server to the internet.
:::


================================================
FILE: apps/docs/docs/deployment/helm.mdx
================================================
---
sidebar_position: 2
---
# Helm
Deploy **Expo Open OTA** using Helm, a package manager for Kubernetes.

A ready-to-use Helm chart is available to deploy **Expo Open OTA** on your Kubernetes cluster.

## Prerequisites
A running Kubernetes cluster and Helm installed on your local machine are required to deploy the application.
If you are not familiar with Helm or Kubernetes, we recommend you to deploy the server with [custom docker deployment](/docs/deployment/custom) or [railway](/docs/deployment/railway).

Clone the repository and navigate to the `helm` directory.

```bash
git clone https://github.com/axelmarciano/expo-open-ota
cd expo-open-ota/helm
```

## Configuration

The Helm chart uses a set of configurable values defined in `values.yaml`. These values can be overridden by passing a custom `values.yaml` file when deploying the chart.

### Conditional Logic for Environment Variables

The environment variables used by the application depend on the following key settings:

- **`secretName`**: If defined, environment variables are loaded from the specified Kubernetes secret instead of being set directly.
- **`storageMode`**:
  - `s3`: Requires `AWS_REGION` and `S3_BUCKET_NAME` to be set. Optionally supports `AWS_BASE_ENDPOINT` for S3-compatible object storage and `AWS_S3_FORCE_PATH_STYLE=true` for providers that require path-style addressing.
  - `local`: Requires `LOCAL_BUCKET_BASE_PATH` to be set.
- **`keysStorageType`**:
  - `aws-secrets-manager`: Requires AWS Secrets Manager variables (`AWSSM_EXPO_PUBLIC_KEY_SECRET_ID`, `AWSSM_EXPO_PRIVATE_KEY_SECRET_ID`).
  - `local`: Requires local key paths (`PRIVATE_LOCAL_EXPO_KEY_PATH`, `PUBLIC_LOCAL_EXPO_KEY_PATH`).
  - `environment`: Requires base64-encoded keys (`PUBLIC_EXPO_KEY_B64`, `PRIVATE_EXPO_KEY_B64`).
- **`useCloudfrontRedirect`**:
  - If `true`, requires `CLOUDFRONT_DOMAIN`, `CLOUDFRONT_KEY_PAIR_ID`, and a CloudFront private key (`CLOUDFRONT_PRIVATE_KEY_B64`, `PRIVATE_CLOUDFRONT_KEY_PATH`, or `AWSSM_CLOUDFRONT_PRIVATE_KEY_SECRET_ID`, depending on `keysStorageType`).
- **`useAWSAccessKeys`**:
  - If `true`, requires `AWS_ACCESS_KEY_ID` and `AWS_SECRET_ACCESS_KEY` to be set.
- **`useGenericCDN`**:
  - If `true`, requires `S3_CDN_PREFIX` to be set.

### Deployment

To install the Helm chart with default values:

```bash
helm install expo-open-ota -n NAMESPACE ./chart
```

To override values, create a custom `my-values.yaml` file and run:

```bash
helm install expo-open-ota ./chart -n NAMESPACE -f my-values.yaml
```

To upgrade an existing release:

```bash
helm upgrade expo-open-ota -n NAMESPACE ./chart -f my-values.yaml
```

For additional configuration details, refer to the [Environment Variables](/docs/reference/environment) documentation.

## Ingress Configuration
The Ingress configuration is crucial for exposing Expo Open OTA through a specific domain and must match the BASE_URL defined in the application.


================================================
FILE: apps/docs/docs/deployment/railway.mdx
================================================
---
sidebar_position: 1
---
# Railway
Deploy **Expo Open OTA** using Railway, a platform for deploying and managing applications.

## Prepare environment variables

Prepare your [environment variables](/docs/reference/environment).

## Deploy on Railway using template

[![Deploy on Railway](https://railway.com/button.svg)](https://railway.com/deploy/MGW3k1?referralCode=OEHlEK&utm_medium=integration&utm_source=template&utm_campaign=generic)

Fill the environment variables in the project settings.


================================================
FILE: apps/docs/docs/deployment/testing.mdx
================================================
---
sidebar_position: 3
---
# Local testing
If you want to test **Expo Open OTA** locally, you can use the provided docker-compose file to run the server locally
and expose it on internet using reverse proxy like ngrok.

## Clone the repository
```bash
git clone https://github.com/axelmarciano/expo-open-ota
cd expo-open-ota
```

## Setup .env

Setup [environment variables](/docs/reference/environment) by creating a `.env` file in the root directory of the project.

## Setup the dashboard (local)

The dashboard isn’t served at **/dashboard** when running locally. Start it in dev mode:

```bash
cd apps/dashboard
npm install
npm run dev
```

## Run the server using docker-compose
```bash
docker-compose build
docker-compose up
```

The server is now running on port **3000**.

## 🚀 Running the Example App

The example app is located in apps/example-app and is designed to help you test the update server locally.

:::note
ℹ️ The app must be run in release mode to properly test OTA behavior.
:::

### Setup certificates

The signing certificates required for update validation are located in:

```bash
apps/example-app/certs/certificate-dev.pem
apps/example-app/certs/public-key.pem
```

To enable update signature verification, you must configure these certificates in your server environment.
[Ref](/docs/server-configuration/key-store#expo-signing-certificate)

### Build the Example App

```bash
yarn prebuild_production
OR
yarn prebuild_staging
```


### Configure the Expo Project

Create an Expo project in your Expo dashboard with two branches:
- `staging`
- `production`

Each branch should have its own release channel. Then retrieve the Project ID from the Expo dashboard.

### Update app.json

In apps/example-app/app.json, replace ``YOUR_PROJECT_ID`` with your actual Expo project ID:

``` json
"extra": {
  "router": {
    "origin": false
  },
  "eas": {
    "projectId": "YOUR_PROJECT_ID"
  }
}
```

### Android Setup

```bash
yarn prebuild_production   # Prebuild the app with the 'production' config
cd android
./gradlew clean            # Clean previous builds
./gradlew assembleRelease  # Build the release APK
./gradlew installRelease   # Install the release APK on a connected device/emulator
```

### IOS

```bash
cd ios
pod cache clean --all
pod install
```
Then:
1. Open the iOS project in Xcode: open ios/*.xcworkspace
2. Go to: Product → Scheme → Edit Scheme
3. Under Run, set Build Configuration to Release

### Testing OTA Updates

Once the app is installed in release mode, use the following commands to test OTA updates:

```bash
yarn release_production   # Publish an update to the 'production' channel
yarn release_staging      # Publish an update to the 'staging' channel
```

The app will dynamically switch between channels at runtime if configured accordingly.


================================================
FILE: apps/docs/docs/eoas/_category_.json
================================================
{
  "label": "Configure Your App",
  "position": 2,
  "link": {
    "type": "doc",
    "id": "eoas/intro"
  }
}


================================================
FILE: apps/docs/docs/eoas/configure.mdx
================================================
---
sidebar_position: 2
---

# Configure your Expo Project

## Prerequisites
A running and deployed **Expo Open OTA** server is required to configure your Expo project. If you haven't deployed the server yet, follow the [deployment guide](/docs/category/deployment) to get started.
You also need to have your expo signing certificate ready. If you haven't generated one yet, follow the [generate signing certificate](/docs/server-configuration/key-store#expo-signing-certificate) guide.

## Initialize your Expo project with Expo Open OTA

Run the following command in your Expo project to initialize the project for OTA updates.

```bash
npx eoas init
```

And follow the instructions.

After running the command, the app.config.js file will be updated with the necessary configuration for OTA updates.

## Create a new build

A new build of your application must be submitted. This ensures that the new configuration is applied to the app.
You can follow this [EAS Guide](https://docs.expo.dev/build/introduction/) to create a new build.


================================================
FILE: apps/docs/docs/eoas/intro.mdx
================================================
---
sidebar_position: 1
---

# EOAS

EOAS (**Expo Open Application Service**) is an npm package maintained by the **Expo Open OTA** project.

## Purpose

EOAS is designed to simplify the setup and management of projects using **Expo Open OTA**. It provides a streamlined way to configure the project and handle update publications efficiently.

## Features

### **Project Configuration**
EOAS helps automate essential setup steps, including:

- **Generating the certificate for code signing**
  - Ensures that updates are properly signed and verified before being delivered to end-users.
- **Configuring `app.config.js`**
  - Automatically updates the Expo configuration to point to the correct **Expo Open OTA server**, ensuring that published updates are fetched correctly.
- **Leveraging existing Expo logic**
  - EOAS relies heavily on Expo’s built-in mechanisms for calculating the project configuration and generating fingerprints.
  - This ensures consistency with Expo’s ecosystem and avoids redundant logic, making integration seamless.

### **Update Management**
EOAS provides commands to publish updates either:
- **Locally**, from a developer’s machine.
- **From CI/CD pipelines**, ensuring automated and consistent update deployment.

### **Authentication with EAS Credentials**
- EOAS uses **Expo Application Services (EAS) credentials** to authenticate update publications on the **Expo Open OTA server**.
- This ensures that only authorized users and CI/CD environments can publish updates, enhancing security and control.


================================================
FILE: apps/docs/docs/eoas/publish.mdx
================================================
---
sidebar_position: 3
---

# Publish OTA updates

## Runtime version

EOAS uses official Expo packages to resolve the runtime version of your project.
It supports the fingerprint policy.

## Publish an update

To publish an update, run the following command in your Expo project:

```bash
npx eoas publish --branch <branch-name> [--nonInteractive] [--outputDir <outputDir>] [--platform <platform>] [--packageRunner <runner>] [--message <message>] [--dumpSourcemap]
```

This command will retrieve the expo credentials from your .expo/state.json file or an EXPO_TOKEN in your runtime environment to authenticate
the request to the Expo API.

:::warning
 🚨EOAS publish will create a new build of your app. Do not forget to pass the necessary environment variables to the runtime environment.
 Example: `EXPO_TOKEN=your_token RELEASE_CHANNEL=staging npx eoas publish --branch <branch-name>`.
 Or with dotenv: `dotenv -e .env.local -- npx eoas publish --branch <branch-name>`.
:::

## Update message

You can attach a short description to each update using the `--message` (or `-m`) flag:

```bash
npx eoas publish --branch production -m "Fix login crash on Android"
```

If omitted, EOAS defaults to the latest git commit message, following the same behavior as EAS Update.
The message is visible in the [dashboard](/docs/dashboard) updates table.

## Package runner

By default, EOAS uses `npx` to spawn Expo CLI commands (`expo export`, `expo config`). If your project uses a different package manager, you can override this.

**Resolution priority:**

1. `--packageRunner` CLI flag
2. `EOAS_PACKAGE_RUNNER` environment variable
3. `packageManager` field in the nearest `package.json` (e.g. `"packageManager": "bun@1.3.6"` → `bunx`)
4. Falls back to `npx`

| `packageManager` value | Resolved runner |
| --- | --- |
| `bun@x.x.x` | `bunx` |
| `pnpm@x.x.x` | `pnpx` |
| `yarn@x.x.x` | `npx` |
| `npm@x.x.x` | `npx` |

```bash
# Auto-detected from package.json (zero config)
npx eoas publish --branch production

# Explicit flag
npx eoas publish --branch production --packageRunner bunx

# Via environment variable
EOAS_PACKAGE_RUNNER=bunx eoas publish --branch production
```

## Source maps

Pass `--dumpSourcemap` to emit Hermes source maps alongside the bundle in the output directory. This forwards `--dump-sourcemap` to the underlying `expo export` call.

```bash
npx eoas publish --branch production --dumpSourcemap
```

The source maps land next to each platform's bundle (e.g. `dist/_expo/static/js/ios/*.map`) and are not uploaded to the OTA server. They can be uploaded to a symbolication service like Sentry or PostHog from the same `dist/` directory, guaranteeing the source maps match the bundle that was published.

## CI/CD

You can automate the process of publishing updates by integrating the `npx eoas publish --nonInteractive` command in your CI/CD pipeline.
However, you need to make sure that the EXPO_TOKEN is set up in your CI/CD environment.
(Do not forget the `--nonInteractive` flag to avoid interactive prompts)


================================================
FILE: apps/docs/docs/eoas/republish.mdx
================================================
---
sidebar_position: 5
---

# Republish

The `republish` command lets you resend an existing update to a specified branch and platform. It walks you through selecting the runtime version and the exact update to republish.

## Usage

```bash
npx eoas republish --branch <branch-name> [--platform <platform>]
```

## Options

- `--branch <branch-name>`: Name of the branch to which the update will be republished.
- `--platform <platform>`: Platform to which the update will be republished. If not specified, all platforms will be used.

## Description

Republishing reuses the same code and assets from a previous update, assigning them a new update ID on the target branch and platform. Use it to re‑trigger deployments, recover from collisions, or reapply a past release.


================================================
FILE: apps/docs/docs/eoas/rollback.mdx
================================================
---
sidebar_position: 4
---

# Rollback

The `rollback` command allows you to publish a rollback update to a specific branch and runtime version.

⚠️ Not compatible with `disableAntiBrickingMeasure`, as expo-updates will ignore embedded updates in that case.

## Usage

Specify the branch and target runtime version to perform a rollback:
```bash
npx eoas rollback --branch <branch-name> [--platform <platform>]
```

## Description

A rollback update reverts the application on the specified branch to the embedded update without requiring a new native build.


================================================
FILE: apps/docs/docs/getting-started/_category_.json
================================================
{
  "label": "Getting Started",
  "position": 1,
  "link": {
    "type": "doc",
    "id": "getting-started/introduction"
  }
}


================================================
FILE: apps/docs/docs/getting-started/introduction.mdx
================================================
---
sidebar_position: 1
---

# Introduction

**Expo Open OTA** is an open-source, multi-cloud OTA update server for Expo and React Native applications. It implements the [Expo Updates protocol](https://docs.expo.dev/technical-specs/expo-updates-1/) and supports **AWS S3**, **Google Cloud Storage**, and any **S3-compatible** provider (Cloudflare R2, MinIO, DigitalOcean Spaces).

:::warning
**Expo Open OTA** is not affiliated with Expo. It is an independent open-source project.
:::

## How It Works

Expo Open OTA works by redirecting the `expo-updates` package of your application to a custom OTA server that implements several key endpoints:

### 1. `/manifest`
This endpoint is called by the Expo application on launch or when executing `checkForUpdateAsync()`. The `expo-updates` package includes several headers in its request:

- `expo-channel-name`
- `expo-protocol-version`
- `expo-platform`
- `expo-runtime-version`

Based on these headers, the server determines whether an update is available. The update is retrieved from the branch associated with the given channel in the Expo account.

### 2. `/assets`
When an update is available, a list of assets is sent back to the client. These assets are accessed via the `/assets` endpoint, which:

- Signs and compresses the files.
- Returns the required assets to the Expo client.

If a CDN is configured (CloudFront) or if using GCS, the returned URL is a pre-signed/signed link and the client downloads directly from the storage provider. Otherwise, the server returns the asset directly.

### 3. `/requestUploadUrl` & `/uploadLocalFile`
These routes are used by the `eoas` package to publish updates to the chosen storage solution, whether it's S3, Google Cloud Storage, or a local file system.
`/uploadLocalFile` is used to upload the file to the server when [storage mode](/docs/server-configuration/storage?storage=local) is set to `local`.

## Why Self-Host Your OTA Update server?

There are several reasons why you might want to self-host your updates instead of relying on the official Expo service:

### 1. **Cost Considerations**

Expo's pricing model for OTA updates is based on the number of Monthly Active Users (MAUs). For large-scale applications, costs can add up quickly. Below is a brief breakdown of their pricing:

- **1,000 MAUs**: Free
- **Next 199,000 MAUs**: $0.005 per MAU
- **Next 300,000 MAUs**: $0.00375 per MAU
- **Next 500,000 MAUs**: $0.0034 per MAU
- **Next 1,000,000 MAUs**: $0.003 per MAU
- [Full pricing details](https://expo.dev/pricing)

Self-hosting removes the dependency on Expo's pricing structure, giving you full control over your costs.

### 2. **Full Control Over Your Infrastructure**

By hosting your own OTA server, you can:

- Store update files on your own infrastructure.
- Secure your files using custom certificates and authentication mechanisms.
- Ensure compliance with specific security requirements.

### 3. **Custom Network and Security Constraints**

One of the key motivations for this project came from my experience at **Skeat** ([skeatapp.com](https://skeatapp.com)), where we needed to deploy applications within highly controlled network environments. Many of our clients operate in restricted setups where:

- Internet access is limited.
- Network traffic must be routed through proxies and VPNs.

Self-hosting an Expo OTA server allows **full control** over network flows, ensuring seamless deployments even in highly secured environments.

## Why Does This Project Rely on Expo?

Although we self-host OTA updates, Expo remains an essential part of our workflow for several reasons:

### 1. **EAS (Expo Application Services) is Great**

EAS provides powerful features for **building, signing, and submitting applications**. These functionalities are industry-standard and difficult to replace, making them **worth every penny**.

### 2. **Branch & Release Channel Management**

We currently use Expo's API to authenticate uploads and manage **branch-to-release channel mappings**, ensuring smooth versioning and deployment.

### 3. **Potential for Future Independence**

At present, this project relies on Expo for managing release channels and branches. However, we aim to implement our own release and versioning logic in the future. This would allow for greater autonomy, reducing dependence on Expo while maintaining flexibility for developers.

---

By self-hosting **Expo Open OTA**, you gain the flexibility, security, and control needed for large-scale or restricted-network deployments, while still benefiting from Expo's powerful development tools.


================================================
FILE: apps/docs/docs/getting-started/prerequisites.mdx
================================================
---
sidebar_position: 2
---

# Prerequisites

To get started with **Expo Open OTA**, you need to review the following prerequisites:

:::note
Some of the environment variables required for the server are listed below. You can set them in a `.env` file in the root of the project or keep them in a safe place to prepare for deployment.
:::

## Expo Token & Project ID

To interact with the Expo API, you need an **Expo token** and **project ID**. These credentials authenticate update publishing
and allow the server to fetch the release channel-to-branch mappings.

### How to Get Your Expo Token

1. Go to the [Access tokens page](https://expo.dev/settings/access-tokens) on your expo dashboard.
2. Click on the **+ Create token** button.
3. Enter a name for your token and click **Create**.
4. Copy the generated token and store it in a safe place.

:::info
This token will be used as the `EXPO_ACCESS_TOKEN` environment variable.
:::

### How to Get Your Project ID

#### From EAS CLI

1. Ensure you have the [EAS CLI](https://github.com/expo/eas-cli) installed.
2. Ensure you are logged in to your Expo account by running

    ```bash title="cd ./my-expo-project"
    eas account:view
    ```
3. On your terminal go to the root directory of your Expo project.
4. Run

    ```bash title="cd ./my-expo-project"
    eas project:info
    ```
   to get the project ID.

#### From Expo Dashboard

1. Login to your [Expo dashboard](https://expo.dev).
2. Go to the **Projects page**
3. Click on the project you want to get the ID for.
4. The project ID is displayed on the top of the page.

:::info
This ID will be used as the `EXPO_APP_ID` environment variable.
:::

## JWT Secret

A JWT Secret can be used to sign and verify some of the requests made to the server. This secret is used to sign the JWT token
that is sent to the client when they request an update manifest.
To generate a JWT secret, you can use the following command:

```bash title="Generate JWT Secret"
openssl rand -base64 32
```

:::info
This secret will be used as the `JWT_SECRET` environment variable.
:::

## Base URL

The base URL is the URL where your server will be hosted. This URL is used to generate the URLs for the assets that are sent to the client.
Example: `https://my-ota-server.com`

:::info
This URL will be used as the `BASE_URL` environment variable.
:::


================================================
FILE: apps/docs/docs/getting-started/quick-start.mdx
================================================
---
sidebar_position: 3
---

# Quick Start

Get your first OTA update running in minutes with a minimal local setup.

:::tip
This guide uses **local storage, local keys, and local cache** — the simplest configuration possible. For production setups (S3, Redis, CloudFront), see [Server Configuration](/docs/category/server-configuration).
:::

## 1. Get your Expo credentials

You need two things from Expo:

- **Access Token**: Create one at [expo.dev/settings/access-tokens](https://expo.dev/settings/access-tokens)
- **Project ID**: Run `eas project:info` in your Expo project, or find it in your [Expo dashboard](https://expo.dev)

## 2. Generate signing certificates

In your Expo project directory, generate the signing key pair:

```bash title="cd ./my-expo-project"
npx eoas generate-certs
```

This creates three files in `certs/`:
- `private-key.pem` and `public-key.pem` — used by the server to sign and verify updates
- `certificate.pem` — commit this to your Expo project (used by the client to verify updates)

## 3. Start the server

Pull and run the Docker image with a minimal configuration:

```bash
docker run --rm -it \
  -p 3000:3000 \
  -e BASE_URL=http://localhost:3000 \
  -e EXPO_ACCESS_TOKEN=your-expo-token \
  -e EXPO_APP_ID=your-project-id \
  -e JWT_SECRET=$(openssl rand -base64 32) \
  -e STORAGE_MODE=local \
  -e KEYS_STORAGE_TYPE=local \
  -e PUBLIC_LOCAL_EXPO_KEY_PATH=/keys/public-key.pem \
  -e PRIVATE_LOCAL_EXPO_KEY_PATH=/keys/private-key.pem \
  -e CACHE_MODE=local \
  -e USE_DASHBOARD=true \
  -e ADMIN_PASSWORD=admin \
  -v $(pwd)/certs:/keys:ro \
  -v ./updates:/updates \
  ghcr.io/axelmarciano/expo-open-ota:latest
```

The server is now running on `http://localhost:3000`. You can verify with:

```bash
curl http://localhost:3000/hc
```

## 4. Configure your Expo app

In your Expo project, point your app to your local server:

```bash title="cd ./my-expo-project"
npx eoas init
```

Follow the prompts — it will update your `app.config.js` to use your Expo Open OTA server.

:::warning
After running `eoas init`, you need to create a **new build** of your app for the configuration to take effect. See [EAS Build guide](https://docs.expo.dev/build/introduction/).
:::

## 5. Create a release channel

Your app uses a **release channel** to know which branch to pull updates from. The server queries Expo for this channel→branch mapping when your app checks for updates.

Go to your [Expo dashboard](https://expo.dev), navigate to your project under **Over-the-air-updates → Channels**, and create a channel (e.g. `production`) pointing to the branch you'll publish to (e.g. `production`).

:::info
Branches are created automatically when you publish. Channels must be created manually on [expo.dev](https://expo.dev).
If you have the EAS CLI installed, you can also run `eas channel:create production`.
:::

## 6. Publish your first update

```bash title="cd ./my-expo-project"
npx eoas publish --branch production
```

That's it! Your app will now receive OTA updates from your self-hosted server.

## Next steps

You now have a working local setup. To move to production:

- **[Configure Your App](/docs/eoas/intro)** — Learn about publishing, rollback, and republishing updates
- **[Deployment](/docs/category/deployment)** — Deploy on Railway, Docker, or Kubernetes
- **[Server Configuration](/docs/category/server-configuration)** — Switch to S3 storage, Redis cache, and CloudFront CDN
- **[Dashboard](/docs/dashboard)** — Enable the web UI to monitor your updates


================================================
FILE: apps/docs/docs/reference/_category_.json
================================================
{
  "label": "Reference",
  "position": 7,
  "link": {
    "type": "generated-index",
    "title": "Reference",
    "description": "Complete reference documentation for Expo Open OTA."
  }
}


================================================
FILE: apps/docs/docs/reference/environment.mdx
================================================
---
sidebar_position: 1
---

# Environment variables

The **Expo Open OTA** server requires several environment variables to be set in order to function correctly. These variables are used to configure the server, interact with the Expo API, and manage the server's behavior.
You can set these variables in a `.env` file for local development or in your deployment environment.

## Supported Environment Variables


### 🌍 **API Configuration**
| Name | Required | Description | Example | Reference |
| --- | --- | --- | --- | --- |
| `BASE_URL` | ✅ | Root URL of your server | `https://ota.mysite.com` | [Ref](/docs/getting-started/prerequisites#base-url) |

### 🔑 **Authentication & Security**
| Name | Required | Description | Example | Reference |
| --- | --- | --- | --- | --- |
| `JWT_SECRET` | ✅ | JWT secret used to sign some endpoints | `Random string` | [Ref](/docs/getting-started/prerequisites#jwt-secret) |

### 📱 **Expo Configuration**
| Name | Required | Description | Example | Reference |
| --- | --- | --- | --- | --- |
| `EXPO_APP_ID` | ✅ | The ID of the Expo project | `Random string` | [Ref](/docs/getting-started/prerequisites#how-to-get-your-project-id) |
| `EXPO_ACCESS_TOKEN` | ✅ | Expo access token | `Random string` | [Ref](/docs/getting-started/prerequisites#how-to-get-your-expo-token) |

### ⚡ **Cache Configuration**
| Name | Required | Description | Example | Reference |
| --- | --- | --- | --- | --- |
| `CACHE_MODE` | ✅ | `local`, `redis`, or `redis-sentinel` | `local` | [Ref](/docs/server-configuration/cache) |
| `REDIS_HOST` | ✅ if CACHE_MODE = `redis` | Redis host | `127.0.0.1` | [Ref](/docs/server-configuration/cache?cache=redis) |
| `REDIS_PORT` | ✅ if CACHE_MODE = `redis` | Redis 
Download .txt
gitextract_u7h5vna8/

├── .github/
│   ├── FUNDING.yml
│   └── workflows/
│       ├── push.yml
│       └── release.yml
├── .gitignore
├── Dockerfile
├── Dockerfile-ci
├── Dockerfile-dev
├── LICENSE.md
├── Makefile
├── README.md
├── apps/
│   ├── dashboard/
│   │   ├── .gitignore
│   │   ├── .prettierrc
│   │   ├── README.md
│   │   ├── components.json
│   │   ├── eslint.config.js
│   │   ├── index.html
│   │   ├── package.json
│   │   ├── postcss.config.js
│   │   ├── public/
│   │   │   └── env.js
│   │   ├── src/
│   │   │   ├── App.tsx
│   │   │   ├── components/
│   │   │   │   ├── APIError/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── Combobox/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── DataTable/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── UpdateDetailsSheet/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── app-sidebar.tsx
│   │   │   │   └── ui/
│   │   │   │       ├── alert.tsx
│   │   │   │       ├── badge.tsx
│   │   │   │       ├── breadcrumb.tsx
│   │   │   │       ├── button.tsx
│   │   │   │       ├── card.tsx
│   │   │   │       ├── command.tsx
│   │   │   │       ├── dialog.tsx
│   │   │   │       ├── form.tsx
│   │   │   │       ├── input.tsx
│   │   │   │       ├── label.tsx
│   │   │   │       ├── popover.tsx
│   │   │   │       ├── progress.tsx
│   │   │   │       ├── separator.tsx
│   │   │   │       ├── sheet.tsx
│   │   │   │       ├── sidebar.tsx
│   │   │   │       ├── skeleton.tsx
│   │   │   │       ├── table.tsx
│   │   │   │       ├── toast.tsx
│   │   │   │       ├── toaster.tsx
│   │   │   │       └── tooltip.tsx
│   │   │   ├── containers/
│   │   │   │   └── Layout/
│   │   │   │       └── index.tsx
│   │   │   ├── hooks/
│   │   │   │   ├── use-mobile.tsx
│   │   │   │   └── use-toast.ts
│   │   │   ├── index.css
│   │   │   ├── lib/
│   │   │   │   ├── api.ts
│   │   │   │   ├── auth.ts
│   │   │   │   └── utils.ts
│   │   │   ├── main.tsx
│   │   │   ├── pages/
│   │   │   │   ├── Channels/
│   │   │   │   │   ├── components/
│   │   │   │   │   │   └── SelectBranch/
│   │   │   │   │   │       └── index.tsx
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── Login/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── Logout/
│   │   │   │   │   └── index.tsx
│   │   │   │   ├── Settings/
│   │   │   │   │   └── index.tsx
│   │   │   │   └── Updates/
│   │   │   │       ├── components/
│   │   │   │       │   ├── BranchesTable/
│   │   │   │       │   │   └── index.tsx
│   │   │   │       │   ├── RuntimeVersionsTable/
│   │   │   │       │   │   └── index.tsx
│   │   │   │       │   └── UpdatesTable/
│   │   │   │       │       └── index.tsx
│   │   │   │       └── index.tsx
│   │   │   └── vite-env.d.ts
│   │   ├── tailwind.config.js
│   │   ├── tsconfig.app.json
│   │   ├── tsconfig.json
│   │   ├── tsconfig.node.json
│   │   └── vite.config.ts
│   ├── docs/
│   │   ├── .gitignore
│   │   ├── README.md
│   │   ├── docs/
│   │   │   ├── advanced/
│   │   │   │   ├── _category_.json
│   │   │   │   └── prometheus.mdx
│   │   │   ├── dashboard.mdx
│   │   │   ├── deployment/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── custom.mdx
│   │   │   │   ├── helm.mdx
│   │   │   │   ├── railway.mdx
│   │   │   │   └── testing.mdx
│   │   │   ├── eoas/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── configure.mdx
│   │   │   │   ├── intro.mdx
│   │   │   │   ├── publish.mdx
│   │   │   │   ├── republish.mdx
│   │   │   │   └── rollback.mdx
│   │   │   ├── getting-started/
│   │   │   │   ├── _category_.json
│   │   │   │   ├── introduction.mdx
│   │   │   │   ├── prerequisites.mdx
│   │   │   │   └── quick-start.mdx
│   │   │   ├── reference/
│   │   │   │   ├── _category_.json
│   │   │   │   └── environment.mdx
│   │   │   └── server-configuration/
│   │   │       ├── _category_.json
│   │   │       ├── cache.mdx
│   │   │       ├── cdn/
│   │   │       │   ├── _category_.json
│   │   │       │   ├── cloudfront.mdx
│   │   │       │   ├── generic.mdx
│   │   │       │   └── intro.mdx
│   │   │       ├── key-store.mdx
│   │   │       └── storage.mdx
│   │   ├── docusaurus.config.ts
│   │   ├── package.json
│   │   ├── sidebars.ts
│   │   ├── src/
│   │   │   ├── components/
│   │   │   │   ├── BrowserWindow/
│   │   │   │   │   ├── index.tsx
│   │   │   │   │   └── styles.module.css
│   │   │   │   └── HomepageFeatures/
│   │   │   │       ├── index.tsx
│   │   │   │       └── styles.module.css
│   │   │   ├── css/
│   │   │   │   └── custom.css
│   │   │   └── pages/
│   │   │       ├── index.module.css
│   │   │       ├── index.tsx
│   │   │       └── markdown-page.md
│   │   ├── static/
│   │   │   └── .nojekyll
│   │   └── tsconfig.json
│   ├── eoas/
│   │   ├── .eslintignore
│   │   ├── .eslintrc.js
│   │   ├── .gitignore
│   │   ├── .prettierrc
│   │   ├── README.md
│   │   ├── package.json
│   │   ├── src/
│   │   │   ├── commands/
│   │   │   │   ├── generate-certs.ts
│   │   │   │   ├── init.ts
│   │   │   │   ├── publish.ts
│   │   │   │   ├── republish.ts
│   │   │   │   └── rollback.ts
│   │   │   ├── index.d.ts
│   │   │   └── lib/
│   │   │       ├── assets.ts
│   │   │       ├── auth.ts
│   │   │       ├── channel.ts
│   │   │       ├── expoConfig.ts
│   │   │       ├── fetch.ts
│   │   │       ├── log.ts
│   │   │       ├── ora.ts
│   │   │       ├── package.ts
│   │   │       ├── packageRunner.ts
│   │   │       ├── prompts.ts
│   │   │       ├── repo.ts
│   │   │       ├── runtimeVersion.ts
│   │   │       ├── utils.ts
│   │   │       ├── vcs/
│   │   │       │   ├── README.md
│   │   │       │   ├── clients/
│   │   │       │   │   ├── git.ts
│   │   │       │   │   ├── gitNoCommit.ts
│   │   │       │   │   └── noVcs.ts
│   │   │       │   ├── git.ts
│   │   │       │   ├── index.ts
│   │   │       │   ├── local.ts
│   │   │       │   └── vcs.ts
│   │   │       └── workflow.ts
│   │   └── tsconfig.json
│   ├── example-app/
│   │   ├── .eslintrc.json
│   │   ├── .gitignore
│   │   ├── .prettierignore
│   │   ├── .prettierrc
│   │   ├── README.md
│   │   ├── app/
│   │   │   ├── +not-found.tsx
│   │   │   ├── _layout.tsx
│   │   │   └── index.tsx
│   │   ├── app.config.ts
│   │   ├── app.json
│   │   ├── components/
│   │   │   ├── LogViewer.tsx
│   │   │   ├── ThemedText.tsx
│   │   │   ├── ThemedView.tsx
│   │   │   ├── __tests__/
│   │   │   │   ├── ThemedText-test.tsx
│   │   │   │   └── __snapshots__/
│   │   │   │       └── ThemedText-test.tsx.snap
│   │   │   └── ui/
│   │   │       ├── IconSymbol.ios.tsx
│   │   │       ├── IconSymbol.tsx
│   │   │       ├── TabBarBackground.ios.tsx
│   │   │       └── TabBarBackground.tsx
│   │   ├── constants/
│   │   │   └── Colors.ts
│   │   ├── hooks/
│   │   │   ├── useColorScheme.ts
│   │   │   ├── useColorScheme.web.ts
│   │   │   └── useThemeColor.ts
│   │   ├── package.json
│   │   ├── scripts/
│   │   │   ├── network_security_config.xml
│   │   │   ├── reset-project.js
│   │   │   └── trust_local_certs.js
│   │   └── tsconfig.json
│   └── example-app-runtime-switch/
│       ├── .eslintrc.json
│       ├── .gitignore
│       ├── .prettierignore
│       ├── .prettierrc
│       ├── README.md
│       ├── app/
│       │   ├── +not-found.tsx
│       │   ├── _layout.tsx
│       │   └── index.tsx
│       ├── app.config.ts
│       ├── app.json
│       ├── components/
│       │   ├── LogViewer.tsx
│       │   ├── ThemedText.tsx
│       │   ├── ThemedView.tsx
│       │   ├── __tests__/
│       │   │   ├── ThemedText-test.tsx
│       │   │   └── __snapshots__/
│       │   │       └── ThemedText-test.tsx.snap
│       │   └── ui/
│       │       ├── IconSymbol.ios.tsx
│       │       ├── IconSymbol.tsx
│       │       ├── TabBarBackground.ios.tsx
│       │       └── TabBarBackground.tsx
│       ├── constants/
│       │   └── Colors.ts
│       ├── hooks/
│       │   ├── useColorScheme.ts
│       │   ├── useColorScheme.web.ts
│       │   └── useThemeColor.ts
│       ├── package.json
│       ├── scripts/
│       │   ├── network_security_config.xml
│       │   ├── reset-project.js
│       │   └── trust_local_certs.js
│       └── tsconfig.json
├── cmd/
│   └── api/
│       └── main.go
├── config/
│   ├── config.go
│   └── config_test.go
├── docker-compose.yml
├── go.mod
├── go.sum
├── grafana/
│   └── dashboard.json
├── grafana-dashboard.json
├── helm/
│   ├── .helmignore
│   ├── Chart.yaml
│   ├── helm_template_test.go
│   ├── templates/
│   │   ├── NOTES.txt
│   │   ├── _helpers.tpl
│   │   ├── deployment.yaml
│   │   ├── hpa.yaml
│   │   ├── ingress.yaml
│   │   ├── service.yaml
│   │   ├── serviceaccount.yaml
│   │   └── tests/
│   │       └── test-connection.yaml
│   └── values.yaml
├── internal/
│   ├── assets/
│   │   └── assets.go
│   ├── auth/
│   │   └── auth.go
│   ├── branch/
│   │   ├── branch.go
│   │   └── branch_test.go
│   ├── bucket/
│   │   ├── bucket.go
│   │   ├── bucket_test.go
│   │   ├── gcsBucket.go
│   │   ├── localBucket.go
│   │   ├── localBucket_test.go
│   │   └── s3Bucket.go
│   ├── cache/
│   │   ├── cache.go
│   │   ├── cache_test.go
│   │   ├── localCache.go
│   │   └── redisCache.go
│   ├── cdn/
│   │   ├── cdn.go
│   │   ├── cdn_test.go
│   │   ├── cloudfront.go
│   │   ├── gcs_direct.go
│   │   └── generic.go
│   ├── compression/
│   │   └── compression.go
│   ├── crypto/
│   │   ├── crypto.go
│   │   └── crypto_test.go
│   ├── dashboard/
│   │   └── dashboard.go
│   ├── handlers/
│   │   ├── assets_handler.go
│   │   ├── auth_handler.go
│   │   ├── dashboard_handler.go
│   │   ├── manifest_handler.go
│   │   ├── republish_handler.go
│   │   ├── rollback_handler.go
│   │   └── upload_handler.go
│   ├── helpers/
│   │   ├── auth.go
│   │   ├── headers.go
│   │   ├── string.go
│   │   └── url.go
│   ├── keyStore/
│   │   ├── awsSMKeyStorage.go
│   │   ├── environmentKeyStorage.go
│   │   ├── keyStore.go
│   │   └── localKeyStorage.go
│   ├── metrics/
│   │   ├── metrics.go
│   │   └── metrics_test.go
│   ├── middleware/
│   │   ├── auth_middleware.go
│   │   ├── cors_middleware.go
│   │   └── logging_middleware.go
│   ├── migration/
│   │   ├── base.go
│   │   ├── migration.go
│   │   ├── registry.go
│   │   └── runner.go
│   ├── migrations/
│   │   ├── 20250417_persist_uuid/
│   │   │   └── 20250417_persist_uuid.go
│   │   └── migrations.go
│   ├── router/
│   │   └── router.go
│   ├── services/
│   │   ├── aws.go
│   │   ├── aws_test.go
│   │   ├── expo.go
│   │   ├── gcp.go
│   │   └── jwt.go
│   ├── types/
│   │   └── types.go
│   ├── update/
│   │   ├── prewarm.go
│   │   └── updates.go
│   └── version/
│       └── version.go
├── prometheus.yml
└── test/
    ├── assets_test.go
    ├── channel_mapping_cache_test.go
    ├── dashboard_path_traversal_test.go
    ├── dashboard_test.go
    ├── expo_multipart_parser.go
    ├── helpers.go
    ├── manifest_test.go
    ├── migrations_test.go
    ├── republish_test.go
    ├── requestUpload_test.go
    ├── rollback_test.go
    ├── test-updates/
    │   ├── branch-1/
    │   │   └── 1/
    │   │       └── 1674170951/
    │   │           ├── .check
    │   │           ├── assets/
    │   │           │   └── 4f1cb2cac2370cd5050681232e8575a8
    │   │           ├── bundles/
    │   │           │   ├── android-82adadb1fb6e489d04ad95fd79670deb.js
    │   │           │   └── ios-9d01842d6ee1224f7188971c5d397115.js
    │   │           ├── expoConfig.json
    │   │           ├── metadata.json
    │   │           └── update-metadata.json
    │   ├── branch-2/
    │   │   └── 1/
    │   │       ├── 1666304169/
    │   │       │   ├── .check
    │   │       │   ├── rollback
    │   │       │   └── update-metadata.json
    │   │       ├── 1666629107/
    │   │       │   ├── .check
    │   │       │   ├── bundles/
    │   │       │   │   ├── android-b00c4b050fca5b0ca395c7c183a2aed3.js
    │   │       │   │   └── ios-673cd0555c467df47093f49cc1b6d00f.js
    │   │       │   ├── metadata.json
    │   │       │   └── update-metadata.json
    │   │       ├── 1666629141/
    │   │       │   ├── .check
    │   │       │   ├── rollback
    │   │       │   └── update-metadata.json
    │   │       ├── 1674170951/
    │   │       │   ├── .check
    │   │       │   ├── assets/
    │   │       │   │   └── 4f1cb2cac2370cd5050681232e8575a8
    │   │       │   ├── bundles/
    │   │       │   │   ├── android-82adadb1fb6e489d04ad95fd79670deb.js
    │   │       │   │   └── ios-9d01842d6ee1224f7188971c5d397115.js
    │   │       │   ├── expoConfig.json
    │   │       │   ├── metadata.json
    │   │       │   └── update-metadata.json
    │   │       └── 1737455526/
    │   │           ├── .check
    │   │           ├── _expo/
    │   │           │   └── static/
    │   │           │       └── js/
    │   │           │           ├── android/
    │   │           │           │   └── AppEntry-3aa3d3f85ad7a30a3c33dba2de772e4f.hbc
    │   │           │           └── ios/
    │   │           │               └── AppEntry-546b83fc2035b34c5f2dbd9bb04a2478.hbc
    │   │           ├── assets/
    │   │           │   └── 4f1cb2cac2370cd5050681232e8575a8
    │   │           ├── expoConfig.json
    │   │           ├── metadata.json
    │   │           └── update-metadata.json
    │   ├── branch-3/
    │   │   └── 1/
    │   │       ├── 1666304168/
    │   │       │   ├── .check
    │   │       │   ├── assets/
    │   │       │   │   └── 4f1cb2cac2370cd5050681232e8575a8
    │   │       │   ├── bundles/
    │   │       │   │   ├── android-82adadb1fb6e489d04ad95fd79670deb.js
    │   │       │   │   └── ios-9d01842d6ee1224f7188971c5d397115.js
    │   │       │   ├── expoConfig.json
    │   │       │   ├── metadata.json
    │   │       │   └── update-metadata.json
    │   │       └── 1666304169/
    │   │           ├── .check
    │   │           ├── rollback
    │   │           └── update-metadata.json
    │   └── branch-4/
    │       └── 1/
    │           ├── 1674170951/
    │           │   ├── .check
    │           │   ├── assets/
    │           │   │   └── 4f1cb2cac2370cd5050681232e8575a8
    │           │   ├── bundles/
    │           │   │   ├── android-82adadb1fb6e489d04ad95fd79670deb.js
    │           │   │   └── ios-9d01842d6ee1224f7188971c5d397115.js
    │           │   ├── expoConfig.json
    │           │   ├── metadata.json
    │           │   └── update-metadata.json
    │           └── 1674170952/
    │               ├── assets/
    │               │   └── 4f1cb2cac2370cd5050681232e8575a8
    │               ├── bundles/
    │               │   └── android-82adadb1fb6e489d04ad95fd79670deb.js
    │               ├── expoConfig.json
    │               ├── metadata.json
    │               └── update-metadata.json
    └── url_encoding_test.go
Download .txt
Showing preview only (1,530K chars total). Download the full file or copy to clipboard to get everything.
SYMBOL INDEX (16156 symbols across 156 files)

FILE: apps/dashboard/src/App.tsx
  function withLayout (line 12) | function withLayout(children: ReactNode) {

FILE: apps/dashboard/src/components/Combobox/index.tsx
  type ComboboxProps (line 18) | interface ComboboxProps {
  function Combobox (line 26) | function Combobox(props: ComboboxProps) {

FILE: apps/dashboard/src/components/DataTable/index.tsx
  type DataTableProps (line 22) | interface DataTableProps<TData, TValue> {
  function DataTable (line 30) | function DataTable<TData, TValue>({

FILE: apps/dashboard/src/components/UpdateDetailsSheet/index.tsx
  type Update (line 16) | interface Update {
  type UpdateDetailsRef (line 24) | type UpdateDetailsRef = {
  type Props (line 164) | type Props = {

FILE: apps/dashboard/src/components/app-sidebar.tsx
  function AppSidebar (line 39) | function AppSidebar() {

FILE: apps/dashboard/src/components/ui/badge.tsx
  type BadgeProps (line 26) | interface BadgeProps
  function Badge (line 30) | function Badge({ className, variant, ...props }: BadgeProps) {

FILE: apps/dashboard/src/components/ui/button.tsx
  type ButtonProps (line 34) | interface ButtonProps

FILE: apps/dashboard/src/components/ui/form.tsx
  type FormFieldContextValue (line 18) | type FormFieldContextValue<
  type FormItemContextValue (line 63) | type FormItemContextValue = {

FILE: apps/dashboard/src/components/ui/sheet.tsx
  type SheetContentProps (line 52) | interface SheetContentProps

FILE: apps/dashboard/src/components/ui/sidebar.tsx
  constant SIDEBAR_COOKIE_NAME (line 15) | const SIDEBAR_COOKIE_NAME = 'sidebar_state';
  constant SIDEBAR_COOKIE_MAX_AGE (line 16) | const SIDEBAR_COOKIE_MAX_AGE = 60 * 60 * 24 * 7;
  constant SIDEBAR_WIDTH (line 17) | const SIDEBAR_WIDTH = '16rem';
  constant SIDEBAR_WIDTH_MOBILE (line 18) | const SIDEBAR_WIDTH_MOBILE = '18rem';
  constant SIDEBAR_WIDTH_ICON (line 19) | const SIDEBAR_WIDTH_ICON = '3rem';
  constant SIDEBAR_KEYBOARD_SHORTCUT (line 20) | const SIDEBAR_KEYBOARD_SHORTCUT = 'b';
  type SidebarContext (line 22) | type SidebarContext = {
  function useSidebar (line 34) | function useSidebar() {

FILE: apps/dashboard/src/components/ui/skeleton.tsx
  function Skeleton (line 3) | function Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivE...

FILE: apps/dashboard/src/components/ui/toast.tsx
  type ToastProps (line 111) | type ToastProps = React.ComponentPropsWithoutRef<typeof Toast>;
  type ToastActionElement (line 113) | type ToastActionElement = React.ReactElement<typeof ToastAction>;

FILE: apps/dashboard/src/components/ui/toaster.tsx
  function Toaster (line 11) | function Toaster() {

FILE: apps/dashboard/src/hooks/use-mobile.tsx
  constant MOBILE_BREAKPOINT (line 3) | const MOBILE_BREAKPOINT = 768
  function useIsMobile (line 5) | function useIsMobile() {

FILE: apps/dashboard/src/hooks/use-toast.ts
  constant TOAST_LIMIT (line 8) | const TOAST_LIMIT = 1;
  constant TOAST_REMOVE_DELAY (line 9) | const TOAST_REMOVE_DELAY = 1000000;
  type ToasterToast (line 11) | type ToasterToast = ToastProps & {
  function genId (line 28) | function genId() {
  type ActionType (line 33) | type ActionType = typeof actionTypes;
  type Action (line 35) | type Action =
  type State (line 53) | interface State {
  function dispatch (line 132) | function dispatch(action: Action) {
  type Toast (line 139) | type Toast = Omit<ToasterToast, 'id'>;
  function toast (line 141) | function toast({ ...props }: Toast) {
  function useToast (line 170) | function useToast() {

FILE: apps/dashboard/src/lib/api.ts
  class ApiClient (line 3) | class ApiClient {
    method constructor (line 6) | constructor() {
    method populateHeaders (line 14) | private populateHeaders(headers: Headers) {
    method request (line 20) | private async request<T>(endpoint: string, options: RequestInit = {}):...
    method refreshTokens (line 39) | private async refreshTokens(refreshToken: string) {
    method login (line 64) | public async login(password: string) {
    method updateChannelBranchMapping (line 73) | public async updateChannelBranchMapping(
    method getChannels (line 85) | public async getChannels() {
    method getBranches (line 97) | public async getBranches() {
    method getRuntimeVersions (line 108) | public async getRuntimeVersions(branch: string) {
    method getUpdates (line 120) | public async getUpdates(branch: string, runtimeVersion: string) {
    method getUpdateDetails (line 134) | public async getUpdateDetails(branch: string, runtimeVersion: string, ...
    method getSettings (line 148) | public async getSettings() {

FILE: apps/dashboard/src/lib/utils.ts
  function cn (line 4) | function cn(...inputs: ClassValue[]) {

FILE: apps/docs/src/components/BrowserWindow/index.tsx
  type Props (line 6) | interface Props {
  function BrowserWindow (line 14) | function BrowserWindow({

FILE: apps/docs/src/components/HomepageFeatures/index.tsx
  type FeatureItem (line 6) | type FeatureItem = {
  function Feature (line 39) | function Feature({title, description}: FeatureItem) {
  function HomepageFeatures (line 50) | function HomepageFeatures(): ReactNode {

FILE: apps/docs/src/pages/index.tsx
  function HomepageHeader (line 11) | function HomepageHeader() {
  function Home (line 33) | function Home(): ReactNode {

FILE: apps/eoas/src/commands/generate-certs.ts
  class GenerateCerts (line 14) | class GenerateCerts extends Command {
    method run (line 19) | public async run(): Promise<void> {

FILE: apps/eoas/src/commands/init.ts
  class Init (line 16) | class Init extends Command {
    method run (line 21) | public async run(): Promise<void> {

FILE: apps/eoas/src/commands/publish.ts
  class Publish (line 28) | class Publish extends Command {
    method sanitizeFlags (line 82) | private sanitizeFlags(flags: any): {
    method run (line 105) | public async run(): Promise<void> {

FILE: apps/eoas/src/commands/republish.ts
  class Publish (line 13) | class Publish extends Command {
    method sanitizeFlags (line 29) | private sanitizeFlags(flags: any): {
    method run (line 38) | public async run(): Promise<void> {

FILE: apps/eoas/src/commands/rollback.ts
  class Publish (line 19) | class Publish extends Command {
    method sanitizeFlags (line 35) | private sanitizeFlags(flags: any): {
    method run (line 44) | public async run(): Promise<void> {

FILE: apps/eoas/src/lib/assets.ts
  type Metadata (line 28) | type Metadata = {
  type AssetToUpload (line 36) | interface AssetToUpload {
  function loadMetadata (line 42) | function loadMetadata(distRoot: string): Metadata {
  function computeFilesRequests (line 72) | function computeFilesRequests(
  type RequestUploadUrlItem (line 95) | interface RequestUploadUrlItem {
  function requestUploadUrls (line 101) | async function requestUploadUrls({

FILE: apps/eoas/src/lib/auth.ts
  type ExpoCredentials (line 4) | interface ExpoCredentials {
  type SessionData (line 8) | type SessionData = {
  function dotExpoHomeDirectory (line 15) | function dotExpoHomeDirectory(): string {
  function getStateJsonPath (line 34) | function getStateJsonPath(): string {
  function getExpoSessionData (line 38) | function getExpoSessionData(): SessionData | null {
  function retrieveExpoCredentials (line 48) | function retrieveExpoCredentials(): ExpoCredentials {
  function getAuthExpoHeaders (line 55) | function getAuthExpoHeaders(credentials: ExpoCredentials): Record<string...

FILE: apps/eoas/src/lib/channel.ts
  function resolveReleaseChannelDynamicallyFromBranch (line 4) | async function resolveReleaseChannelDynamicallyFromBranch(

FILE: apps/eoas/src/lib/expoConfig.ts
  type RequestedPlatform (line 14) | enum RequestedPlatform {
  type PublicExpoConfig (line 20) | type PublicExpoConfig = Omit<
  type ExpoConfigOptions (line 29) | interface ExpoConfigOptions {
  type ExpoConfigOptionsInternal (line 36) | interface ExpoConfigOptionsInternal extends ExpoConfigOptions {
  function getExpoConfigInternalAsync (line 42) | async function getExpoConfigInternalAsync(
  function getPrivateExpoConfigAsync (line 118) | async function getPrivateExpoConfigAsync(
  function ensureExpoConfigExists (line 126) | function ensureExpoConfigExists(projectDir: string): void {
  function isUsingStaticExpoConfig (line 134) | function isUsingStaticExpoConfig(projectDir: string): boolean {
  function getPublicExpoConfigAsync (line 139) | async function getPublicExpoConfigAsync(
  function getExpoConfigUpdateUrl (line 148) | function getExpoConfigUpdateUrl(config: ExpoConfig): string | undefined {
  function createOrModifyExpoConfigAsync (line 152) | async function createOrModifyExpoConfigAsync(
  function updateObjectExpression (line 223) | function updateObjectExpression(
  function createValueNode (line 247) | function createValueNode(j: typeof jscodeshift, value: any): any {
  function stringifyWithEnv (line 266) | function stringifyWithEnv(obj: Record<string, any>): string {
  function resolveServerUrl (line 270) | async function resolveServerUrl(config: ExpoConfig): Promise<string> {

FILE: apps/eoas/src/lib/fetch.ts
  function fetchWithRetries (line 7) | async function fetchWithRetries(url: string, options: RequestInit): Prom...

FILE: apps/eoas/src/lib/log.ts
  type Color (line 8) | type Color = (...text: string[]) => string;
  class Log (line 10) | class Log {
    method log (line 13) | public static log(...args: any[]): void {
    method newLine (line 17) | public static newLine(): void {
    method addNewLineIfNone (line 21) | public static addNewLineIfNone(): void {
    method error (line 27) | public static error(...args: any[]): void {
    method warn (line 31) | public static warn(...args: any[]): void {
    method debug (line 35) | public static debug(...args: any[]): void {
    method gray (line 41) | public static gray(...args: any[]): void {
    method warnDeprecatedFlag (line 45) | public static warnDeprecatedFlag(flag: string, message: string): void {
    method fail (line 49) | public static fail(message: string): void {
    method succeed (line 53) | public static succeed(message: string): void {
    method withTick (line 57) | public static withTick(...args: any[]): void {
    method withInfo (line 61) | public static withInfo(...args: any[]): void {
    method consoleLog (line 65) | private static consoleLog(...args: any[]): void {
    method withTextColor (line 71) | private static withTextColor(args: any[], chalkColor: Color): string[] {
    method updateIsLastLineNewLine (line 76) | private static updateIsLastLineNewLine(args: any[]): void {
  function link (line 96) | function link(
  function learnMore (line 114) | function learnMore(

FILE: apps/eoas/src/lib/ora.ts
  function ora (line 27) | function ora(options?: Options | string): Ora {

FILE: apps/eoas/src/lib/package.ts
  function isExpoInstalled (line 3) | function isExpoInstalled(projectDir: string): boolean {

FILE: apps/eoas/src/lib/packageRunner.ts
  constant DEFAULT_PACKAGE_RUNNER (line 4) | const DEFAULT_PACKAGE_RUNNER = 'npx';
  constant VALID_RUNNER_RE (line 6) | const VALID_RUNNER_RE = /^[a-zA-Z0-9._-]+$/;
  function assertValidRunner (line 8) | function assertValidRunner(value: string, source: string): void {
  constant PACKAGE_MANAGER_RUNNERS (line 16) | const PACKAGE_MANAGER_RUNNERS: Record<string, string> = {
  function resolvePackageRunner (line 34) | function resolvePackageRunner(explicit?: string, projectDir?: string): s...
  function detectRunnerFromPackageJson (line 56) | function detectRunnerFromPackageJson(startDir: string): string | undefin...

FILE: apps/eoas/src/lib/prompts.ts
  type ExpoChoice (line 5) | interface ExpoChoice<T> extends Choice {
  function promptAsync (line 9) | async function promptAsync<T extends string = string>(
  function confirmAsync (line 27) | async function confirmAsync(
  function selectAsync (line 43) | async function selectAsync<T>(
  function toggleConfirmAsync (line 67) | async function toggleConfirmAsync(
  function pressAnyKeyToContinueAsync (line 84) | async function pressAnyKeyToContinueAsync(): Promise<void> {

FILE: apps/eoas/src/lib/repo.ts
  function commitPromptAsync (line 8) | async function commitPromptAsync(
  function ensureRepoIsCleanAsync (line 32) | async function ensureRepoIsCleanAsync(

FILE: apps/eoas/src/lib/runtimeVersion.ts
  class ExpoUpdatesCLIModuleNotFoundError (line 11) | class ExpoUpdatesCLIModuleNotFoundError extends Error {}
  class ExpoUpdatesCLIInvalidCommandError (line 12) | class ExpoUpdatesCLIInvalidCommandError extends Error {}
  class ExpoUpdatesCLICommandFailedError (line 13) | class ExpoUpdatesCLICommandFailedError extends Error {}
  function expoUpdatesCommandAsync (line 15) | async function expoUpdatesCommandAsync(
  function getExpoUpdatesPackageVersionIfInstalledAsync (line 59) | async function getExpoUpdatesPackageVersionIfInstalledAsync(
  function isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedAsync (line 70) | async function isModernExpoUpdatesCLIWithRuntimeVersionCommandSupportedA...
  function resolveRuntimeVersionUsingCLIAsync (line 86) | async function resolveRuntimeVersionUsingCLIAsync({
  function resolveRuntimeVersionAsync (line 136) | async function resolveRuntimeVersionAsync({

FILE: apps/eoas/src/lib/utils.ts
  function isValidUpdateUrl (line 1) | function isValidUpdateUrl(updateUrl: string): boolean {

FILE: apps/eoas/src/lib/vcs/clients/git.ts
  class GitClient (line 19) | class GitClient extends Client {
    method constructor (line 20) | constructor(private readonly maybeCwdOverride?: string) {
    method ensureRepoExistsAsync (line 24) | public override async ensureRepoExistsAsync(): Promise<void> {
    method commitAsync (line 90) | public override async commitAsync({
    method isCommitRequiredAsync (line 124) | public override async isCommitRequiredAsync(): Promise<boolean> {
    method showChangedFilesAsync (line 128) | public override async showChangedFilesAsync(): Promise<void> {
    method hasUncommittedChangesAsync (line 136) | public override async hasUncommittedChangesAsync(): Promise<boolean> {
    method getRootPathAsync (line 141) | public async getRootPathAsync(): Promise<string> {
    method makeShallowCopyAsync (line 149) | public async makeShallowCopyAsync(destinationPath: string): Promise<vo...
    method getCommitHashAsync (line 195) | public override async getCommitHashAsync(): Promise<string | undefined> {
    method trackFileAsync (line 207) | public override async trackFileAsync(file: string): Promise<void> {
    method getBranchNameAsync (line 213) | public override async getBranchNameAsync(): Promise<string | null> {
    method getLastCommitMessageAsync (line 225) | public override async getLastCommitMessageAsync(): Promise<string | nu...
    method showDiffAsync (line 237) | public override async showDiffAsync(): Promise<void> {
    method isFileUntrackedAsync (line 243) | public async isFileUntrackedAsync(path: string): Promise<boolean> {
    method isFileIgnoredAsync (line 256) | public override async isFileIgnoredAsync(filePath: string): Promise<bo...
    method canGetLastCommitMessage (line 267) | public override canGetLastCommitMessage(): boolean {
  function ensureGitConfiguredAsync (line 272) | async function ensureGitConfiguredAsync({
  function isGitCaseSensitiveAsync (line 348) | async function isGitCaseSensitiveAsync(
  function setGitCaseSensitivityAsync (line 372) | async function setGitCaseSensitivityAsync(

FILE: apps/eoas/src/lib/vcs/clients/gitNoCommit.ts
  class GitNoCommitClient (line 9) | class GitNoCommitClient extends GitClient {
    method isCommitRequiredAsync (line 10) | public override async isCommitRequiredAsync(): Promise<boolean> {
    method getRootPathAsync (line 14) | public override async getRootPathAsync(): Promise<string> {
    method makeShallowCopyAsync (line 18) | public override async makeShallowCopyAsync(destinationPath: string): P...
    method isFileIgnoredAsync (line 24) | public override async isFileIgnoredAsync(filePath: string): Promise<bo...
    method trackFileAsync (line 32) | public override async trackFileAsync(file: string): Promise<void> {

FILE: apps/eoas/src/lib/vcs/clients/noVcs.ts
  class NoVcsClient (line 4) | class NoVcsClient extends Client {
    method getRootPathAsync (line 5) | public async getRootPathAsync(): Promise<string> {
    method makeShallowCopyAsync (line 9) | public async makeShallowCopyAsync(destinationPath: string): Promise<vo...
    method isFileIgnoredAsync (line 14) | public override async isFileIgnoredAsync(filePath: string): Promise<bo...
    method canGetLastCommitMessage (line 20) | public override canGetLastCommitMessage(): boolean {

FILE: apps/eoas/src/lib/vcs/git.ts
  function isGitInstalledAsync (line 3) | async function isGitInstalledAsync(): Promise<boolean> {
  function doesGitRepoExistAsync (line 15) | async function doesGitRepoExistAsync(cwd: string | undefined): Promise<b...
  type GitStatusOptions (line 26) | interface GitStatusOptions {
  function gitStatusAsync (line 31) | async function gitStatusAsync({ showUntracked, cwd }: GitStatusOptions):...
  function getGitDiffOutputAsync (line 39) | async function getGitDiffOutputAsync(cwd: string | undefined): Promise<s...
  function gitDiffAsync (line 47) | async function gitDiffAsync({

FILE: apps/eoas/src/lib/vcs/index.ts
  constant NO_VCS_WARNING (line 8) | const NO_VCS_WARNING = `Using EAS CLI without version control system is ...
  function resolveVcsClient (line 10) | function resolveVcsClient(requireCommit: boolean = false): Client {

FILE: apps/eoas/src/lib/vcs/local.ts
  constant EASIGNORE_FILENAME (line 6) | const EASIGNORE_FILENAME = '.easignore';
  constant GITIGNORE_FILENAME (line 7) | const GITIGNORE_FILENAME = '.gitignore';
  constant DEFAULT_IGNORE (line 9) | const DEFAULT_IGNORE = `
  function getRootPath (line 14) | function getRootPath(): string {
  class Ignore (line 32) | class Ignore {
    method constructor (line 35) | constructor(private readonly rootDir: string) {}
    method initIgnoreAsync (line 37) | public async initIgnoreAsync(): Promise<void> {
    method ignores (line 67) | public ignores(relativePath: string): boolean {
  function makeShallowCopyAsync (line 77) | async function makeShallowCopyAsync(src: string, dst: string): Promise<v...

FILE: apps/eoas/src/lib/vcs/vcs.ts
  method ensureRepoExistsAsync (line 17) | public async ensureRepoExistsAsync(): Promise<void> {}
  method isCommitRequiredAsync (line 23) | public async isCommitRequiredAsync(): Promise<boolean> {
  method hasUncommittedChangesAsync (line 28) | public async hasUncommittedChangesAsync(): Promise<boolean | undefined> {
  method commitAsync (line 37) | public async commitAsync(_arg: {
  method trackFileAsync (line 48) | public async trackFileAsync(_file: string): Promise<void> {}
  method showDiffAsync (line 52) | public async showDiffAsync(): Promise<void> {}
  method showChangedFilesAsync (line 55) | public async showChangedFilesAsync(): Promise<void> {}
  method getCommitHashAsync (line 59) | public async getCommitHashAsync(): Promise<string | undefined> {
  method getBranchNameAsync (line 65) | public async getBranchNameAsync(): Promise<string | null> {
  method getLastCommitMessageAsync (line 71) | public async getLastCommitMessageAsync(): Promise<string | null> {
  method isFileIgnoredAsync (line 81) | public async isFileIgnoredAsync(_filePath: string): Promise<boolean> {

FILE: apps/eoas/src/lib/workflow.ts
  function resolveWorkflowAsync (line 8) | async function resolveWorkflowAsync(
  function resolveWorkflowPerPlatformAsync (line 38) | async function resolveWorkflowPerPlatformAsync(

FILE: apps/example-app-runtime-switch/app/+not-found.tsx
  function NotFoundScreen (line 7) | function NotFoundScreen() {

FILE: apps/example-app-runtime-switch/app/_layout.tsx
  function RootLayout (line 14) | function RootLayout() {

FILE: apps/example-app-runtime-switch/app/index.tsx
  constant RELEASE_CHANNELS (line 19) | const RELEASE_CHANNELS = ['production', 'staging']
  function HomeScreen (line 21) | function HomeScreen() {

FILE: apps/example-app-runtime-switch/components/LogViewer.tsx
  function UpdatesLogViewer (line 7) | function UpdatesLogViewer({

FILE: apps/example-app-runtime-switch/components/ThemedText.tsx
  type ThemedTextProps (line 5) | type ThemedTextProps = TextProps & {
  function ThemedText (line 11) | function ThemedText({

FILE: apps/example-app-runtime-switch/components/ThemedView.tsx
  type ThemedViewProps (line 5) | type ThemedViewProps = ViewProps & {
  function ThemedView (line 10) | function ThemedView({ style, lightColor, darkColor, ...otherProps }: The...

FILE: apps/example-app-runtime-switch/components/ui/IconSymbol.ios.tsx
  function IconSymbol (line 4) | function IconSymbol({

FILE: apps/example-app-runtime-switch/components/ui/IconSymbol.tsx
  constant MAPPING (line 9) | const MAPPING = {
  type IconSymbolName (line 23) | type IconSymbolName = keyof typeof MAPPING;
  function IconSymbol (line 30) | function IconSymbol({

FILE: apps/example-app-runtime-switch/components/ui/TabBarBackground.ios.tsx
  function BlurTabBarBackground (line 6) | function BlurTabBarBackground() {
  function useBottomTabOverflow (line 18) | function useBottomTabOverflow() {

FILE: apps/example-app-runtime-switch/components/ui/TabBarBackground.tsx
  function useBottomTabOverflow (line 4) | function useBottomTabOverflow() {

FILE: apps/example-app-runtime-switch/hooks/useColorScheme.web.ts
  function useColorScheme (line 7) | function useColorScheme() {

FILE: apps/example-app-runtime-switch/hooks/useThemeColor.ts
  function useThemeColor (line 9) | function useThemeColor(

FILE: apps/example-app-runtime-switch/scripts/trust_local_certs.js
  function setCustomConfigAsync (line 16) | async function setCustomConfigAsync(config, androidManifest) {

FILE: apps/example-app/app/+not-found.tsx
  function NotFoundScreen (line 7) | function NotFoundScreen() {

FILE: apps/example-app/app/_layout.tsx
  function RootLayout (line 14) | function RootLayout() {

FILE: apps/example-app/app/index.tsx
  function HomeScreen (line 18) | function HomeScreen() {

FILE: apps/example-app/components/LogViewer.tsx
  function UpdatesLogViewer (line 7) | function UpdatesLogViewer({

FILE: apps/example-app/components/ThemedText.tsx
  type ThemedTextProps (line 5) | type ThemedTextProps = TextProps & {
  function ThemedText (line 11) | function ThemedText({

FILE: apps/example-app/components/ThemedView.tsx
  type ThemedViewProps (line 5) | type ThemedViewProps = ViewProps & {
  function ThemedView (line 10) | function ThemedView({ style, lightColor, darkColor, ...otherProps }: The...

FILE: apps/example-app/components/ui/IconSymbol.ios.tsx
  function IconSymbol (line 4) | function IconSymbol({

FILE: apps/example-app/components/ui/IconSymbol.tsx
  constant MAPPING (line 9) | const MAPPING = {
  type IconSymbolName (line 23) | type IconSymbolName = keyof typeof MAPPING;
  function IconSymbol (line 30) | function IconSymbol({

FILE: apps/example-app/components/ui/TabBarBackground.ios.tsx
  function BlurTabBarBackground (line 6) | function BlurTabBarBackground() {
  function useBottomTabOverflow (line 18) | function useBottomTabOverflow() {

FILE: apps/example-app/components/ui/TabBarBackground.tsx
  function useBottomTabOverflow (line 4) | function useBottomTabOverflow() {

FILE: apps/example-app/hooks/useColorScheme.web.ts
  function useColorScheme (line 7) | function useColorScheme() {

FILE: apps/example-app/hooks/useThemeColor.ts
  function useThemeColor (line 9) | function useThemeColor(

FILE: apps/example-app/scripts/trust_local_certs.js
  function setCustomConfigAsync (line 16) | async function setCustomConfigAsync(config, androidManifest) {

FILE: cmd/api/main.go
  function init (line 17) | func init() {
  function main (line 22) | func main() {

FILE: config/config.go
  function validateStorageMode (line 12) | func validateStorageMode(storageMode string) bool {
  function GetPort (line 16) | func GetPort() string {
  function validateBucketParams (line 24) | func validateBucketParams(storageMode string) bool {
  function validateBaseUrl (line 52) | func validateBaseUrl(baseUrl string) bool {
  function IsTestMode (line 56) | func IsTestMode() bool {
  function resolveDefaultBaseUrl (line 60) | func resolveDefaultBaseUrl() string {
  function LoadConfig (line 69) | func LoadConfig() {
  function GetEnv (line 113) | func GetEnv(key string) string {

FILE: config/config_test.go
  function setup (line 10) | func setup(t *testing2.T) func() {
  function TestNotValidStorage (line 14) | func TestNotValidStorage(t *testing2.T) {
  function TestValidLocalStorage (line 21) | func TestValidLocalStorage(t *testing2.T) {
  function TestNotValidEmptyBaseUrl (line 28) | func TestNotValidEmptyBaseUrl(t *testing2.T) {
  function TestNotValidBaseUrl (line 35) | func TestNotValidBaseUrl(t *testing2.T) {
  function TestMissingBucketParamsForS3 (line 42) | func TestMissingBucketParamsForS3(t *testing2.T) {
  function TestMissingBucketParamsForLocal (line 50) | func TestMissingBucketParamsForLocal(t *testing2.T) {
  function TestValidBaseUrl (line 59) | func TestValidBaseUrl(t *testing2.T) {
  function TestNotValidConfigStorage (line 66) | func TestNotValidConfigStorage(t *testing2.T) {
  function TestValidConfig (line 89) | func TestValidConfig(t *testing2.T) {
  function TestFallbackDefaultEnv (line 101) | func TestFallbackDefaultEnv(t *testing2.T) {
  function TestNotSetEnv (line 115) | func TestNotSetEnv(t *testing2.T) {
  function TestAwsBaseEndpointSet (line 129) | func TestAwsBaseEndpointSet(t *testing2.T) {
  function TestAwsBaseEndpointNotSet (line 146) | func TestAwsBaseEndpointNotSet(t *testing2.T) {
  function TestTestMode (line 162) | func TestTestMode(t *testing2.T) {

FILE: helm/helm_template_test.go
  function TestRedisSentinelModeRendersRedisAuthAndTLSEnvVars (line 13) | func TestRedisSentinelModeRendersRedisAuthAndTLSEnvVars(t *testing.T) {
  function deploymentEnvByName (line 66) | func deploymentEnvByName(t *testing.T, manifest []byte) map[string]map[s...
  function secretKeyRefOptional (line 102) | func secretKeyRefOptional(env map[string]any) bool {
  function asMap (line 115) | func asMap(t *testing.T, value any) map[string]any {
  function asSlice (line 125) | func asSlice(t *testing.T, value any) []any {

FILE: internal/assets/assets.go
  type AssetsRequest (line 13) | type AssetsRequest struct
  type AssetsResponse (line 21) | type AssetsResponse struct
  function getAssetMetadata (line 29) | func getAssetMetadata(req AssetsRequest, returnAsset bool) (AssetsRespon...
  function HandleAssetsWithFile (line 123) | func HandleAssetsWithFile(req AssetsRequest) (AssetsResponse, error) {
  function HandleAssetsWithURL (line 157) | func HandleAssetsWithURL(req AssetsRequest, resolvedCDN cdn.CDN) (Assets...

FILE: internal/auth/auth.go
  type Auth (line 13) | type Auth struct
    method generateAuthToken (line 39) | func (a *Auth) generateAuthToken() (*string, error) {
    method generateRefreshToken (line 52) | func (a *Auth) generateRefreshToken() (*string, error) {
    method LoginWithPassword (line 65) | func (a *Auth) LoginWithPassword(password string) (*AuthResponse, erro...
    method ValidateToken (line 84) | func (a *Auth) ValidateToken(tokenString string) (*jwt.Token, error) {
    method RefreshToken (line 99) | func (a *Auth) RefreshToken(tokenString string) (*AuthResponse, error) {
  function getAdminPassword (line 17) | func getAdminPassword() string {
  function isPasswordValid (line 21) | func isPasswordValid(password string) bool {
  type AuthResponse (line 30) | type AuthResponse struct
  function NewAuth (line 35) | func NewAuth() *Auth {

FILE: internal/branch/branch.go
  function UpsertBranch (line 8) | func UpsertBranch(branch string) error {

FILE: internal/branch/branch_test.go
  function setup (line 10) | func setup(t *testing2.T) func() {
  function TestUpsertBranch (line 18) | func TestUpsertBranch(t *testing2.T) {

FILE: internal/bucket/bucket.go
  type RuntimeVersionWithStats (line 13) | type RuntimeVersionWithStats struct
  type Bucket (line 20) | type Bucket interface
  type BucketType (line 34) | type BucketType
  constant S3BucketType (line 37) | S3BucketType    BucketType = "s3"
  constant LocalBucketType (line 38) | LocalBucketType BucketType = "local"
  constant GCSBucketType (line 39) | GCSBucketType   BucketType = "gcs"
  function ResolveBucketType (line 42) | func ResolveBucketType() BucketType {
  function GetBucket (line 61) | func GetBucket() Bucket {
  function ConvertReadCloserToBytes (line 94) | func ConvertReadCloserToBytes(rc io.ReadCloser) ([]byte, error) {
  function ResetBucketInstance (line 103) | func ResetBucketInstance() {
  type FileUploadRequest (line 108) | type FileUploadRequest struct
  function RequestUploadUrlsForFileUpdates (line 114) | func RequestUploadUrlsForFileUpdates(branch string, runtimeVersion strin...

FILE: internal/bucket/bucket_test.go
  function setup (line 12) | func setup(t *testing2.T) func() {
  function TestResolveLocalBucketType (line 18) | func TestResolveLocalBucketType(t *testing2.T) {
  function TestResolveGCSBucketType (line 28) | func TestResolveGCSBucketType(t *testing2.T) {
  function TestResolveS3BucketType (line 35) | func TestResolveS3BucketType(t *testing2.T) {
  function TestConvertReadCloserToBytes (line 43) | func TestConvertReadCloserToBytes(t *testing2.T) {
  function TestErrorOnConvertReadCloserToBytes (line 52) | func TestErrorOnConvertReadCloserToBytes(t *testing2.T) {
  type ErrorReadCloser (line 67) | type ErrorReadCloser struct
    method Read (line 72) | func (e *ErrorReadCloser) Read(p []byte) (int, error) {
    method Close (line 76) | func (e *ErrorReadCloser) Close() error {
  function TestGetS3Bucket (line 80) | func TestGetS3Bucket(t *testing2.T) {
  function TestPrefixedKeyWithPrefix (line 89) | func TestPrefixedKeyWithPrefix(t *testing2.T) {
  function TestPrefixedKeyWithoutPrefix (line 94) | func TestPrefixedKeyWithoutPrefix(t *testing2.T) {
  function TestGetS3BucketWithKeyPrefix (line 99) | func TestGetS3BucketWithKeyPrefix(t *testing2.T) {
  function TestGetS3BucketKeyPrefixAlreadyHasSlash (line 112) | func TestGetS3BucketKeyPrefixAlreadyHasSlash(t *testing2.T) {
  function TestGetS3BucketWithoutKeyPrefix (line 125) | func TestGetS3BucketWithoutKeyPrefix(t *testing2.T) {
  function TestGetLocalBucket (line 137) | func TestGetLocalBucket(t *testing2.T) {
  function TestGetGCSBucket (line 147) | func TestGetGCSBucket(t *testing2.T) {

FILE: internal/bucket/gcsBucket.go
  type GCSBucket (line 22) | type GCSBucket struct
    method bucketHandle (line 26) | func (b *GCSBucket) bucketHandle(ctx context.Context) (*storage.Bucket...
    method DeleteUpdateFolder (line 37) | func (b *GCSBucket) DeleteUpdateFolder(branch, runtimeVersion, updateI...
    method GetRuntimeVersions (line 79) | func (b *GCSBucket) GetRuntimeVersions(branch string) ([]RuntimeVersio...
    method GetBranches (line 136) | func (b *GCSBucket) GetBranches() ([]string, error) {
    method GetUpdates (line 161) | func (b *GCSBucket) GetUpdates(branch string, runtimeVersion string) (...
    method GetFile (line 194) | func (b *GCSBucket) GetFile(update types.Update, assetPath string) (*t...
    method RequestUploadUrlForFileUpdate (line 217) | func (b *GCSBucket) RequestUploadUrlForFileUpdate(branch string, runti...
    method UploadFileIntoUpdate (line 229) | func (b *GCSBucket) UploadFileIntoUpdate(update types.Update, fileName...
    method CreateUpdateFrom (line 247) | func (b *GCSBucket) CreateUpdateFrom(previousUpdate *types.Update, new...
    method RetrieveMigrationHistory (line 323) | func (b *GCSBucket) RetrieveMigrationHistory() ([]string, error) {
    method ApplyMigration (line 351) | func (b *GCSBucket) ApplyMigration(migrationId string) error {
    method RemoveMigrationFromHistory (line 379) | func (b *GCSBucket) RemoveMigrationFromHistory(migrationId string) err...

FILE: internal/bucket/localBucket.go
  type LocalBucket (line 25) | type LocalBucket struct
    method DeleteUpdateFolder (line 29) | func (b *LocalBucket) DeleteUpdateFolder(branch string, runtimeVersion...
    method RequestUploadUrlForFileUpdate (line 37) | func (b *LocalBucket) RequestUploadUrlForFileUpdate(branch string, run...
    method GetUpdates (line 69) | func (b *LocalBucket) GetUpdates(branch string, runtimeVersion string)...
    method GetFile (line 95) | func (b *LocalBucket) GetFile(update types.Update, assetPath string) (...
    method GetBranches (line 125) | func (b *LocalBucket) GetBranches() ([]string, error) {
    method GetRuntimeVersions (line 142) | func (b *LocalBucket) GetRuntimeVersions(branch string) ([]RuntimeVers...
    method UploadFileIntoUpdate (line 190) | func (b *LocalBucket) UploadFileIntoUpdate(update types.Update, fileNa...
    method CreateUpdateFrom (line 246) | func (b *LocalBucket) CreateUpdateFrom(previousUpdate *types.Update, n...
    method RetrieveMigrationHistory (line 342) | func (b *LocalBucket) RetrieveMigrationHistory() ([]string, error) {
    method ApplyMigration (line 363) | func (b *LocalBucket) ApplyMigration(migrationId string) error {
    method RemoveMigrationFromHistory (line 393) | func (b *LocalBucket) RemoveMigrationFromHistory(migrationId string) e...
  function ValidateUploadTokenAndResolveFilePath (line 208) | func ValidateUploadTokenAndResolveFilePath(token string) (string, error) {
  function HandleUploadFile (line 229) | func HandleUploadFile(filePath string, body multipart.File) (bool, error) {
  function copyFile (line 322) | func copyFile(src, dst string) error {
  function copyDirParallel (line 437) | func copyDirParallel(srcDir, dstDir string) error {

FILE: internal/bucket/localBucket_test.go
  function TestGetFile_ValidAssetPath (line 13) | func TestGetFile_ValidAssetPath(t *testing.T) {
  function TestGetFile_PathTraversalBlocked (line 35) | func TestGetFile_PathTraversalBlocked(t *testing.T) {
  function TestGetFile_PathTraversalMultipleLevels (line 52) | func TestGetFile_PathTraversalMultipleLevels(t *testing.T) {
  function TestGetFile_NormalSubdirectoryAllowed (line 69) | func TestGetFile_NormalSubdirectoryAllowed(t *testing.T) {

FILE: internal/bucket/s3Bucket.go
  type S3Bucket (line 22) | type S3Bucket struct
    method prefixedKey (line 27) | func (b *S3Bucket) prefixedKey(key string) string {
    method DeleteUpdateFolder (line 31) | func (b *S3Bucket) DeleteUpdateFolder(branch, runtimeVersion, updateId...
    method GetRuntimeVersions (line 92) | func (b *S3Bucket) GetRuntimeVersions(branch string) ([]RuntimeVersion...
    method GetBranches (line 154) | func (b *S3Bucket) GetBranches() ([]string, error) {
    method GetUpdates (line 180) | func (b *S3Bucket) GetUpdates(branch string, runtimeVersion string) ([...
    method GetFile (line 213) | func (b *S3Bucket) GetFile(update types.Update, assetPath string) (*ty...
    method RequestUploadUrlForFileUpdate (line 243) | func (b *S3Bucket) RequestUploadUrlForFileUpdate(branch string, runtim...
    method UploadFileIntoUpdate (line 272) | func (b *S3Bucket) UploadFileIntoUpdate(update types.Update, fileName ...
    method CreateUpdateFrom (line 293) | func (b *S3Bucket) CreateUpdateFrom(previousUpdate *types.Update, newU...
    method RetrieveMigrationHistory (line 391) | func (b *S3Bucket) RetrieveMigrationHistory() ([]string, error) {
    method ApplyMigration (line 425) | func (b *S3Bucket) ApplyMigration(migrationId string) error {
    method RemoveMigrationFromHistory (line 474) | func (b *S3Bucket) RemoveMigrationFromHistory(migrationId string) error {

FILE: internal/cache/cache.go
  type Cache (line 9) | type Cache interface
  type CacheType (line 19) | type CacheType
  constant LocalCacheType (line 22) | LocalCacheType         CacheType = "local"
  constant RedisCacheType (line 23) | RedisCacheType         CacheType = "redis"
  constant RedisSentinelCacheType (line 24) | RedisSentinelCacheType CacheType = "redis-sentinel"
  constant defaultPrefix (line 27) | defaultPrefix = "expoopenota"
  function withPrefix (line 29) | func withPrefix(key string) string {
  function ResolveCacheType (line 37) | func ResolveCacheType() CacheType {
  function parseSentinelAddrs (line 49) | func parseSentinelAddrs(addrs string) []string {
  function GetCache (line 66) | func GetCache() Cache {

FILE: internal/cache/cache_test.go
  function TestParseSentinelAddrsTrimsWhitespaceAndDropsEmptyEntries (line 8) | func TestParseSentinelAddrsTrimsWhitespaceAndDropsEmptyEntries(t *testin...
  function TestParseSentinelAddrsReturnsEmptyForBlankInput (line 17) | func TestParseSentinelAddrsReturnsEmptyForBlankInput(t *testing.T) {

FILE: internal/cache/localCache.go
  type LocalCache (line 10) | type LocalCache struct
    method Get (line 30) | func (c *LocalCache) Get(key string) string {
    method Set (line 47) | func (c *LocalCache) Set(key string, value string, ttl *int) error {
    method Delete (line 64) | func (c *LocalCache) Delete(key string) {
    method Clear (line 75) | func (c *LocalCache) Clear() error {
    method TryLock (line 86) | func (c *LocalCache) TryLock(key string, ttl int) (bool, error) {
    method Sadd (line 110) | func (c *LocalCache) Sadd(key string, members []string, ttl *int) error {
    method Scard (line 140) | func (c *LocalCache) Scard(key string) (int64, error) {
  type CacheItem (line 17) | type CacheItem struct
  function NewLocalCache (line 22) | func NewLocalCache() *LocalCache {

FILE: internal/cache/redisCache.go
  type RedisCache (line 16) | type RedisCache struct
    method Get (line 90) | func (c *RedisCache) Get(key string) string {
    method Set (line 103) | func (c *RedisCache) Set(key string, value string, ttl *int) error {
    method Delete (line 115) | func (c *RedisCache) Delete(key string) {
    method Clear (line 122) | func (c *RedisCache) Clear() error {
    method TryLock (line 127) | func (r *RedisCache) TryLock(key string, ttl int) (bool, error) {
    method Sadd (line 133) | func (c *RedisCache) Sadd(key string, members []string, ttl *int) error {
    method Scard (line 159) | func (c *RedisCache) Scard(key string) (int64, error) {
  function buildTLSConfig (line 20) | func buildTLSConfig(caCertB64 string) *tls.Config {
  function NewRedisCache (line 40) | func NewRedisCache(host, password, port string, useTLS bool, username, c...
  function NewRedisSentinelCache (line 64) | func NewRedisSentinelCache(sentinelAddrs []string, masterName, password ...

FILE: internal/cdn/cdn.go
  type CDN (line 5) | type CDN interface
  function GetCDN (line 15) | func GetCDN() CDN {
  function ResetCDNInstance (line 36) | func ResetCDNInstance() {

FILE: internal/cdn/cdn_test.go
  function TestGetCDNReturnsGCSDirectWhenGCSConfigured (line 8) | func TestGetCDNReturnsGCSDirectWhenGCSConfigured(t *testing2.T) {
  function TestGetCDNReturnsGenericWhenGenericConfigured (line 22) | func TestGetCDNReturnsGenericWhenGenericConfigured(t *testing2.T) {

FILE: internal/cdn/cloudfront.go
  type CloudfrontCDN (line 14) | type CloudfrontCDN struct
    method isCDNAvailable (line 24) | func (c *CloudfrontCDN) isCDNAvailable() bool {
    method ComputeRedirectionURLForAsset (line 43) | func (c *CloudfrontCDN) ComputeRedirectionURLForAsset(branch, runtimeV...
  function getCloudfrontDomain (line 16) | func getCloudfrontDomain() string {
  function getCloudfrontKeyPairId (line 20) | func getCloudfrontKeyPairId() string {
  function getSigner (line 31) | func getSigner(key string) (crypto.Signer, error) {

FILE: internal/cdn/gcs_direct.go
  type GCSDirectCDN (line 10) | type GCSDirectCDN struct
    method isCDNAvailable (line 12) | func (c *GCSDirectCDN) isCDNAvailable() bool {
    method ComputeRedirectionURLForAsset (line 16) | func (c *GCSDirectCDN) ComputeRedirectionURLForAsset(branch, runtimeVe...

FILE: internal/cdn/generic.go
  type GenericCDN (line 8) | type GenericCDN struct
    method isCDNAvailable (line 10) | func (c *GenericCDN) isCDNAvailable() bool {
    method ComputeRedirectionURLForAsset (line 14) | func (c *GenericCDN) ComputeRedirectionURLForAsset(branch, runtimeVers...

FILE: internal/compression/compression.go
  function compressWithGzip (line 11) | func compressWithGzip(w http.ResponseWriter, data []byte, requestID stri...
  function compressWithBrotli (line 23) | func compressWithBrotli(w http.ResponseWriter, data []byte, requestID st...
  function ServeCompressedAsset (line 35) | func ServeCompressedAsset(w http.ResponseWriter, r *http.Request, data [...

FILE: internal/crypto/crypto.go
  function CreateHash (line 21) | func CreateHash(data []byte, hashingAlgorithm, encoding string) (string,...
  function ConvertSHA256HashToUUID (line 47) | func ConvertSHA256HashToUUID(value string) string {
  function GetBase64URLEncoding (line 60) | func GetBase64URLEncoding(encodedString string) string {
  function SignRSASHA256 (line 67) | func SignRSASHA256(data, privateKeyPEM string) (string, error) {
  function SignRSASHA1 (line 93) | func SignRSASHA1(data, privateKeyPEM string) (string, error) {

FILE: internal/crypto/crypto_test.go
  function TestCreateHash (line 15) | func TestCreateHash(t *testing.T) {
  function TestConvertSHA256HashToUUID (line 43) | func TestConvertSHA256HashToUUID(t *testing.T) {
  function TestGetBase64URLEncoding (line 58) | func TestGetBase64URLEncoding(t *testing.T) {
  function TestSignRSASHA256 (line 68) | func TestSignRSASHA256(t *testing.T) {

FILE: internal/dashboard/dashboard.go
  function IsDashboardEnabled (line 9) | func IsDashboardEnabled() bool {
  function ComputeGetRuntimeVersionsCacheKey (line 13) | func ComputeGetRuntimeVersionsCacheKey(branch string) string {
  function ComputeGetBranchesCacheKey (line 17) | func ComputeGetBranchesCacheKey() string {
  function ComputeGetChannelsCacheKey (line 21) | func ComputeGetChannelsCacheKey() string {
  function ComputeGetUpdatesCacheKey (line 25) | func ComputeGetUpdatesCacheKey(branch string, runtimeVersion string) str...
  function ComputeGetUpdateDetailsCacheKey (line 29) | func ComputeGetUpdateDetailsCacheKey(branch string, runtimeVersion strin...

FILE: internal/handlers/assets_handler.go
  function AssetsHandler (line 13) | func AssetsHandler(w http.ResponseWriter, r *http.Request) {

FILE: internal/handlers/auth_handler.go
  function LoginHandler (line 9) | func LoginHandler(w http.ResponseWriter, r *http.Request) {
  function RefreshTokenHandler (line 32) | func RefreshTokenHandler(w http.ResponseWriter, r *http.Request) {

FILE: internal/handlers/dashboard_handler.go
  type BranchMapping (line 22) | type BranchMapping struct
  type ChannelMapping (line 28) | type ChannelMapping struct
  type UpdateItem (line 35) | type UpdateItem struct
  type UpdateDetails (line 44) | type UpdateDetails struct
  type SettingsEnv (line 55) | type SettingsEnv struct
  function maskSecret (line 86) | func maskSecret(value string) string {
  function GetSettingsHandler (line 93) | func GetSettingsHandler(w http.ResponseWriter, r *http.Request) {
  function GetChannelsHandler (line 130) | func GetChannelsHandler(w http.ResponseWriter, r *http.Request) {
  function GetBranchesHandler (line 179) | func GetBranchesHandler(w http.ResponseWriter, r *http.Request) {
  function GetRuntimeVersionsHandler (line 213) | func GetRuntimeVersionsHandler(w http.ResponseWriter, r *http.Request) {
  function GetUpdateDetails (line 248) | func GetUpdateDetails(w http.ResponseWriter, r *http.Request) {
  function GetUpdatesHandler (line 304) | func GetUpdatesHandler(w http.ResponseWriter, r *http.Request) {
  function UpdateChannelBranchMappingHandler (line 377) | func UpdateChannelBranchMappingHandler(w http.ResponseWriter, r *http.Re...

FILE: internal/handlers/manifest_handler.go
  function createMultipartResponse (line 21) | func createMultipartResponse(headers map[string][]string, jsonContent in...
  function signDirectiveOrManifest (line 38) | func signDirectiveOrManifest(content interface{}, expectSignatureHeader ...
  function writeResponse (line 54) | func writeResponse(w http.ResponseWriter, writer *multipart.Writer, buf ...
  function putResponse (line 69) | func putResponse(w http.ResponseWriter, r *http.Request, content interfa...
  function putUpdateInResponse (line 93) | func putUpdateInResponse(w http.ResponseWriter, r *http.Request, lastUpd...
  function putRollbackInResponse (line 119) | func putRollbackInResponse(w http.ResponseWriter, r *http.Request, lastU...
  function putNoUpdateAvailableInResponse (line 144) | func putNoUpdateAvailableInResponse(w http.ResponseWriter, r *http.Reque...
  function ManifestHandler (line 153) | func ManifestHandler(w http.ResponseWriter, r *http.Request) {

FILE: internal/handlers/republish_handler.go
  function RepublishHandler (line 16) | func RepublishHandler(w http.ResponseWriter, r *http.Request) {

FILE: internal/handlers/rollback_handler.go
  function RollbackHandler (line 15) | func RollbackHandler(w http.ResponseWriter, r *http.Request) {

FILE: internal/handlers/upload_handler.go
  type FileNamesRequest (line 22) | type FileNamesRequest struct
  function MarkUpdateAsUploadedHandler (line 27) | func MarkUpdateAsUploadedHandler(w http.ResponseWriter, r *http.Request) {
  function RequestUploadLocalFileHandler (line 141) | func RequestUploadLocalFileHandler(w http.ResponseWriter, r *http.Reques...
  function RequestUploadUrlHandler (line 198) | func RequestUploadUrlHandler(w http.ResponseWriter, r *http.Request) {

FILE: internal/helpers/auth.go
  function GetExpoAuth (line 9) | func GetExpoAuth(r *http.Request) types.ExpoAuth {
  function GetBearerToken (line 25) | func GetBearerToken(r *http.Request) (string, error) {

FILE: internal/helpers/headers.go
  function ParseExpoExtraParams (line 7) | func ParseExpoExtraParams(header string) map[string]string {

FILE: internal/helpers/string.go
  function StringInSlice (line 3) | func StringInSlice(a string, list []string) bool {

FILE: internal/helpers/url.go
  function IsValidURL (line 5) | func IsValidURL(str string) bool {

FILE: internal/keyStore/awsSMKeyStorage.go
  type AWSSMKeysStorage (line 5) | type AWSSMKeysStorage struct
    method GetPublicExpoKey (line 11) | func (c *AWSSMKeysStorage) GetPublicExpoKey() string {
    method GetPrivateExpoKey (line 18) | func (c *AWSSMKeysStorage) GetPrivateExpoKey() string {
    method GetPrivateCloudfrontKey (line 25) | func (c *AWSSMKeysStorage) GetPrivateCloudfrontKey() string {

FILE: internal/keyStore/environmentKeyStorage.go
  type EnvironmentKeysStorage (line 9) | type EnvironmentKeysStorage struct
    method GetPublicExpoKey (line 27) | func (c *EnvironmentKeysStorage) GetPublicExpoKey() string {
    method GetPrivateExpoKey (line 31) | func (c *EnvironmentKeysStorage) GetPrivateExpoKey() string {
    method GetPrivateCloudfrontKey (line 35) | func (c *EnvironmentKeysStorage) GetPrivateCloudfrontKey() string {
  function decodeKey (line 15) | func decodeKey(key string) string {

FILE: internal/keyStore/keyStore.go
  type KeysStorageType (line 8) | type KeysStorageType
  constant AWSSecretsManager (line 11) | AWSSecretsManager KeysStorageType = "aws-secrets-manager"
  constant LocalFiles (line 12) | LocalFiles        KeysStorageType = "local-files"
  constant Environment (line 13) | Environment       KeysStorageType = "environment"
  type KeysStorage (line 16) | type KeysStorage interface
  function getStorage (line 22) | func getStorage() (KeysStorage, error) {
  function GetPublicExpoKey (line 68) | func GetPublicExpoKey() string {
  function GetPrivateExpoKey (line 76) | func GetPrivateExpoKey() string {
  function GetPrivateCloudfrontKey (line 84) | func GetPrivateCloudfrontKey() string {

FILE: internal/keyStore/localKeyStorage.go
  type LocalKeysStorage (line 9) | type LocalKeysStorage struct
    method GetPublicExpoKey (line 31) | func (c *LocalKeysStorage) GetPublicExpoKey() string {
    method GetPrivateExpoKey (line 38) | func (c *LocalKeysStorage) GetPrivateExpoKey() string {
    method GetPrivateCloudfrontKey (line 46) | func (c *LocalKeysStorage) GetPrivateCloudfrontKey() string {
  function retrieveFileContent (line 15) | func retrieveFileContent(path string) string {

FILE: internal/metrics/metrics.go
  function InitMetrics (line 46) | func InitMetrics() {
  function CleanupMetrics (line 53) | func CleanupMetrics() {
  function TrackUpdateErrorUsers (line 60) | func TrackUpdateErrorUsers(clientId, platform, runtime, branch, update s...
  function TrackActiveUser (line 81) | func TrackActiveUser(clientId, platform, runtime, branch, update string) {
  function TrackUpdateDownload (line 107) | func TrackUpdateDownload(platform, runtime, branch, update, updateType s...
  function PrometheusHandler (line 114) | func PrometheusHandler() http.Handler {
  function ResetMetricsForTest (line 118) | func ResetMetricsForTest() {

FILE: internal/metrics/metrics_test.go
  function setupMetrics (line 16) | func setupMetrics(t *testing.T) func() {
  function getMetricValue (line 26) | func getMetricValue(metricName string, labelFilter map[string]string) fl...
  function getActiveUsers (line 65) | func getActiveUsers(platform, runtime, branch, update string) float64 {
  function getTotalUpdateDownloads (line 74) | func getTotalUpdateDownloads(platform, runtime, branch, update, updateTy...
  function TestTrackUpdateDownload (line 84) | func TestTrackUpdateDownload(t *testing.T) {
  function TestTrackActiveUser (line 99) | func TestTrackActiveUser(t *testing.T) {
  function TestGetActiveUsers (line 114) | func TestGetActiveUsers(t *testing.T) {
  function TestGetTotalUpdateDownloadsByUpdate (line 139) | func TestGetTotalUpdateDownloadsByUpdate(t *testing.T) {
  function TestPrometheusHandler (line 160) | func TestPrometheusHandler(t *testing.T) {

FILE: internal/middleware/auth_middleware.go
  function AuthMiddleware (line 11) | func AuthMiddleware(next http.Handler) http.Handler {

FILE: internal/middleware/cors_middleware.go
  function CorsMiddleware (line 7) | func CorsMiddleware(next http.Handler) http.Handler {

FILE: internal/middleware/logging_middleware.go
  function redactHeaders (line 11) | func redactHeaders(headers http.Header) http.Header {
  function LoggingMiddleware (line 23) | func LoggingMiddleware(next http.Handler) http.Handler {
  type statusRecorder (line 54) | type statusRecorder struct
    method WriteHeader (line 59) | func (r *statusRecorder) WriteHeader(code int) {

FILE: internal/migration/base.go
  type BaseMigration (line 8) | type BaseMigration struct
    method ID (line 15) | func (m BaseMigration) ID() string                 { return m.Id }
    method Timestamp (line 16) | func (m BaseMigration) Timestamp() time.Time       { return m.Time }
    method Up (line 17) | func (m BaseMigration) Up(b bucket.Bucket) error   { return m.UpFunc(b) }
    method Down (line 18) | func (m BaseMigration) Down(b bucket.Bucket) error { return m.DownFunc...

FILE: internal/migration/migration.go
  type Migration (line 8) | type Migration interface

FILE: internal/migration/registry.go
  function Register (line 13) | func Register(m Migration) {
  function ClearRegisteredMigrations (line 19) | func ClearRegisteredMigrations() {
  function All (line 25) | func All() []Migration {

FILE: internal/migration/runner.go
  function RunMigrations (line 10) | func RunMigrations(b bucket.Bucket) error {
  function RollbackLastMigration (line 35) | func RollbackLastMigration(b bucket.Bucket) error {
  function RunMigrationsWithLock (line 62) | func RunMigrationsWithLock() {

FILE: internal/migrations/20250417_persist_uuid/20250417_persist_uuid.go
  function init (line 15) | func init() {

FILE: internal/router/router.go
  function HealthCheck (line 18) | func HealthCheck(w http.ResponseWriter, r *http.Request) {
  function getDashboardPath (line 22) | func getDashboardPath() string {
  function NewRouter (line 36) | func NewRouter() *mux.Router {

FILE: internal/services/aws.go
  function GetS3Client (line 21) | func GetS3Client() (*s3.Client, error) {
  function applyS3ClientOptions (line 52) | func applyS3ClientOptions(o *s3.Options) {
  function FetchSecret (line 61) | func FetchSecret(secretName string) string {

FILE: internal/services/aws_test.go
  function TestApplyS3ClientOptionsUsesPathStyleWhenEnabled (line 10) | func TestApplyS3ClientOptionsUsesPathStyleWhenEnabled(t *testing.T) {
  function TestApplyS3ClientOptionsUsesVirtualHostStyleByDefault (line 20) | func TestApplyS3ClientOptionsUsesVirtualHostStyleByDefault(t *testing.T) {

FILE: internal/services/expo.go
  type ExpoUserAccount (line 19) | type ExpoUserAccount struct
  type ExpoChannelMapping (line 25) | type ExpoChannelMapping struct
  type ExpoBranchMapping (line 30) | type ExpoBranchMapping struct
  type ExpoChannel (line 36) | type ExpoChannel struct
  type BranchMapping (line 42) | type BranchMapping struct
  function ValidateExpoAuth (line 50) | func ValidateExpoAuth(expoAuth types.ExpoAuth) (*ExpoUserAccount, error) {
  function GetExpoAccessToken (line 68) | func GetExpoAccessToken() string {
  function GetExpoAppId (line 72) | func GetExpoAppId() string {
  function SetAuthHeaders (line 76) | func SetAuthHeaders(expoAuth types.ExpoAuth, req *http.Request) {
  function makeGraphQLRequest (line 85) | func makeGraphQLRequest(ctx context.Context, query string, variables map...
  function FetchExpoChannels (line 126) | func FetchExpoChannels() ([]ExpoChannel, error) {
  function UpdateChannelBranchMapping (line 167) | func UpdateChannelBranchMapping(channelName, branchId string) error {
  function FetchExpoBranches (line 213) | func FetchExpoBranches() ([]string, error) {
  function FetchExpoUserAccountInformations (line 261) | func FetchExpoUserAccountInformations(expoAuth types.ExpoAuth) (*ExpoUse...
  function FetchSelfExpoUsername (line 316) | func FetchSelfExpoUsername() string {
  function ComputeChannelMappingCacheKey (line 334) | func ComputeChannelMappingCacheKey(channelName string) string {
  function FetchExpoChannelMapping (line 338) | func FetchExpoChannelMapping(channelName string) (*ExpoChannelMapping, e...
  function FetchExpoBranchesMapping (line 441) | func FetchExpoBranchesMapping() ([]ExpoBranchMapping, error) {
  function CreateBranch (line 535) | func CreateBranch(branch string) error {
  type ExpoApp (line 562) | type ExpoApp struct

FILE: internal/services/gcp.go
  function GetGCSClient (line 23) | func GetGCSClient() (*storage.Client, error) {
  type googleCreds (line 44) | type googleCreds struct
  function loadGoogleCreds (line 49) | func loadGoogleCreds() (*googleCreds, error) {
  function GCSSignedURL (line 68) | func GCSSignedURL(bucket, key, method, contentType string, expires time....

FILE: internal/services/jwt.go
  function GenerateJWTToken (line 5) | func GenerateJWTToken(secret string, claims jwt.Claims) (string, error) {
  function DecodeAndExtractJWTToken (line 10) | func DecodeAndExtractJWTToken(secret string, tokenString string, claims ...

FILE: internal/types/types.go
  type Asset (line 9) | type Asset struct
  type PlatformMetadata (line 14) | type PlatformMetadata struct
  type FileMetadata (line 19) | type FileMetadata struct
  type MetadataObject (line 24) | type MetadataObject struct
  type UpdateMetadata (line 30) | type UpdateMetadata struct
  type UpdateStoredMetadata (line 37) | type UpdateStoredMetadata struct
  type UpdateType (line 44) | type UpdateType
  constant NormalUpdate (line 47) | NormalUpdate UpdateType = iota
  constant Rollback (line 48) | Rollback
  type ManifestAsset (line 51) | type ManifestAsset struct
  type ExtraManifestData (line 59) | type ExtraManifestData struct
  type UpdateManifest (line 64) | type UpdateManifest struct
  type RollbackDirectiveParameters (line 74) | type RollbackDirectiveParameters struct
  type RollbackDirective (line 78) | type RollbackDirective struct
  type NoUpdateAvailableDirective (line 83) | type NoUpdateAvailableDirective struct
  type Update (line 87) | type Update struct
  type BucketFile (line 94) | type BucketFile struct
  type ExpoAuth (line 99) | type ExpoAuth struct

FILE: internal/update/prewarm.go
  function PreWarmManifestCache (line 11) | func PreWarmManifestCache(branch, runtimeVersion, platform string) {

FILE: internal/update/updates.go
  function sortUpdates (line 22) | func sortUpdates(updates []types.Update) []types.Update {
  function filterPlatformUpdates (line 29) | func filterPlatformUpdates(updates []types.Update, platform string) []ty...
  function GetAllUpdatesForRuntimeVersion (line 40) | func GetAllUpdatesForRuntimeVersion(branch string, runtimeVersion string...
  function StoreUpdateUUIDInMetadata (line 50) | func StoreUpdateUUIDInMetadata(update types.Update) error {
  function MarkUpdateAsChecked (line 79) | func MarkUpdateAsChecked(update types.Update) error {
  function IsUpdateValid (line 104) | func IsUpdateValid(Update types.Update) bool {
  function ComputeLastUpdateCacheKey (line 115) | func ComputeLastUpdateCacheKey(branch string, runtimeVersion string, pla...
  function ComputeMetadataCacheKey (line 119) | func ComputeMetadataCacheKey(branch string, runtimeVersion string, updat...
  function ComputeUpdataManifestCacheKey (line 123) | func ComputeUpdataManifestCacheKey(branch string, runtimeVersion string,...
  function ComputeManifestAssetCacheKey (line 127) | func ComputeManifestAssetCacheKey(update types.Update, assetPath string)...
  function VerifyUploadedUpdate (line 131) | func VerifyUploadedUpdate(update types.Update) error {
  function GetUpdate (line 166) | func GetUpdate(branch string, runtimeVersion string, updateId string) (*...
  function AreUpdatesIdentical (line 179) | func AreUpdatesIdentical(update1, update2 types.Update) (bool, error) {
  function GetLatestUpdateBundlePathForRuntimeVersion (line 191) | func GetLatestUpdateBundlePathForRuntimeVersion(branch string, runtimeVe...
  function GetUpdateType (line 224) | func GetUpdateType(update types.Update) types.UpdateType {
  function GetExpoConfig (line 234) | func GetExpoConfig(update types.Update) (json.RawMessage, error) {
  function GetMetadata (line 253) | func GetMetadata(update types.Update) (types.UpdateMetadata, error) {
  function BuildFinalManifestAssetUrlURL (line 306) | func BuildFinalManifestAssetUrlURL(baseURL, assetFilePath, runtimeVersio...
  function GetAssetEndpoint (line 321) | func GetAssetEndpoint() string {
  function shapeManifestAsset (line 325) | func shapeManifestAsset(update types.Update, asset *types.Asset, isLaunc...
  function appendChannelOverrideToUrl (line 389) | func appendChannelOverrideToUrl(urlStr string) string {
  function computeManifestMetadata (line 399) | func computeManifestMetadata(update types.Update) json.RawMessage {
  function ComposeUpdateManifest (line 411) | func ComposeUpdateManifest(
  function CreateRollbackDirective (line 504) | func CreateRollbackDirective(update types.Update) (types.RollbackDirecti...
  function CreateNoUpdateAvailableDirective (line 520) | func CreateNoUpdateAvailableDirective() types.NoUpdateAvailableDirective {
  function RetrieveUpdateStoredMetadata (line 526) | func RetrieveUpdateStoredMetadata(update types.Update) (*types.UpdateSto...
  function createUpdateMetadata (line 544) | func createUpdateMetadata(platform, commitHash string) (*strings.Reader,...
  function GenerateUpdateTimestamp (line 558) | func GenerateUpdateTimestamp() int64 {
  function ConvertUpdateTimestampToString (line 562) | func ConvertUpdateTimestampToString(updateId int64) string {
  function CreateRollback (line 566) | func CreateRollback(platform, commitHash, runtimeVersion, branchName str...
  function RepublishUpdate (line 600) | func RepublishUpdate(previousUpdate *types.Update, platform, commitHash ...

FILE: test/assets_test.go
  function TestEmptyAssetNameForAssets (line 22) | func TestEmptyAssetNameForAssets(t *testing.T) {
  function TestBadPlatformForAssets (line 54) | func TestBadPlatformForAssets(t *testing.T) {
  function TestMissingRuntimeVersionForAssets (line 85) | func TestMissingRuntimeVersionForAssets(t *testing.T) {
  function TestEmptyUpdatesForAssets (line 116) | func TestEmptyUpdatesForAssets(t *testing.T) {
  function TestBadRuntimeVersion (line 147) | func TestBadRuntimeVersion(t *testing.T) {
  function TestToRetrieveBundleAsset (line 178) | func TestToRetrieveBundleAsset(t *testing.T) {
  function TestToRetrieveBundleAssetWithGzipCompression (line 212) | func TestToRetrieveBundleAssetWithGzipCompression(t *testing.T) {
  function TestToRetrieveBundleAssetWithBrotliCompression (line 254) | func TestToRetrieveBundleAssetWithBrotliCompression(t *testing.T) {
  function TestToRetrievePNGAssetWithGzipCompression (line 299) | func TestToRetrievePNGAssetWithGzipCompression(t *testing.T) {
  function TestAutomaticUrlRedirectionIfCDNIsSet (line 328) | func TestAutomaticUrlRedirectionIfCDNIsSet(t *testing.T) {
  function TestPathTraversalRejectedForCDNAsset (line 348) | func TestPathTraversalRejectedForCDNAsset(t *testing.T) {
  function TestPreventCDNRedirectionHeader (line 367) | func TestPreventCDNRedirectionHeader(t *testing.T) {

FILE: test/channel_mapping_cache_test.go
  function TestChannelMappingIsCached (line 15) | func TestChannelMappingIsCached(t *testing.T) {
  function TestUpdateChannelBranchMappingInvalidatesChannelMappingCache (line 37) | func TestUpdateChannelBranchMappingInvalidatesChannelMappingCache(t *tes...

FILE: test/dashboard_path_traversal_test.go
  function TestDashboardServesStaticFile (line 11) | func TestDashboardServesStaticFile(t *testing.T) {
  function TestDashboardSPAFallback (line 22) | func TestDashboardSPAFallback(t *testing.T) {
  function TestDashboardPathTraversalBlockedJson (line 33) | func TestDashboardPathTraversalBlockedJson(t *testing.T) {
  function TestDashboardRedirectWithoutTrailingSlash (line 47) | func TestDashboardRedirectWithoutTrailingSlash(t *testing.T) {

FILE: test/dashboard_test.go
  function TestLoginDashboardNotEnabled (line 19) | func TestLoginDashboardNotEnabled(t *testing.T) {
  function TestLoginInvalidPassword (line 31) | func TestLoginInvalidPassword(t *testing.T) {
  function TestShouldRejectLoginIfAdminPasswordNotSet (line 44) | func TestShouldRejectLoginIfAdminPasswordNotSet(t *testing.T) {
  function TestLoginValidPassword (line 58) | func TestLoginValidPassword(t *testing.T) {
  function login (line 79) | func login() auth.AuthResponse {
  function TestRefreshToken (line 93) | func TestRefreshToken(t *testing.T) {
  function TestSettings (line 112) | func TestSettings(t *testing.T) {
  function TestSettingsWithoutAuth (line 137) | func TestSettingsWithoutAuth(t *testing.T) {
  function TestBranches (line 147) | func TestBranches(t *testing.T) {
  function TestBranchesWithoutAuth (line 167) | func TestBranchesWithoutAuth(t *testing.T) {
  function TestRuntimeVersionsWithoutAuth (line 177) | func TestRuntimeVersionsWithoutAuth(t *testing.T) {
  function TestRuntimeVersions (line 187) | func TestRuntimeVersions(t *testing.T) {
  function TestUpdatesWithoutAuth (line 206) | func TestUpdatesWithoutAuth(t *testing.T) {
  function TestUpdatesRegularBranch1 (line 216) | func TestUpdatesRegularBranch1(t *testing.T) {
  function TestUpdatesMultiBranch2 (line 232) | func TestUpdatesMultiBranch2(t *testing.T) {
  function TestUpdatesSomeNotValidBranch4 (line 248) | func TestUpdatesSomeNotValidBranch4(t *testing.T) {

FILE: test/expo_multipart_parser.go
  type MultipartPart (line 21) | type MultipartPart struct
  function ParseMultipartMixedResponse (line 29) | func ParseMultipartMixedResponse(contentTypeHeader string, bodyBuffer []...
  function IsMultipartPartWithName (line 80) | func IsMultipartPartWithName(part MultipartPart, name string) bool {
  function ValidateSignatureHeader (line 84) | func ValidateSignatureHeader(signature string, content string) bool {

FILE: test/helpers.go
  function setup (line 20) | func setup(t *testing.T) func() {
  function GlobalBeforeEach (line 31) | func GlobalBeforeEach() {
  function GlobalAfterEach (line 40) | func GlobalAfterEach(t *testing.T) {
  function findProjectRoot (line 86) | func findProjectRoot() (string, error) {
  function MockExpoChannelMapping (line 105) | func MockExpoChannelMapping(updateBranches []map[string]interface{}, upd...
  function MockExpoBranchesMappingResponse (line 119) | func MockExpoBranchesMappingResponse(updateBranches []map[string]interfa...
  function MockExpoBranchesResponse (line 133) | func MockExpoBranchesResponse(updateBranches []map[string]interface{}) (...
  function MockExpoAccountResponse (line 146) | func MockExpoAccountResponse(me map[string]interface{}) (*http.Response,...
  function StringifyBranchMapping (line 154) | func StringifyBranchMapping(branchMapping map[string]interface{}) string {
  function mockWorkingExpoResponse (line 162) | func mockWorkingExpoResponse(channelName string) {
  function mockExpoForRequestUploadUrlTest (line 236) | func mockExpoForRequestUploadUrlTest(channelName string) {
  function ComputeUploadRequestsInput (line 346) | func ComputeUploadRequestsInput(dirPath string) handlers.FileNamesRequest {
  function ChangeModTime (line 377) | func ChangeModTime(filePath string, newTime time.Time) error {
  function ChangeModTimeRecursively (line 393) | func ChangeModTimeRecursively(dir string, newTime time.Time) error {
  function SetValidConfiguration (line 410) | func SetValidConfiguration() {

FILE: test/manifest_test.go
  function TestNotValidChannelForManifest (line 19) | func TestNotValidChannelForManifest(t *testing.T) {
  function TestNotMappedChannelForManifest (line 52) | func TestNotMappedChannelForManifest(t *testing.T) {
  function TestNotValidProtocolVersionsForManifest (line 101) | func TestNotValidProtocolVersionsForManifest(t *testing.T) {
  function TestNotValidPlatformForManifest (line 119) | func TestNotValidPlatformForManifest(t *testing.T) {
  function TestNotValidRuntimeVersionForManifest (line 137) | func TestNotValidRuntimeVersionForManifest(t *testing.T) {
  function TestNotValidCertificatesForManifest (line 156) | func TestNotValidCertificatesForManifest(t *testing.T) {
  function TestNoUpdatesForManifest (line 182) | func TestNoUpdatesForManifest(t *testing.T) {
  function TestSkippingNotValidUpdatesAndCache (line 222) | func TestSkippingNotValidUpdatesAndCache(t *testing.T) {
  function TestValidRequestForStagingManifest (line 291) | func TestValidRequestForStagingManifest(t *testing.T) {
  function TestNoUpdatesResponseForManifest (line 334) | func TestNoUpdatesResponseForManifest(t *testing.T) {
  function TestRollbackResponseforManifest (line 376) | func TestRollbackResponseforManifest(t *testing.T) {
  function TestValidRequestForProductionManifest (line 464) | func TestValidRequestForProductionManifest(t *testing.T) {
  function TestEmptyRequestForAndroid (line 553) | func TestEmptyRequestForAndroid(t *testing.T) {
  function TestPreWarmManifestCache (line 640) | func TestPreWarmManifestCache(t *testing.T) {

FILE: test/migrations_test.go
  type dummyMigrationsBucket (line 12) | type dummyMigrationsBucket struct
    method DeleteUpdateFolder (line 17) | func (b *dummyMigrationsBucket) DeleteUpdateFolder(_, _, _ string) err...
    method RequestUploadUrlForFileUpdate (line 21) | func (b *dummyMigrationsBucket) RequestUploadUrlForFileUpdate(_, _, _,...
    method GetUpdates (line 25) | func (b *dummyMigrationsBucket) GetUpdates(_, _ string) ([]types.Updat...
    method GetFile (line 29) | func (b *dummyMigrationsBucket) GetFile(_ types.Update, _ string) (*ty...
    method GetBranches (line 33) | func (b *dummyMigrationsBucket) GetBranches() ([]string, error) {
    method GetRuntimeVersions (line 37) | func (b *dummyMigrationsBucket) GetRuntimeVersions(_ string) ([]bucket...
    method UploadFileIntoUpdate (line 41) | func (b *dummyMigrationsBucket) UploadFileIntoUpdate(_ types.Update, _...
    method CreateUpdateFrom (line 45) | func (b *dummyMigrationsBucket) CreateUpdateFrom(_ *types.Update, _ st...
    method RetrieveMigrationHistory (line 49) | func (b *dummyMigrationsBucket) RetrieveMigrationHistory() ([]string, ...
    method ApplyMigration (line 52) | func (b *dummyMigrationsBucket) ApplyMigration(migrationId string) err...
    method RemoveMigrationFromHistory (line 56) | func (b *dummyMigrationsBucket) RemoveMigrationFromHistory(migrationId...
  function TestShouldNotRunAppliedMigrations (line 66) | func TestShouldNotRunAppliedMigrations(t *testing.T) {
  function TestShouldRunMultipleMigrationsAsc (line 101) | func TestShouldRunMultipleMigrationsAsc(t *testing.T) {

FILE: test/republish_test.go
  function createRepublishRequest (line 20) | func createRepublishRequest(branch, runtimeVersion, headerKey, headerVal...
  function TestToRepublishRollbackWithBadBearer (line 34) | func TestToRepublishRollbackWithBadBearer(t *testing.T) {
  function copyDir (line 44) | func copyDir(src string, dst string) error {
  function TestGoodRepublish (line 83) | func TestGoodRepublish(t *testing.T) {
  function TestGoodRepublishWithoutCommitHash (line 147) | func TestGoodRepublishWithoutCommitHash(t *testing.T) {
  function TestRepublishOnBadPlatform (line 211) | func TestRepublishOnBadPlatform(t *testing.T) {
  function TestRepublishInvalidUpdate (line 233) | func TestRepublishInvalidUpdate(t *testing.T) {
  function TestRepublishWithBadUpdate (line 260) | func TestRepublishWithBadUpdate(t *testing.T) {
  function TestToRepublishARollback (line 282) | func TestToRepublishARollback(t *testing.T) {

FILE: test/requestUpload_test.go
  function createUploadRequest (line 27) | func createUploadRequest(t *testing.T, projectRoot, branch, runtimeVersi...
  function performUpload (line 43) | func performUpload(t *testing.T, projectRoot, branch, runtimeVersion, sa...
  function markUpdateAsUploaded (line 144) | func markUpdateAsUploaded(t *testing.T, branch, runtimeVersion, updateId...
  function TestRequestUploadUrlWithoutBearer (line 154) | func TestRequestUploadUrlWithoutBearer(t *testing.T) {
  function TestRequestUploadUrlWithBadBearer (line 169) | func TestRequestUploadUrlWithBadBearer(t *testing.T) {
  function TestRequestUploadUrlWithoutRuntimeVersion (line 184) | func TestRequestUploadUrlWithoutRuntimeVersion(t *testing.T) {
  function TestRequestUploadUrlWithBadRequestBody (line 210) | func TestRequestUploadUrlWithBadRequestBody(t *testing.T) {
  function TestRequestUploadUrlWithBadFilenamesType (line 234) | func TestRequestUploadUrlWithBadFilenamesType(t *testing.T) {
  function TestRequestUploadUrlWithSampleUpdate (line 258) | func TestRequestUploadUrlWithSampleUpdate(t *testing.T) {
  function TestRequestUploadUrlWithValidExpoSession (line 389) | func TestRequestUploadUrlWithValidExpoSession(t *testing.T) {
  function TestShouldPreserveCacheOnUploadRequest (line 415) | func TestShouldPreserveCacheOnUploadRequest(t *testing.T) {
  function TestRequestUploadUrlWithInvalidExpoSession (line 460) | func TestRequestUploadUrlWithInvalidExpoSession(t *testing.T) {
  function TestIdenticalUpload (line 486) | func TestIdenticalUpload(t *testing.T) {
  function TestDifferentUpload (line 515) | func TestDifferentUpload(t *testing.T) {

FILE: test/rollback_test.go
  function createRollbackRequest (line 19) | func createRollbackRequest(projectRoot, branch, runtimeVersion, headerKe...
  function TestToRollbackWithBadBearer (line 34) | func TestToRollbackWithBadBearer(t *testing.T) {
  function TestGoodRollback (line 48) | func TestGoodRollback(t *testing.T) {
  function TestGoodRollbackWithoutCommitHash (line 83) | func TestGoodRollbackWithoutCommitHash(t *testing.T) {

FILE: test/test-updates/branch-1/1/1674170951/bundles/android-82adadb1fb6e489d04ad95fd79670deb.js
  function o (line 2) | function o(){return e=Object.create(null)}
  function i (line 2) | function i(r){var t=r,n=e[t];return n&&n.isInitialized?n.publicModule.ex...
  function l (line 2) | function l(r){var n=r;if(e[n]&&e[n].importedDefault!==t)return e[n].impo...
  function u (line 2) | function u(r){var o=r;if(e[o]&&e[o].importedAll!==t)return e[o].imported...
  function d (line 2) | function d(e,t){if(!a&&r.ErrorUtils){var n;a=!0;try{n=h(e,t)}catch(e){r....
  function p (line 2) | function p(r){return{segmentId:r>>>c,localId:r&f}}
  function h (line 2) | function h(t,n){if(!n&&s.length>0){var o,a=null!==(o=v.get(t))&&void 0!=...
  function _ (line 2) | function _(r,e){return Error('Requiring module "'+r+'", which threw an e...
  function n (line 3) | function n(n,e){return n}
  function e (line 3) | function e(n){var e={};return n.forEach(function(n,r){e[n]=!0}),e}
  function r (line 3) | function r(n,r,u){if(n.formatValueCalls++,n.formatValueCalls>200)return"...
  function t (line 3) | function t(n,e){if(s(e))return n.stylize('undefined','undefined');if('st...
  function o (line 3) | function o(n){return'['+Error.prototype.toString.call(n)+']'}
  function i (line 3) | function i(n,e,r,t,o){for(var i=[],a=0,u=e.length;a<u;++a)b(e,String(a))...
  function l (line 3) | function l(n,e,t,o,i,l){var a,u,c;if((c=Object.getOwnPropertyDescriptor(...
  function a (line 3) | function a(n,e,r){return n.reduce(function(n,e){return 0,e.indexOf('\n')...
  function u (line 3) | function u(n){return'boolean'==typeof n}
  function f (line 3) | function f(n){return null===n}
  function c (line 3) | function c(n){return'number'==typeof n}
  function s (line 3) | function s(n){return void 0===n}
  function p (line 3) | function p(n){return g(n)&&'[object RegExp]'===h(n)}
  function g (line 3) | function g(n){return'object'==typeof n&&null!==n}
  function y (line 3) | function y(n){return g(n)&&'[object Date]'===h(n)}
  function d (line 3) | function d(n){return g(n)&&('[object Error]'===h(n)||n instanceof Error)}
  function v (line 3) | function v(n){return'function'==typeof n}
  function h (line 3) | function h(n){return Object.prototype.toString.call(n)}
  function b (line 3) | function b(n,e){return Object.prototype.hasOwnProperty.call(n,e)}
  function l (line 3) | function l(r){return function(){var l;l=1===arguments.length&&'string'==...
  function a (line 3) | function a(n,e){return Array.apply(null,Array(e)).map(function(){return ...
  function p (line 3) | function p(n,e){return s.join('')+n+' '+(e||'')}
  function y (line 3) | function y(n,e){var r=n.map(function(n,e){return n+a(' ',c[e]-n.length)....
  function g (line 3) | function g(){}
  function f (line 8) | function f(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function f (line 9) | function f(t){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function p (line 9) | function p(t,o){if(!o&&t&&t.__esModule)return t;if(null===t||"object"!=t...
  function u (line 10) | function u(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function c (line 11) | function c(n,t){var o=Object.keys(n);if(Object.getOwnPropertySymbols){va...
  function p (line 11) | function p(n){for(var t=1;t<arguments.length;t++){var i=null!=arguments[...
  function N (line 11) | function N(n){return!S(n)}
  function S (line 11) | function S(n){return'metadata'in n}
  function C (line 11) | function C(){var n=arguments.length>0&&void 0!==arguments[0]&&arguments[...
  method AccessibilityInfo (line 16) | get AccessibilityInfo(){return r(d[2]).default}
  method ActivityIndicator (line 16) | get ActivityIndicator(){return r(d[3])}
  method Button (line 16) | get Button(){return r(d[4])}
  method DatePickerIOS (line 16) | get DatePickerIOS(){return n('DatePickerIOS-merged',"DatePickerIOS has b...
  method DrawerLayoutAndroid (line 16) | get DrawerLayoutAndroid(){return r(d[6])}
  method FlatList (line 16) | get FlatList(){return r(d[7])}
  method Image (line 16) | get Image(){return r(d[8])}
  method ImageBackground (line 16) | get ImageBackground(){return r(d[9])}
  method InputAccessoryView (line 16) | get InputAccessoryView(){return r(d[10])}
  method KeyboardAvoidingView (line 16) | get KeyboardAvoidingView(){return r(d[11]).default}
  method MaskedViewIOS (line 16) | get MaskedViewIOS(){return n('maskedviewios-moved',"MaskedViewIOS has be...
  method Modal (line 16) | get Modal(){return r(d[13])}
  method Pressable (line 16) | get Pressable(){return r(d[14]).default}
  method ProgressBarAndroid (line 16) | get ProgressBarAndroid(){return n('progress-bar-android-moved',"Progress...
  method ProgressViewIOS (line 16) | get ProgressViewIOS(){return n('progress-view-ios-moved',"ProgressViewIO...
  method RefreshControl (line 16) | get RefreshControl(){return r(d[17])}
  method SafeAreaView (line 16) | get SafeAreaView(){return r(d[18]).default}
  method ScrollView (line 16) | get ScrollView(){return r(d[19])}
  method SectionList (line 16) | get SectionList(){return r(d[20]).default}
  method Slider (line 16) | get Slider(){return n('slider-moved',"Slider has been extracted from rea...
  method StatusBar (line 16) | get StatusBar(){return r(d[22])}
  method Switch (line 16) | get Switch(){return r(d[23]).default}
  method Text (line 16) | get Text(){return r(d[24])}
  method TextInput (line 16) | get TextInput(){return r(d[25])}
  method Touchable (line 16) | get Touchable(){return r(d[26])}
  method TouchableHighlight (line 16) | get TouchableHighlight(){return r(d[27])}
  method TouchableNativeFeedback (line 16) | get TouchableNativeFeedback(){return r(d[28])}
  method TouchableOpacity (line 16) | get TouchableOpacity(){return r(d[29])}
  method TouchableWithoutFeedback (line 16) | get TouchableWithoutFeedback(){return r(d[30])}
  method View (line 16) | get View(){return r(d[31])}
  method VirtualizedList (line 16) | get VirtualizedList(){return r(d[32])}
  method VirtualizedSectionList (line 16) | get VirtualizedSectionList(){return r(d[33])}
  method ActionSheetIOS (line 16) | get ActionSheetIOS(){return r(d[34])}
  method Alert (line 16) | get Alert(){return r(d[35])}
  method Animated (line 16) | get Animated(){return r(d[36])}
  method Appearance (line 16) | get Appearance(){return r(d[37])}
  method AppRegistry (line 16) | get AppRegistry(){return r(d[38])}
  method AppState (line 16) | get AppState(){return r(d[39])}
  method AsyncStorage (line 16) | get AsyncStorage(){return n('async-storage-moved',"AsyncStorage has been...
  method BackHandler (line 16) | get BackHandler(){return r(d[41])}
  method Clipboard (line 16) | get Clipboard(){return n('clipboard-moved',"Clipboard has been extracted...
  method DeviceInfo (line 16) | get DeviceInfo(){return r(d[43])}
  method DevSettings (line 16) | get DevSettings(){return r(d[44])}
  method Dimensions (line 16) | get Dimensions(){return r(d[45])}
  method Easing (line 16) | get Easing(){return r(d[46])}
  method findNodeHandle (line 16) | get findNodeHandle(){return r(d[47]).findNodeHandle}
  method I18nManager (line 16) | get I18nManager(){return r(d[48])}
  method ImagePickerIOS (line 16) | get ImagePickerIOS(){return n('imagePickerIOS-moved',"ImagePickerIOS has...
  method InteractionManager (line 16) | get InteractionManager(){return r(d[50])}
  method Keyboard (line 16) | get Keyboard(){return r(d[51])}
  method LayoutAnimation (line 16) | get LayoutAnimation(){return r(d[52])}
  method Linking (line 16) | get Linking(){return r(d[53])}
  method LogBox (line 16) | get LogBox(){return r(d[54])}
  method NativeDialogManagerAndroid (line 16) | get NativeDialogManagerAndroid(){return r(d[55]).default}
  method NativeEventEmitter (line 16) | get NativeEventEmitter(){return r(d[56]).default}
  method Networking (line 16) | get Networking(){return r(d[57])}
  method PanResponder (line 16) | get PanResponder(){return r(d[58])}
  method PermissionsAndroid (line 16) | get PermissionsAndroid(){return r(d[59])}
  method PixelRatio (line 16) | get PixelRatio(){return r(d[60])}
  method PushNotificationIOS (line 16) | get PushNotificationIOS(){return n('pushNotificationIOS-moved',"PushNoti...
  method Settings (line 16) | get Settings(){return r(d[62])}
  method Share (line 16) | get Share(){return r(d[63])}
  method StyleSheet (line 16) | get StyleSheet(){return r(d[64])}
  method Systrace (line 16) | get Systrace(){return r(d[65])}
  method ToastAndroid (line 16) | get ToastAndroid(){return r(d[66])}
  method TurboModuleRegistry (line 16) | get TurboModuleRegistry(){return r(d[67])}
  method UIManager (line 16) | get UIManager(){return r(d[68])}
  method unstable_batchedUpdates (line 16) | get unstable_batchedUpdates(){return r(d[47]).unstable_batchedUpdates}
  method useColorScheme (line 16) | get useColorScheme(){return r(d[69]).default}
  method useWindowDimensions (line 16) | get useWindowDimensions(){return r(d[70]).default}
  method UTFSequence (line 16) | get UTFSequence(){return r(d[71])}
  method Vibration (line 16) | get Vibration(){return r(d[72])}
  method YellowBox (line 16) | get YellowBox(){return r(d[73])}
  method DeviceEventEmitter (line 16) | get DeviceEventEmitter(){return r(d[74]).default}
  method DynamicColorIOS (line 16) | get DynamicColorIOS(){return r(d[75]).DynamicColorIOS}
  method NativeAppEventEmitter (line 16) | get NativeAppEventEmitter(){return r(d[76])}
  method NativeModules (line 16) | get NativeModules(){return r(d[77])}
  method Platform (line 16) | get Platform(){return r(d[78])}
  method PlatformColor (line 16) | get PlatformColor(){return r(d[79]).PlatformColor}
  method processColor (line 16) | get processColor(){return r(d[80])}
  method requireNativeComponent (line 16) | get requireNativeComponent(){return r(d[81])}
  method RootTagContext (line 16) | get RootTagContext(){return r(d[82]).RootTagContext}
  method unstable_enableLogBox (line 16) | get unstable_enableLogBox(){return function(){return console.warn('LogBo...
  method ColorPropType (line 16) | get ColorPropType(){t(!1,"ColorPropType has been removed from React Nati...
  method EdgeInsetsPropType (line 16) | get EdgeInsetsPropType(){t(!1,"EdgeInsetsPropType has been removed from ...
  method PointPropType (line 16) | get PointPropType(){t(!1,"PointPropType has been removed from React Nati...
  method ViewPropTypes (line 16) | get ViewPropTypes(){t(!1,"ViewPropTypes has been removed from React Nati...
  function t (line 21) | function t(){(0,u.default)(this,t),this._registry={}}
  function o (line 21) | function o(t,n){var u=t[n];return null==u&&(u=new Set,t[n]=u),u}
  function t (line 29) | function t(t,o){for(var n=0;n<o.length;n++){var p=o[n];p.enumerable=p.en...
  function c (line 31) | function c(e,n,t,r,l,a,i,u,o){var s=Array.prototype.slice.call(arguments...
  function m (line 31) | function m(e,n,t,r,l,a,i,u,o){d=!1,f=null,c.apply(g,arguments)}
  function v (line 31) | function v(e,n,t,r,l,a,i,u,o){if(m.apply(this,arguments),d){if(!d)throw ...
  function w (line 31) | function w(e,n,t){var r=e.type||"unknown-event";e.currentTarget=k(t),v(r...
  function T (line 31) | function T(e){var n=e._dispatchListeners,t=e._dispatchInstances;if(b(n))...
  function E (line 31) | function E(){return!0}
  function _ (line 31) | function _(){return!1}
  function P (line 31) | function P(e,n,t,r){for(var l in this.dispatchConfig=e,this._targetInst=...
  function R (line 31) | function R(e,n,t,r){if(this.eventPool.length){var l=this.eventPool.pop()...
  function C (line 31) | function C(e){if(!(e instanceof this))throw Error("Trying to release an ...
  function z (line 31) | function z(e){e.getPooled=R,e.eventPool=[],e.release=C}
  function n (line 31) | function n(){}
  function t (line 31) | function t(){return r.apply(this,arguments)}
  function I (line 31) | function I(e){return"topTouchStart"===e}
  function L (line 31) | function L(e){return"topTouchMove"===e}
  function Q (line 31) | function Q(e){return e.timeStamp||e.timestamp}
  function j (line 31) | function j(e){if(null==(e=e.identifier))throw Error("Touch object is mis...
  function B (line 31) | function B(e){var n=j(e),t=D[n];t?(t.touchActive=!0,t.startPageX=e.pageX...
  function H (line 31) | function H(e){var n=D[j(e)];n&&(n.touchActive=!0,n.previousPageX=n.curre...
  function O (line 31) | function O(e){var n=D[j(e)];n&&(n.touchActive=!1,n.previousPageX=n.curre...
  function Y (line 31) | function Y(e,n){if(null==n)throw Error("accumulate(...): Accumulated ite...
  function q (line 31) | function q(e,n){if(null==n)throw Error("accumulateInto(...): Accumulated...
  function $ (line 31) | function $(e,n,t){Array.isArray(e)?e.forEach(n,t):e&&n.call(t,e)}
  function K (line 31) | function K(e,n){var t=X;X=e,null!==ie.GlobalResponderHandler&&ie.GlobalR...
  function Z (line 31) | function Z(e){do{e=e.return}while(e&&5!==e.tag);return e||null}
  function ee (line 31) | function ee(e,n,t){for(var r=[];e;)r.push(e),e=Z(e);for(e=r.length;0<e--...
  function ne (line 31) | function ne(e,n){if(null===(e=e.stateNode))return null;if(null===(e=y(e)...
  function te (line 31) | function te(e,n,t){(n=ne(e,t.dispatchConfig.phasedRegistrationNames[n]))...
  function re (line 31) | function re(e){if(e&&e.dispatchConfig.registrationName){var n=e._targetI...
  function le (line 31) | function le(e){if(e&&e.dispatchConfig.phasedRegistrationNames){var n=e._...
  function ae (line 31) | function ae(e){e&&e.dispatchConfig.phasedRegistrationNames&&ee(e._target...
  function se (line 31) | function se(){if(ue)for(var e in oe){var n=oe[e],t=ue.indexOf(e);if(-1>=...
  function ce (line 31) | function ce(e,n){if(pe[e])throw Error("EventPluginRegistry: More than on...
  function he (line 31) | function he(e,n,t,r){var l=e.stateNode;if(null===l)return null;if(null==...
  function ve (line 31) | function ve(e,n,t){var r=t?b(t)?t.length:1:0;if(0<r)if(n._dispatchListen...
  function be (line 31) | function be(e,n,t){ve(e,t,n=he(e,t.dispatchConfig.phasedRegistrationName...
  function ye (line 31) | function ye(e,n,t,r){for(var l=[];e;){l.push(e);do{e=e.return}while(e&&5...
  function Se (line 31) | function Se(e){e&&e.dispatchConfig.phasedRegistrationNames&&ye(e._target...
  function ke (line 31) | function ke(e){if(e&&e.dispatchConfig.registrationName){var n=e._targetI...
  function Re (line 31) | function Re(e){return _e.get(e)||null}
  function Ce (line 31) | function Ce(e,n){return e(n)}
  function Ne (line 31) | function Ne(e,n){if(ze)return e(n);ze=!0;try{return Ce(e,n)}finally{ze=!1}}
  function Le (line 31) | function Le(e){if(e){var n=e._dispatchListeners,t=e._dispatchInstances;i...
  function Me (line 31) | function Me(e,n,t){var r=t||Ue,l=Re(e),a=null;null!=l&&(a=l.stateNode),N...
  function Ke (line 31) | function Ke(e){return null===e||"object"!=typeof e?null:"function"==type...
  function Je (line 31) | function Je(e){if(null==e)return null;if("function"==typeof e)return e.d...
  function Ze (line 31) | function Ze(e){var n=e.type;switch(e.tag){case 24:return"Cache";case 9:r...
  function en (line 31) | function en(e){var n=e,t=e;if(e.alternate)for(;n.return;)n=n.return;else...
  function nn (line 31) | function nn(e){if(en(e)!==e)throw Error("Unable to find node on an unmou...
  function tn (line 31) | function tn(e){var n=e.alternate;if(!n){if(null===(n=en(e)))throw Error(...
  function rn (line 31) | function rn(e){return null!==(e=tn(e))?ln(e):null}
  function ln (line 31) | function ln(e){if(5===e.tag||6===e.tag)return e;for(e=e.child;null!==e;)...
  function cn (line 31) | function cn(e,n){return"object"!=typeof n||null===n||u.deepDiffer(e,n,sn)}
  function dn (line 31) | function dn(e,n,t){if(b(n))for(var r=n.length;r--&&0<on;)dn(e,n[r],t);el...
  function fn (line 31) | function fn(e,n,t,r){if(!e&&n===t)return e;if(!n||!t)return t?pn(e,t,r):...
  function pn (line 31) | function pn(e,n,t){if(!n)return e;if(!b(n))return gn(e,an,n,t);for(var r...
  function hn (line 31) | function hn(e,n,t){if(!n)return e;if(!b(n))return gn(e,n,an,t);for(var r...
  function gn (line 31) | function gn(e,n,t,r){var l,a;for(a in t)if(l=r[a]){var i=n[a],u=t[a];"fu...
  function mn (line 31) | function mn(e,n){return function(){if(n&&("boolean"!=typeof e.__isMounte...
  function e (line 31) | function e(e,n){this._nativeTag=e,this._children=[],this.viewConfig=n}
  function Cn (line 31) | function Cn(e){if(Rn&&"function"==typeof Rn.onCommitFiberRoot)try{Rn.onC...
  function Mn (line 31) | function Mn(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:retur...
  function Fn (line 31) | function Fn(e,n){var t=e.pendingLanes;if(0===t)return 0;var r=0,l=e.susp...
  function Dn (line 31) | function Dn(e,n){switch(e){case 1:case 2:case 4:return n+250;case 8:case...
  function An (line 31) | function An(e){return 0!==(e=-1073741825&e.pendingLanes)?e:1073741824&e?...
  function Qn (line 31) | function Qn(){var e=Ln;return 0==(4194240&(Ln<<=1))&&(Ln=64),e}
  function jn (line 31) | function jn(e){for(var n=[],t=0;31>t;t++)n.push(e);return n}
  function Bn (line 31) | function Bn(e,n,t){e.pendingLanes|=n,536870912!==n&&(e.suspendedLanes=0,...
  function Hn (line 31) | function Hn(e,n){var t=e.pendingLanes&~n;e.pendingLanes=n,e.suspendedLan...
  function On (line 31) | function On(e,n){var t=e.entangledLanes|=n;for(e=e.entanglements;t;){var...
  function Vn (line 31) | function Vn(e){return 1<(e&=-e)?4<e?0!=(268435455&e)?16:536870912:4:1}
  function Yn (line 31) | function Yn(){throw Error("The current renderer does not support hydrati...
  function Gn (line 31) | function Gn(){var e=Xn;return 1==e%10&&(e+=2),Xn=e+2,e}
  function Kn (line 31) | function Kn(e){if("number"==typeof e)_e.delete(e),Pe.delete(e);else{var ...
  function Jn (line 31) | function Jn(e){if(0===e._children.length)return!1;var n=e._children.map(...
  function nt (line 31) | function nt(e,n,t){return n="",t&&(n=" (created by "+t+")"),"\n    in "+...
  function tt (line 31) | function tt(e,n){return e?nt(e.displayName||e.name||null,n,null):""}
  function it (line 31) | function it(e){return{current:e}}
  function ut (line 31) | function ut(e){0>at||(e.current=lt[at],lt[at]=null,at--)}
  function ot (line 31) | function ot(e,n){lt[++at]=e.current,e.current=n}
  function pt (line 31) | function pt(e,n){var t=e.type.contextTypes;if(!t)return st;var r=e.state...
  function ht (line 31) | function ht(e){return null!==(e=e.childContextTypes)&&void 0!==e}
  function gt (line 31) | function gt(){ut(dt),ut(ct)}
  function mt (line 31) | function mt(e,n,t){if(ct.current!==st)throw Error("Unexpected context fo...
  function vt (line 31) | function vt(e,n,t){var r=e.stateNode;if(n=n.childContextTypes,"function"...
  function bt (line 31) | function bt(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMerged...
  function yt (line 31) | function yt(e,n,t){var r=e.stateNode;if(!r)throw Error("Expected to have...
  function xt (line 31) | function xt(){if(!Tt&&null!==kt){Tt=!0;var e=0,n=Wn;try{var t=kt;for(Wn=...
  function Nt (line 31) | function Nt(e){for(;e===Pt;)Pt=Et[--_t],Et[_t]=null,Et[--_t]=null;for(;e...
  function Ut (line 31) | function Ut(e,n){if(St(e,n))return!0;if("object"!=typeof e||null===e||"o...
  function Mt (line 31) | function Mt(e){switch(e.tag){case 5:return nt(e.type,null,null);case 16:...
  function Ft (line 31) | function Ft(e){try{var n="";do{n+=Mt(e),e=e.return}while(e);return n}cat...
  function Dt (line 31) | function Dt(e,n){if(e&&e.defaultProps){for(var t in n=x({},n),e=e.defaul...
  function Ht (line 31) | function Ht(){Bt=jt=Qt=null}
  function Ot (line 31) | function Ot(e){var n=At.current;ut(At),e._currentValue=n}
  function Wt (line 31) | function Wt(e,n,t){for(;null!==e;){var r=e.alternate;if((e.childLanes&n)...
  function Vt (line 31) | function Vt(e,n){Qt=e,Bt=jt=null,null!==(e=e.dependencies)&&null!==e.fir...
  function Yt (line 31) | function Yt(e){var n=e._currentValue;if(Bt!==e)if(e={context:e,memoizedV...
  function Xt (line 31) | function Xt(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:...
  function Gt (line 31) | function Gt(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={base...
  function Kt (line 31) | function Kt(e,n){return{eventTime:e,lane:n,tag:0,payload:null,callback:n...
  function Jt (line 31) | function Jt(e,n){var t=e.updateQueue;null!==t&&(t=t.shared,fi(e)?(null==...
  function Zt (line 31) | function Zt(e,n,t){if(null!==(n=n.updateQueue)&&(n=n.shared,0!=(4194240&...
  function er (line 31) | function er(e,n){var t=e.updateQueue,r=e.alternate;if(null!==r&&t===(r=r...
  function nr (line 31) | function nr(e,n,t,r){var l=e.updateQueue;$t=!1;var a=l.firstBaseUpdate,i...
  function tr (line 31) | function tr(e,n,t){if(e=n.effects,n.effects=null,null!==e)for(n=0;n<e.le...
  function lr (line 31) | function lr(e,n,t,r){t=null===(t=t(r,n=e.memoizedState))||void 0===t?n:x...
  function ir (line 31) | function ir(e,n,t,r,l,a,i){return"function"==typeof(e=e.stateNode).shoul...
  function ur (line 31) | function ur(e,n,t){var r=!1,l=st,a=n.contextType;return"object"==typeof ...
  function or (line 31) | function or(e,n,t,r){e=n.state,"function"==typeof n.componentWillReceive...
  function sr (line 31) | function sr(e,n,t,r){var l=e.stateNode;l.props=t,l.state=e.memoizedState...
  function cr (line 31) | function cr(e,n,t){if(null!==(e=t.ref)&&"function"!=typeof e&&"object"!=...
  function dr (line 31) | function dr(e,n){throw e=Object.prototype.toString.call(n),Error("Object...
  function fr (line 31) | function fr(e){return(0,e._init)(e._payload)}
  function pr (line 31) | function pr(e){function n(n,t){if(e){var r=n.deletions;null===r?(n.delet...
  function Sr (line 31) | function Sr(e){if(e===mr)throw Error("Expected host context to exist. Th...
  function kr (line 31) | function kr(e,n){ot(yr,n),ot(br,e),ot(vr,mr),ut(vr),ot(vr,{isInAParentTe...
  function wr (line 31) | function wr(){ut(vr),ut(br),ut(yr)}
  function Tr (line 31) | function Tr(e){Sr(yr.current);var n=Sr(vr.current),t=e.type;t="AndroidTe...
  function xr (line 31) | function xr(e){br.current===e&&(ut(vr),ut(br))}
  function _r (line 31) | function _r(e){for(var n=e;null!==n;){if(13===n.tag){var t=n.memoizedSta...
  function Rr (line 31) | function Rr(){for(var e=0;e<Pr.length;e++)Pr[e]._workInProgressVersionPr...
  function Ar (line 31) | function Ar(){throw Error("Invalid hook call. Hooks can only be called i...
  function Qr (line 31) | function Qr(e,n){if(null===n)return!1;for(var t=0;t<n.length&&t<e.length...
  function jr (line 31) | function jr(e,n,t,r,l,a){if(Nr=a,Ir=n,n.memoizedState=null,n.updateQueue...
  function Br (line 31) | function Br(){var e={memoizedState:null,baseState:null,baseQueue:null,qu...
  function Hr (line 31) | function Hr(){if(null===Lr){var e=Ir.alternate;e=null!==e?e.memoizedStat...
  function Or (line 31) | function Or(e,n){return"function"==typeof n?n(e):n}
  function Wr (line 31) | function Wr(e){var n=Hr(),t=n.queue;if(null===t)throw Error("Should have...
  function Vr (line 31) | function Vr(e){var n=Hr(),t=n.queue;if(null===t)throw Error("Should have...
  function Yr (line 31) | function Yr(){}
  function qr (line 31) | function qr(e,n){var t=Ir,r=Hr(),l=n(),a=!St(r.memoizedState,l);if(a&&(r...
  function $r (line 31) | function $r(e,n,t){e.flags|=16384,e={getSnapshot:n,value:t},null===(n=Ir...
  function Xr (line 31) | function Xr(e,n,t,r){n.value=t,n.getSnapshot=r,Kr(n)&&ci(e,1,-1)}
  function Gr (line 31) | function Gr(e,n,t){return t(function(){Kr(n)&&ci(e,1,-1)})}
  function Kr (line 31) | function Kr(e){var n=e.getSnapshot;e=e.value;try{var t=n();return!St(e,t...
  function Jr (line 31) | function Jr(e){var n=Br();return"function"==typeof e&&(e=e()),n.memoized...
  function Zr (line 31) | function Zr(e,n,t,r){return e={tag:e,create:n,destroy:t,deps:r,next:null...
  function el (line 31) | function el(){return Hr().memoizedState}
  function nl (line 31) | function nl(e,n,t,r){var l=Br();Ir.flags|=e,l.memoizedState=Zr(1|n,t,voi...
  function tl (line 31) | function tl(e,n,t,r){var l=Hr();r=void 0===r?null:r;var a=void 0;if(null...
  function rl (line 31) | function rl(e,n){return nl(8390656,8,e,n)}
  function ll (line 31) | function ll(e,n){return tl(2048,8,e,n)}
  function al (line 31) | function al(e,n){return tl(4,2,e,n)}
  function il (line 31) | function il(e,n){return tl(4,4,e,n)}
  function ul (line 31) | function ul(e,n){return"function"==typeof n?(e=e(),n(e),function(){n(nul...
  function ol (line 31) | function ol(e,n,t){return t=null!==t&&void 0!==t?t.concat([e]):null,tl(4...
  function sl (line 31) | function sl(){}
  function cl (line 31) | function cl(e,n){var t=Hr();n=void 0===n?null:n;var r=t.memoizedState;re...
  function dl (line 31) | function dl(e,n){var t=Hr();n=void 0===n?null:n;var r=t.memoizedState;re...
  function fl (line 31) | function fl(e,n,t){return 0==(21&Nr)?(e.baseState&&(e.baseState=!1,Il=!0...
  function pl (line 31) | function pl(e,n){var t=Wn;Wn=0!==t&&4>t?t:4,e(!0);var r=zr.transition;zr...
  function hl (line 31) | function hl(){return Hr().memoizedState}
  function gl (line 31) | function gl(e,n,t){var r=si(e);t={lane:r,action:t,hasEagerState:!1,eager...
  function ml (line 31) | function ml(e,n,t){var r=si(e),l={lane:r,action:t,hasEagerState:!1,eager...
  function vl (line 31) | function vl(e){var n=e.alternate;return e===Ir||null!==n&&n===Ir}
  function bl (line 31) | function bl(e,n){Fr=Mr=!0;var t=e.pending;null===t?n.next=n:(n.next=t.ne...
  function yl (line 31) | function yl(e,n,t){fi(e)?(null===(e=n.interleaved)?(t.next=t,null===qt?q...
  function Sl (line 31) | function Sl(e,n,t){if(0!=(4194240&t)){var r=n.lanes;t|=r&=e.pendingLanes...
  function El (line 31) | function El(e,n){return{value:e,source:n,stack:Ft(n)}}
  function _l (line 31) | function _l(e,n){try{!1!==u.ReactFiberErrorDialog.showErrorDialog({compo...
  function Rl (line 31) | function Rl(e,n,t){(t=Kt(-1,t)).tag=3,t.payload={element:null};var r=n.v...
  function Cl (line 31) | function Cl(e,n,t){(t=Kt(-1,t)).tag=3;var r=e.type.getDerivedStateFromEr...
  function zl (line 31) | function zl(e,n,t){var r=e.pingCache;if(null===r){r=e.pingCache=new Pl;v...
  function Ll (line 31) | function Ll(e,n,t,r){n.child=null===e?gr(n,null,t,r):hr(n,e.child,t,r)}
  function Ul (line 31) | function Ul(e,n,t,r,l){t=t.render;var a=n.ref;return Vt(n,l),r=jr(e,n,t,...
  function Ml (line 31) | function Ml(e,n,t,r,l){if(null===e){var a=t.type;return"function"!=typeo...
  function Fl (line 31) | function Fl(e,n,t,r,l){if(null!==e){var a=e.memoizedProps;if(Ut(a,r)&&e....
  function Dl (line 31) | function Dl(e,n,t){var r=n.pendingProps,l=r.children,a=null!==e?e.memoiz...
  function Al (line 31) | function Al(e,n){var t=n.ref;(null===e&&null!==t||null!==e&&e.ref!==t)&&...
  function Ql (line 31) | function Ql(e,n,t,r,l){var a=ht(t)?ft:ct.current;return a=pt(n,a),Vt(n,l...
  function jl (line 31) | function jl(e,n,t,r,l){if(ht(t)){var a=!0;bt(n)}else a=!1;if(Vt(n,l),nul...
  function Bl (line 31) | function Bl(e,n,t,r,l,a){Al(e,n);var i=0!=(128&n.flags);if(!r&&!i)return...
  function Hl (line 31) | function Hl(e){var n=e.stateNode;n.pendingContext?mt(0,n.pendingContext,...
  function $l (line 31) | function $l(e){return{baseLanes:e,cachePool:null,transitions:null}}
  function Xl (line 31) | function Xl(e,n,t){var r,l=n.pendingProps,a=Er.current,i=!1,u=0!=(128&n....
  function Gl (line 31) | function Gl(e,n){return(n=qi({mode:"visible",children:n},e.mode,0,null))...
  function Kl (line 31) | function Kl(e,n,t,r){return null!==r&&(null===It?It=[r]:It.push(r)),hr(n...
  function Jl (line 31) | function Jl(e,n,t,r,l,a,i){if(t)return 256&n.flags?(n.flags&=-257,Kl(e,n...
  function Zl (line 31) | function Zl(e,n,t){e.lanes|=n;var r=e.alternate;null!==r&&(r.lanes|=n),W...
  function ea (line 31) | function ea(e,n,t,r,l){var a=e.memoizedState;null===a?e.memoizedState={i...
  function na (line 31) | function na(e,n,t){var r=n.pendingProps,l=r.revealOrder,a=r.tail;if(Ll(e...
  function ta (line 31) | function ta(e,n){0==(1&n.mode)&&null!==e&&(e.alternate=null,n.alternate=...
  function ra (line 31) | function ra(e,n,t){if(null!==e&&(n.dependencies=e.dependencies),Wa|=n.la...
  function la (line 31) | function la(e,n,t){switch(n.tag){case 3:Hl(n);break;case 5:Tr(n);break;c...
  function aa (line 31) | function aa(e,n){switch(e.tailMode){case"hidden":n=e.tail;for(var t=null...
  function ia (line 31) | function ia(e){var n=null!==e.alternate&&e.alternate.child===e.child,t=0...
  function ua (line 31) | function ua(e,n,t){var r=n.pendingProps;switch(Nt(n),n.tag){case 2:case ...
  function oa (line 31) | function oa(e,n){switch(Nt(n),n.tag){case 1:return ht(n.type)&&gt(),6553...
  function da (line 31) | function da(e,n){var t=e.ref;if(null!==t)if("function"==typeof t)try{t(n...
  function fa (line 31) | function fa(e,n,t){try{t()}catch(t){Ui(e,n,t)}}
  function ha (line 31) | function ha(e,n){for(ca=n;null!==ca;)if(n=(e=ca).child,0!=(1028&e.subtre...
  function ga (line 31) | function ga(e,n,t){var r=n.updateQueue;if(null!==(r=null!==r?r.lastEffec...
  function ma (line 31) | function ma(e,n){if(null!==(n=null!==(n=n.updateQueue)?n.lastEffect:null...
  function va (line 31) | function va(e){var n=e.alternate;null!==n&&(e.alternate=null,va(n)),e.ch...
  function ba (line 31) | function ba(e){return 5===e.tag||3===e.tag||4===e.tag}
  function ya (line 31) | function ya(e){e:for(;;){for(;null===e.sibling;){if(null===e.return||ba(...
  function Sa (line 31) | function Sa(e,n,t){var r=e.tag;if(5===r||6===r)if(e=e.stateNode,n){if("n...
  function ka (line 31) | function ka(e,n,t){var r=e.tag;if(5===r||6===r)if(e=e.stateNode,n){var l...
  function xa (line 31) | function xa(e,n,t){for(t=t.child;null!==t;)Ea(e,n,t),t=t.sibling}
  function Ea (line 31) | function Ea(e,n,t){if(Rn&&"function"==typeof Rn.onCommitFiberUnmount)try...
  function _a (line 31) | function _a(e){var n=e.updateQueue;if(null!==n){e.updateQueue=null;var t...
  function Pa (line 31) | function Pa(e,n){var t=n.deletions;if(null!==t)for(var r=0;r<t.length;r+...
  function Ra (line 31) | function Ra(e,n){var t=e.alternate,r=e.flags;switch(e.tag){case 0:case 1...
  function Ca (line 31) | function Ca(e){var n=e.flags;if(2&n){try{e:{for(var t=e.return;null!==t;...
  function za (line 31) | function za(e){for(ca=e;null!==ca;){var n=ca,t=n.child;if(0!=(8772&n.sub...
  function oi (line 31) | function oi(){return 0!=(6&Fa)?wn():-1!==ii?ii:ii=wn()}
  function si (line 31) | function si(e){return 0==(1&e.mode)?1:0!=(2&Fa)&&0!==Qa?Qa&-Qa:null!==Lt...
  function ci (line 31) | function ci(e,n,t){if(50<li)throw li=0,ai=null,Error("Maximum update dep...
  function di (line 31) | function di(e,n){e.lanes|=n;var t=e.alternate;for(null!==t&&(t.lanes|=n)...
  function fi (line 31) | function fi(e){return(null!==Da||null!==qt)&&0!=(1&e.mode)&&0==(2&Fa)}
  function pi (line 31) | function pi(e,n){for(var t=e.callbackNode,r=e.suspendedLanes,l=e.pingedL...
  function hi (line 31) | function hi(e,n){if(ii=-1,ui=0,0!=(6&Fa))throw Error("Should not already...
  function gi (line 31) | function gi(e,n){var t=qa;return e.current.memoizedState.isDehydrated&&(...
  function mi (line 31) | function mi(e){null===$a?$a=e:$a.push.apply($a,e)}
  function vi (line 31) | function vi(e){for(var n=e;;){if(16384&n.flags){var t=n.updateQueue;if(n...
  function bi (line 31) | function bi(e,n){for(n&=~Ya,n&=~Va,e.suspendedLanes|=n,e.pingedLanes&=~n...
  function yi (line 31) | function yi(e){if(0!=(6&Fa))throw Error("Should not already be working."...
  function Si (line 31) | function Si(){ja=Ba.current,ut(Ba)}
  function ki (line 31) | function ki(e,n){e.finishedWork=null,e.finishedLanes=0;var t=e.timeoutHa...
  function wi (line 31) | function wi(e,n){for(;;){var t=Aa;try{if(Ht(),Cr.current=kl,Mr){for(var ...
  function Ti (line 31) | function Ti(){var e=La.current;return La.current=kl,null===e?kl:e}
  function xi (line 31) | function xi(){0!==Ha&&3!==Ha&&2!==Ha||(Ha=4),null===Da||0==(268435455&Wa...
  function Ei (line 31) | function Ei(e,n){var t=Fa;Fa|=2;var r=Ti();for(Da===e&&Qa===n||(Ka=null,...
  function _i (line 31) | function _i(){for(;null!==Aa;)Ri(Aa)}
  function Pi (line 31) | function Pi(){for(;null!==Aa&&!Sn();)Ri(Aa)}
  function Ri (line 31) | function Ri(e){var n=Na(e.alternate,e,ja);e.memoizedProps=e.pendingProps...
  function Ci (line 31) | function Ci(e){var n=e;do{var t=n.alternate;if(e=n.return,0==(32768&n.fl...
  function zi (line 31) | function zi(e,n,t){var r=Wn,l=Ma.transition;try{Ma.transition=null,Wn=1,...
  function Ni (line 31) | function Ni(e,n,t,r){do{Ii()}while(null!==ti);if(0!=(6&Fa))throw Error("...
  function Ii (line 31) | function Ii(){if(null!==ti){var e=Vn(ri),n=Ma.transition,t=Wn;try{if(Ma....
  function Li (line 31) | function Li(e,n,t){Jt(e,n=Rl(e,n=El(t,n),1)),n=oi(),null!==(e=di(e,1))&&...
  function Ui (line 31) | function Ui(e,n,t){if(3===e.tag)Li(e,e,t);else for(n=e.return;null!==n;)...
  function Mi (line 31) | function Mi(e,n,t){var r=e.pingCache;null!==r&&r.delete(n),n=oi(),e.ping...
  function Fi (line 31) | function Fi(e,n){0===n&&(0==(1&e.mode)?n=1:(n=Un,0==(130023424&(Un<<=1))...
  function Di (line 31) | function Di(e){var n=e.memoizedState,t=0;null!==n&&(t=n.retryLane),Fi(e,t)}
  function Ai (line 31) | function Ai(e,n){var t=0;switch(e.tag){case 13:var r=e.stateNode,l=e.mem...
  function Qi (line 31) | function Qi(e,n){return bn(e,n)}
  function ji (line 31) | function ji(e,n,t,r){this.tag=e,this.key=t,this.sibling=this.child=this....
  function Bi (line 31) | function Bi(e,n,t,r){return new ji(e,n,t,r)}
  function Hi (line 31) | function Hi(e){return!(!(e=e.prototype)||!e.isReactComponent)}
  function Oi (line 31) | function Oi(e){if("function"==typeof e)return Hi(e)?1:0;if(void 0!==e&&n...
  function Wi (line 31) | function Wi(e,n){var t=e.alternate;return null===t?((t=Bi(e.tag,n,e.key,...
  function Vi (line 31) | function Vi(e,n,t,r,l,a){var i=2;if(r=e,"function"==typeof e)Hi(e)&&(i=1...
  function Yi (line 31) | function Yi(e,n,t,r){return(e=Bi(7,e,r,n)).lanes=t,e}
  function qi (line 31) | function qi(e,n,t,r){return(e=Bi(22,e,r,n)).elementType=Xe,e.lanes=t,e.s...
  function $i (line 31) | function $i(e,n,t){return(e=Bi(6,e,null,n)).lanes=t,e}
  function Xi (line 31) | function Xi(e,n,t){return(n=Bi(4,null!==e.children?e.children:[],e.key,n...
  function Gi (line 31) | function Gi(e,n,t,r,l){this.tag=n,this.containerInfo=e,this.finishedWork...
  function Ki (line 31) | function Ki(e,n,t){var r=3<arguments.length&&void 0!==arguments[3]?argum...
  function Ji (line 31) | function Ji(e){var n=e._reactInternals;if(void 0===n){if("function"==typ...
  function Zi (line 31) | function Zi(e,n,t,r){var l=n.current,a=oi(),i=si(l);e:if(t){t=t._reactIn...
  function eu (line 31) | function eu(e){return null==e?null:"number"==typeof e?e:e._nativeTag?e._...
  function nu (line 31) | function nu(e){console.error(e)}
  function tu (line 31) | function tu(e){var n=ru.get(e);n&&Zi(null,n,null,function(){ru.delete(e)})}
  function s (line 39) | function s(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function v (line 39) | function v(){var n;e(this,v);for(var t=arguments.length,o=new Array(t),c...
  function p (line 39) | function p(n){if(l&&!f){f=!0;try{return l(n)}catch(n){}finally{f=!1}}ret...
  function y (line 39) | function y(n,e,t){var o=r(d[7])(null==n?void 0:n.stack),c=++v,s=n.messag...
  function h (line 39) | function h(){for(var n,e=arguments.length,t=new Array(e),o=0;o<e;o++)t[o...
  function t (line 41) | function t(o,s){return m.exports=t=Object.setPrototypeOf||function(t,o){...
  function o (line 43) | function o(t){"@babel/helpers - typeof";return m.exports=o="function"==t...
  function t (line 45) | function t(o){return m.exports=t=Object.setPrototypeOf?Object.getPrototy...
  function p (line 46) | function p(s){var f="function"==typeof Map?new Map:void 0;return m.expor...
  function p (line 48) | function p(s,u,n){return o()?(m.exports=p=Reflect.construct,m.exports.__...
  function n (line 50) | function n(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function o (line 50) | function o(o){for(var c=1;c<arguments.length;c++){var u=null!=arguments[...
  function u (line 50) | function u(t){var n=[];for(var o of t.entries)if('FRAME'===o.type){var c...
  function s (line 51) | function s(s){var i=s.match(t);if(i)return{type:'FRAME',functionName:i[1...
  function t (line 52) | function t(t){var o=l.exec(t);if(!o)return null;var c=o[2]&&0===o[2].ind...
  function c (line 52) | function c(l){var u=o.exec(l);return u?{file:u[2],methodName:u[1]||n,arg...
  function f (line 52) | function f(l){var u=s.exec(l);if(!u)return null;var t=u[3]&&u[3].indexOf...
  function p (line 52) | function p(l){var u=b.exec(l);return u?{file:u[3],methodName:u[1]||n,arg...
  function h (line 52) | function h(l){var u=x.exec(l);return u?{file:u[2],methodName:u[1]||n,arg...
  function o (line 53) | function o(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function l (line 54) | function l(n){if(!0!==g.RN$Bridgeless){var t=u[n];if(null!=t)return t}re...
  function u (line 55) | function u(t,u){if(!t)return null;var l=n(t,5),c=l[0],v=l[1],h=l[2],y=l[...
  function l (line 55) | function l(n,t){o(g.nativeRequireModuleConfig,"Can't lazily create modul...
  function f (line 55) | function f(n,u,l){var f=null;return(f='promise'===l?function(){for(var o...
  function s (line 55) | function s(n,t){return-1!==n.indexOf(t)}
  function c (line 55) | function c(n,t){return Object.assign(t,n||{})}
  function c (line 61) | function c(){l(this,c),this._lazyCallableModules={},this._queue=[[],[],[...
  function n (line 63) | function n(t){var n=t.maxDepth,f=void 0===n?Number.POSITIVE_INFINITY:n,u...
  function s (line 65) | function s(u){b=u,l=!0,Object.defineProperty(t,n,{value:u,configurable:!...
  function o (line 67) | function o(o,t,n){var c=Object.getOwnPropertyDescriptor(o,t),b=c||{},f=b...
  function l (line 69) | function l(t){var o=new n(n._D);return o._y=1,o._z=t,o}
  function h (line 69) | function h(n){return{status:'fulfilled',value:n}}
  function p (line 69) | function p(n){return{status:'rejected',reason:n}}
  function s (line 69) | function s(t){if(t&&('object'==typeof t||'function'==typeof t)){if(t ins...
  function v (line 69) | function v(n){if('function'==typeof AggregateError)return new AggregateE...
  function i (line 69) | function i(c,l){if(l&&('object'==typeof l||'function'==typeof l)){if(l i...
  function l (line 69) | function l(n){i||(i=!0,o(n))}
  function h (line 69) | function h(n){c.push(n),c.length===u.length&&f(v(c))}
  function n (line 70) | function n(){}
  function i (line 70) | function i(n){try{return n.then}catch(n){return t=n,o}}
  function u (line 70) | function u(n,i){try{return n(i)}catch(n){return t=n,o}}
  function f (line 70) | function f(n,i,u){try{n(i,u)}catch(n){return t=n,o}}
  function c (line 70) | function c(t){if('object'!=typeof this)throw new TypeError('Promises mus...
  function _ (line 70) | function _(t,o,i){return new t.constructor(function(u,f){var _=new c(n);...
  function s (line 70) | function s(n,t){for(;3===n._y;)n=n._z;if(c._B&&c._B(n),0===n._y)return 0...
  function l (line 70) | function l(n,i){setImmediate(function(){var f=1===n._y?i.onFulfilled:i.o...
  function h (line 70) | function h(n,u){if(u===n)return y(n,new TypeError('A promise cannot be r...
  function y (line 70) | function y(n,t){n._y=2,n._z=t,c._C&&c._C(n,t),p(n)}
  function p (line 70) | function p(n){if(1===n._x&&(s(n,n._A),n._A=null),2===n._x){for(var t=0;t...
  function a (line 70) | function a(n,t,o){this.onFulfilled='function'==typeof n?n:null,this.onRe...
  function v (line 70) | function v(n,i){var u=!1,c=f(n,function(n){u||(u=!0,h(i,n))},function(n)...
  function t (line 73) | function t(t){return'function'==typeof t&&t.toString().indexOf('[native ...
  function l (line 74) | function l(t,n,o){return Object.defineProperty(t,n,{value:o,enumerable:!...
  function s (line 74) | function s(t,n,o,i){var c=n&&n.prototype instanceof b?n:b,u=Object.creat...
  function p (line 74) | function p(t,n,o){try{return{type:"normal",arg:t.call(n,o)}}catch(t){ret...
  function b (line 74) | function b(){}
  function E (line 74) | function E(){}
  function _ (line 74) | function _(){}
  function N (line 74) | function N(t){["next","throw","return"].forEach(function(n){l(t,n,functi...
  function T (line 74) | function T(t,n){function o(c,u,h,f){var l=p(t[c],t,u);if("throw"!==l.typ...
  function F (line 74) | function F(t,n,o){var i=y;return function(c,u){if(i===w)throw new Error(...
  function P (line 74) | function P(t,o){var i=t.iterator[o.method];if(i===n){if(o.delegate=null,...
  function S (line 74) | function S(t){var n={tryLoc:t[0]};1 in t&&(n.catchLoc=t[1]),2 in t&&(n.f...
  function I (line 74) | function I(t){var n=t.completion||{};n.type="normal",delete n.arg,t.comp...
  function R (line 74) | function R(t){this.tryEntries=[{tryLoc:"root"}],t.forEach(S,this),this.r...
  function A (line 74) | function A(t){if(t){var o=t[u];if(o)return o.call(t);if("function"==type...
  function Y (line 74) | function Y(){return{value:n,done:!0}}
  function c (line 74) | function c(i,c){return f.type="throw",f.arg=t,o.next=i,c&&(o.method="nex...
  function k (line 76) | function k(){var e=c.indexOf(null);return-1===e&&(e=c.length),e}
  function w (line 76) | function w(e,t){var n=v++,i=k();return c[i]=n,l[i]=e,o[i]=t,n}
  function p (line 76) | function p(e,t,n){e>v&&console.warn('Tried to call timer with ID %s but ...
  function N (line 76) | function N(){if(0===u.length)return!1;var e=u;u=[];for(var t=0;t<e.lengt...
  function b (line 76) | function b(e){c[e]=null,l[e]=null,o[e]=null}
  function I (line 76) | function I(e){if(null!=e){var t=c.indexOf(e);if(-1!==t){var n=o[t];b(t),...
  function R (line 76) | function R(t,i,l,o){n(e.default,'NativeTiming is available'),e.default.c...
  function x (line 76) | function x(t){n(e.default,'NativeTiming is available'),e.default.deleteT...
  function y (line 76) | function y(t){n(e.default,'NativeTiming is available'),e.default.setSend...
  function t (line 77) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function p (line 81) | function p(e){var t=c();return function(){var s,n=(0,u.default)(e);if(t)...
  function c (line 81) | function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function s (line 81) | function s(){return(0,a.default)(this,s),t.apply(this,arguments)}
  function c (line 81) | function c(){var e;return(0,a.default)(this,c),(e=h.call(this)).UNSENT=E...
  function o (line 82) | function o(){return"undefined"!=typeof Reflect&&Reflect.get?(m.exports=o...
  function i (line 84) | function i(t,r){var l=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function f (line 84) | function f(t){for(var l=1;l<arguments.length;l++){var o=null!=arguments[...
  function t (line 84) | function t(){(0,l.default)(this,t)}
  function t (line 85) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function s (line 86) | function s(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0...
  function u (line 89) | function u(t,n){var s=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function l (line 89) | function l(t){for(var n=1;n<arguments.length;n++){var i=null!=arguments[...
  function t (line 89) | function t(){(0,i.default)(this,t),this._timespans={},this._extras={},th...
  function i (line 93) | function i(t){var n=t.length;if(n%4>0)throw new Error('Invalid string. L...
  function f (line 93) | function f(t,n,o){return 3*(n+o)/4-o}
  function A (line 93) | function A(n,o,h){for(var u,c,i=[],f=o;f<h;f+=3)u=(n[f]<<16&16711680)+(n...
  function c (line 94) | function c(t,n){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function s (line 94) | function s(n){for(var i=1;i<arguments.length;i++){var o=null!=arguments[...
  function t (line 94) | function t(){i(this,t),this._parts=[]}
  function o (line 95) | function o(n){var o=t.get(n);return console.assert(null!=o,"'this' is ex...
  function i (line 95) | function i(t){null==t.passiveListener?t.event.cancelable&&(t.canceled=!0...
  function l (line 95) | function l(n,o){t.set(this,{eventTarget:n,event:o,eventPhase:2,currentTa...
  function u (line 95) | function u(t){return{get:function(){return o(this).event[t]},set:functio...
  function s (line 95) | function s(t){return{value:function(){var n=o(this).event;return n[t].ap...
  function p (line 95) | function p(t,n){var o=Object.keys(n);if(0===o.length)return t;function i...
  function c (line 95) | function c(t){if(null==t||t===Object.prototype)return l;var o=n.get(t);r...
  function f (line 95) | function f(t,n){return new(c(Object.getPrototypeOf(n)))(t,n)}
  function v (line 95) | function v(t){return o(t).immediateStopped}
  function y (line 95) | function y(t,n){o(t).eventPhase=n}
  function b (line 95) | function b(t,n){o(t).currentTarget=n}
  function h (line 95) | function h(t,n){o(t).passiveListener=n}
  method type (line 95) | get type(){return o(this).event.type}
  method target (line 95) | get target(){return o(this).eventTarget}
  method currentTarget (line 95) | get currentTarget(){return o(this).currentTarget}
  method NONE (line 95) | get NONE(){return 0}
  method CAPTURING_PHASE (line 95) | get CAPTURING_PHASE(){return 1}
  method AT_TARGET (line 95) | get AT_TARGET(){return 2}
  method BUBBLING_PHASE (line 95) | get BUBBLING_PHASE(){return 3}
  method eventPhase (line 95) | get eventPhase(){return o(this).eventPhase}
  method bubbles (line 95) | get bubbles(){return Boolean(o(this).event.bubbles)}
  method cancelable (line 95) | get cancelable(){return Boolean(o(this).event.cancelable)}
  method defaultPrevented (line 95) | get defaultPrevented(){return o(this).canceled}
  method composed (line 95) | get composed(){return Boolean(o(this).event.composed)}
  method timeStamp (line 95) | get timeStamp(){return o(this).timeStamp}
  method srcElement (line 95) | get srcElement(){return o(this).eventTarget}
  method cancelBubble (line 95) | get cancelBubble(){return o(this).stopped}
  method cancelBubble (line 95) | set cancelBubble(t){if(t){var n=o(this);n.stopped=!0,"boolean"==typeof n...
  method returnValue (line 95) | get returnValue(){return!o(this).canceled}
  method returnValue (line 95) | set returnValue(t){t||i(o(this))}
  function P (line 95) | function P(t){return null!==t&&"object"==typeof t}
  function x (line 95) | function x(t){var n=w.get(t);if(null==n)throw new TypeError("'this' is e...
  function E (line 95) | function E(t){return{get:function(){for(var n=x(this).get(t);null!=n;){i...
  function O (line 95) | function O(t,n){Object.defineProperty(t,"on"+n,E(n))}
  function j (line 95) | function j(t){function n(){B.call(this)}n.prototype=Object.create(B.prot...
  function B (line 95) | function B(){if(!(this instanceof B)){if(1===arguments.length&&Array.isA...
  function s (line 97) | function s(t){if('string'!=typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.^_...
  function h (line 97) | function h(t){return'string'!=typeof t&&(t=String(t)),t}
  function f (line 97) | function f(t){var e={next:function(){var e=t.shift();return{done:void 0=...
  function u (line 97) | function u(t){this.map={},t instanceof u?t.forEach(function(t,e){this.ap...
  function c (line 97) | function c(t){if(t.bodyUsed)return Promise.reject(new TypeError('Already...
  function y (line 97) | function y(t){return new Promise(function(e,o){t.onload=function(){e(t.r...
  function l (line 97) | function l(t){var e=new FileReader,o=y(e);return e.readAsArrayBuffer(t),o}
  function p (line 97) | function p(t){for(var e=new Uint8Array(t),o=new Array(e.length),n=0;n<e....
  function b (line 97) | function b(t){if(t.slice)return t.slice(0);var e=new Uint8Array(t.byteLe...
  function w (line 97) | function w(){return this.bodyUsed=!1,this._initBody=function(t){var e;th...
  function _ (line 97) | function _(t,e){if(!(this instanceof _))throw new TypeError('Please use ...
  function E (line 97) | function E(t){var e=new FormData;return t.trim().split('&').forEach(func...
  function T (line 97) | function T(t,e){if(!(this instanceof T))throw new TypeError('Please use ...
  function B (line 97) | function B(n,i){return new Promise(function(s,f){var c=new _(n,i);if(c.s...
  function I (line 98) | function I(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function R (line 98) | function R(e,s,o){var u;(0,n.default)(this,R),(u=A.call(this)).CONNECTIN...
  function t (line 99) | function t(l){(0,n.default)(this,t),'ios'===u.default.OS&&(0,s.default)(...
  function t (line 100) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function o (line 102) | function o(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 102) | function y(e,n,u){var c;return t(this,y),l(null!=e&&null!=n,'Failed to c...
  function f (line 103) | function f(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function R (line 103) | function R(){var t;return(0,e.default)(this,R),(t=v.call(this)).EMPTY=c,...
  function t (line 104) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 105) | function t(n){var s=this;(0,o.default)(this,t),this._searchParams=[],'ob...
  function l (line 105) | function l(t){return/^(?:(?:(?:https?|ftp):)?\/\/)(?:(?:[1-9]\d?|1\d\d|2...
  function t (line 105) | function t(n,s){(0,o.default)(this,t),this._searchParamsInstance=null;va...
  function u (line 106) | function u(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 106) | function y(){throw t(this,y),s.call(this),new TypeError("AbortSignal can...
  function o (line 106) | function o(){var e;t(this,o),s.set(this,(e=Object.create(f.prototype),c....
  function y (line 106) | function y(t){var e=s.get(t);if(null==e)throw new TypeError("Expected 't...
  function t (line 108) | function t(){(0,n.default)(this,t)}
  function t (line 109) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 113) | function t(n){if("function"!=typeof WeakMap)return null;var u=new WeakMa...
  function t (line 115) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 119) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 122) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  method BatchedBridge (line 123) | get BatchedBridge(){return r(d[0])}
  method ExceptionsManager (line 123) | get ExceptionsManager(){return r(d[1])}
  method Platform (line 123) | get Platform(){return r(d[2])}
  method RCTEventEmitter (line 123) | get RCTEventEmitter(){return r(d[3])}
  method ReactNativeViewConfigRegistry (line 123) | get ReactNativeViewConfigRegistry(){return r(d[4])}
  method TextInputState (line 123) | get TextInputState(){return r(d[5])}
  method UIManager (line 123) | get UIManager(){return r(d[6])}
  method deepDiffer (line 123) | get deepDiffer(){return r(d[7])}
  method deepFreezeAndThrowOnMutationInDev (line 123) | get deepFreezeAndThrowOnMutationInDev(){return r(d[8])}
  method flattenStyle (line 123) | get flattenStyle(){return r(d[9])}
  method ReactFiberErrorDialog (line 123) | get ReactFiberErrorDialog(){return r(d[10]).default}
  method legacySendAccessibilityEvent (line 123) | get legacySendAccessibilityEvent(){return r(d[11])}
  method RawEventEmitter (line 123) | get RawEventEmitter(){return r(d[12]).default}
  method CustomEvent (line 123) | get CustomEvent(){return r(d[13]).default}
  function l (line 125) | function l(t){var s=t.bubblingEventTypes,u=t.directEventTypes;if(null!=s...
  function l (line 126) | function l(n){t!==n&&null!=n&&(t=n)}
  function f (line 126) | function f(n){t===n&&null!=n&&(t=null)}
  function u (line 127) | function u(t){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function c (line 130) | function c(e,n,t,r,l,a,i,u,o){var s=Array.prototype.slice.call(arguments...
  function m (line 130) | function m(e,n,t,r,l,a,i,u,o){d=!1,f=null,c.apply(g,arguments)}
  function v (line 130) | function v(e,n,t,r,l,a,i,u,o){if(m.apply(this,arguments),d){if(!d)throw ...
  function w (line 130) | function w(e,n,t){var r=e.type||"unknown-event";e.currentTarget=k(t),v(r...
  function x (line 130) | function x(e){var n=e._dispatchListeners,t=e._dispatchInstances;if(b(n))...
  function P (line 130) | function P(){return!0}
  function R (line 130) | function R(){return!1}
  function T (line 130) | function T(e,n,t,r){for(var l in this.dispatchConfig=e,this._targetInst=...
  function _ (line 130) | function _(e,n,t,r){if(this.eventPool.length){var l=this.eventPool.pop()...
  function N (line 130) | function N(e){if(!(e instanceof this))throw Error("Trying to release an ...
  function C (line 130) | function C(e){e.getPooled=_,e.eventPool=[],e.release=N}
  function n (line 130) | function n(){}
  function t (line 130) | function t(){return r.apply(this,arguments)}
  function I (line 130) | function I(e){return"topTouchStart"===e}
  function L (line 130) | function L(e){return"topTouchMove"===e}
  function j (line 130) | function j(e){return e.timeStamp||e.timestamp}
  function H (line 130) | function H(e){if(null==(e=e.identifier))throw Error("Touch object is mis...
  function Q (line 130) | function Q(e){var n=H(e),t=D[n];t?(t.touchActive=!0,t.startPageX=e.pageX...
  function B (line 130) | function B(e){var n=D[H(e)];n&&(n.touchActive=!0,n.previousPageX=n.curre...
  function W (line 130) | function W(e){var n=D[H(e)];n&&(n.touchActive=!1,n.previousPageX=n.curre...
  function Y (line 130) | function Y(e,n){if(null==n)throw Error("accumulate(...): Accumulated ite...
  function q (line 130) | function q(e,n){if(null==n)throw Error("accumulateInto(...): Accumulated...
  function $ (line 130) | function $(e,n,t){Array.isArray(e)?e.forEach(n,t):e&&n.call(t,e)}
  function J (line 130) | function J(e,n){var t=X;X=e,null!==ie.GlobalResponderHandler&&ie.GlobalR...
  function Z (line 130) | function Z(e){do{e=e.return}while(e&&5!==e.tag);return e||null}
  function ee (line 130) | function ee(e,n,t){for(var r=[];e;)r.push(e),e=Z(e);for(e=r.length;0<e--...
  function ne (line 130) | function ne(e,n){if(null===(e=e.stateNode))return null;if(null===(e=y(e)...
  function te (line 130) | function te(e,n,t){(n=ne(e,t.dispatchConfig.phasedRegistrationNames[n]))...
  function re (line 130) | function re(e){if(e&&e.dispatchConfig.registrationName){var n=e._targetI...
  function le (line 130) | function le(e){if(e&&e.dispatchConfig.phasedRegistrationNames){var n=e._...
  function ae (line 130) | function ae(e){e&&e.dispatchConfig.phasedRegistrationNames&&ee(e._target...
  function se (line 130) | function se(){if(ue)for(var e in oe){var n=oe[e],t=ue.indexOf(e);if(-1>=...
  function ce (line 130) | function ce(e,n){if(pe[e])throw Error("EventPluginRegistry: More than on...
  function he (line 130) | function he(e,n,t,r){var l=e.stateNode;if(null===l)return null;if(null==...
  function ve (line 130) | function ve(e,n,t){var r=t?b(t)?t.length:1:0;if(0<r)if(n._dispatchListen...
  function be (line 130) | function be(e,n,t){ve(e,t,n=he(e,t.dispatchConfig.phasedRegistrationName...
  function ye (line 130) | function ye(e,n,t,r){for(var l=[];e;){l.push(e);do{e=e.return}while(e&&5...
  function Se (line 130) | function Se(e){e&&e.dispatchConfig.phasedRegistrationNames&&ye(e._target...
  function ke (line 130) | function ke(e){if(e&&e.dispatchConfig.registrationName){var n=e._targetI...
  function Re (line 130) | function Re(e){return e}
  function Be (line 130) | function Be(e){return null===e||"object"!=typeof e?null:"function"==type...
  function We (line 130) | function We(e){if(null==e)return null;if("function"==typeof e)return e.d...
  function Oe (line 130) | function Oe(e){var n=e.type;switch(e.tag){case 24:return"Cache";case 9:r...
  function Ve (line 130) | function Ve(e){var n=e,t=e;if(e.alternate)for(;n.return;)n=n.return;else...
  function Ye (line 130) | function Ye(e){if(Ve(e)!==e)throw Error("Unable to find node on an unmou...
  function qe (line 130) | function qe(e){var n=e.alternate;if(!n){if(null===(n=Ve(e)))throw Error(...
  function $e (line 130) | function $e(e){return null!==(e=qe(e))?Xe(e):null}
  function Xe (line 130) | function Xe(e){if(5===e.tag||6===e.tag)return e;for(e=e.child;null!==e;)...
  function Ge (line 130) | function Ge(e,n){return function(){if(n&&("boolean"!=typeof e.__isMounte...
  function nn (line 130) | function nn(e,n){return"object"!=typeof n||null===n||u.deepDiffer(e,n,en)}
  function tn (line 130) | function tn(e,n,t){if(b(n))for(var r=n.length;r--&&0<Ze;)tn(e,n[r],t);el...
  function rn (line 130) | function rn(e,n,t,r){if(!e&&n===t)return e;if(!n||!t)return t?ln(e,t,r):...
  function ln (line 130) | function ln(e,n,t){if(!n)return e;if(!b(n))return un(e,Je,n,t);for(var r...
  function an (line 130) | function an(e,n,t){if(!n)return e;if(!b(n))return un(e,n,Je,t);for(var r...
  function un (line 130) | function un(e,n,t,r){var l,a;for(a in t)if(l=r[a]){var i=n[a],u=t[a];"fu...
  function on (line 130) | function on(e,n){return e(n)}
  function cn (line 130) | function cn(e,n){if(sn)return e(n);sn=!0;try{return on(e,n)}finally{sn=!1}}
  function fn (line 130) | function fn(e){if(e){var n=e._dispatchListeners,t=e._dispatchInstances;i...
  function En (line 130) | function En(e){if(xn&&"function"==typeof xn.onCommitFiberRoot)try{xn.onC...
  function Cn (line 130) | function Cn(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:retur...
  function zn (line 130) | function zn(e,n){var t=e.pendingLanes;if(0===t)return 0;var r=0,l=e.susp...
  function In (line 130) | function In(e,n){switch(e){case 1:case 2:case 4:return n+250;case 8:case...
  function Ln (line 130) | function Ln(e){return 0!==(e=-1073741825&e.pendingLanes)?e:1073741824&e?...
  function Un (line 130) | function Un(){var e=_n;return 0==(4194240&(_n<<=1))&&(_n=64),e}
  function Mn (line 130) | function Mn(e){for(var n=[],t=0;31>t;t++)n.push(e);return n}
  function Fn (line 130) | function Fn(e,n,t){e.pendingLanes|=n,536870912!==n&&(e.suspendedLanes=0,...
  function Dn (line 130) | function Dn(e,n){var t=e.pendingLanes&~n;e.pendingLanes=n,e.suspendedLan...
  function An (line 130) | function An(e,n){var t=e.entangledLanes|=n;for(e=e.entanglements;t;){var...
  function Hn (line 130) | function Hn(e){return 1<(e&=-e)?4<e?0!=(268435455&e)?16:536870912:4:1}
  function Qn (line 130) | function Qn(){throw Error("The current renderer does not support hydrati...
  function e (line 130) | function e(e,n,t,r){this._nativeTag=e,this.viewConfig=n,this.currentProp...
  function ut (line 130) | function ut(e,n,t,r){return t=at,at+=2,{node:Wn(t,"RCTRawText",n,{text:e...
  function ct (line 130) | function ct(e){var n=e.node,t=un(null,Je,{style:{display:"none"}},e.cano...
  function dt (line 130) | function dt(e,n,t){return n="",t&&(n=" (created by "+t+")"),"\n    in "+...
  function ft (line 130) | function ft(e,n){return e?dt(e.displayName||e.name||null,n,null):""}
  function mt (line 130) | function mt(e){return{current:e}}
  function vt (line 130) | function vt(e){0>gt||(e.current=ht[gt],ht[gt]=null,gt--)}
  function bt (line 130) | function bt(e,n){ht[++gt]=e.current,e.current=n}
  function xt (line 130) | function xt(e,n){var t=e.type.contextTypes;if(!t)return yt;var r=e.state...
  function Et (line 130) | function Et(e){return null!==(e=e.childContextTypes)&&void 0!==e}
  function Pt (line 130) | function Pt(){vt(kt),vt(St)}
  function Rt (line 130) | function Rt(e,n,t){if(St.current!==yt)throw Error("Unexpected context fo...
  function Tt (line 130) | function Tt(e,n,t){var r=e.stateNode;if(n=n.childContextTypes,"function"...
  function _t (line 130) | function _t(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMerged...
  function Nt (line 130) | function Nt(e,n,t){var r=e.stateNode;if(!r)throw Error("Expected to have...
  function Ut (line 130) | function Ut(){if(!Lt&&null!==zt){Lt=!0;var e=0,n=jn;try{var t=zt;for(jn=...
  function Qt (line 130) | function Qt(e){for(;e===Dt;)Dt=Mt[--Ft],Mt[Ft]=null,Mt[--Ft]=null;for(;e...
  function Ot (line 130) | function Ot(e,n){if(Ct(e,n))return!0;if("object"!=typeof e||null===e||"o...
  function Vt (line 130) | function Vt(e){switch(e.tag){case 5:return dt(e.type,null,null);case 16:...
  function Yt (line 130) | function Yt(e,n){if(e&&e.defaultProps){for(var t in n=E({},n),e=e.defaul...
  function Jt (line 130) | function Jt(){Gt=Xt=$t=null}
  function Kt (line 130) | function Kt(e){var n=qt.current;vt(qt),e._currentValue2=n}
  function Zt (line 130) | function Zt(e,n,t){for(;null!==e;){var r=e.alternate;if((e.childLanes&n)...
  function er (line 130) | function er(e,n){$t=e,Gt=Xt=null,null!==(e=e.dependencies)&&null!==e.fir...
  function nr (line 130) | function nr(e){var n=e._currentValue2;if(Gt!==e)if(e={context:e,memoized...
  function lr (line 130) | function lr(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:...
  function ar (line 130) | function ar(e,n){e=e.updateQueue,n.updateQueue===e&&(n.updateQueue={base...
  function ir (line 130) | function ir(e,n){return{eventTime:e,lane:n,tag:0,payload:null,callback:n...
  function ur (line 130) | function ur(e,n){var t=e.updateQueue;null!==t&&(t=t.shared,vi(e)?(null==...
  function or (line 130) | function or(e,n,t){if(null!==(n=n.updateQueue)&&(n=n.shared,0!=(4194240&...
  function sr (line 130) | function sr(e,n){var t=e.updateQueue,r=e.alternate;if(null!==r&&t===(r=r...
  function cr (line 130) | function cr(e,n,t,r){var l=e.updateQueue;rr=!1;var a=l.firstBaseUpdate,i...
  function dr (line 130) | function dr(e,n,t){if(e=n.effects,n.effects=null,null!==e)for(n=0;n<e.le...
  function pr (line 130) | function pr(e,n,t,r){t=null===(t=t(r,n=e.memoizedState))||void 0===t?n:E...
  function gr (line 130) | function gr(e,n,t,r,l,a,i){return"function"==typeof(e=e.stateNode).shoul...
  function mr (line 130) | function mr(e,n,t){var r=!1,l=yt,a=n.contextType;return"object"==typeof ...
  function vr (line 130) | function vr(e,n,t,r){e=n.state,"function"==typeof n.componentWillReceive...
  function br (line 130) | function br(e,n,t,r){var l=e.stateNode;l.props=t,l.state=e.memoizedState...
  function yr (line 130) | function yr(e,n,t){if(null!==(e=t.ref)&&"function"!=typeof e&&"object"!=...
  function Sr (line 130) | function Sr(e,n){throw e=Object.prototype.toString.call(n),Error("Object...
  function kr (line 130) | function kr(e){return(0,e._init)(e._payload)}
  function wr (line 130) | function wr(e){function n(n,t){if(e){var r=n.deletions;null===r?(n.delet...
  function Nr (line 130) | function Nr(e){if(e===Pr)throw Error("Expected host context to exist. Th...
  function Cr (line 130) | function Cr(e,n){bt(_r,n),bt(Tr,e),bt(Rr,Pr),vt(Rr),bt(Rr,{isInAParentTe...
  function zr (line 130) | function zr(){vt(Rr),vt(Tr),vt(_r)}
  function Ir (line 130) | function Ir(e){Nr(_r.current);var n=Nr(Rr.current),t=e.type;t="AndroidTe...
  function Lr (line 130) | function Lr(e){Tr.current===e&&(vt(Rr),vt(Tr))}
  function Mr (line 130) | function Mr(e){for(var n=e;null!==n;){if(13===n.tag){var t=n.memoizedSta...
  function Dr (line 130) | function Dr(){for(var e=0;e<Fr.length;e++)Fr[e]._workInProgressVersionSe...
  function qr (line 130) | function qr(){throw Error("Invalid hook call. Hooks can only be called i...
  function $r (line 130) | function $r(e,n){if(null===n)return!1;for(var t=0;t<n.length&&t<e.length...
  function Xr (line 130) | function Xr(e,n,t,r,l,a){if(Hr=a,Qr=n,n.memoizedState=null,n.updateQueue...
  function Gr (line 130) | function Gr(){var e={memoizedState:null,baseState:null,baseQueue:null,qu...
  function Jr (line 130) | function Jr(){if(null===Br){var e=Qr.alternate;e=null!==e?e.memoizedStat...
  function Kr (line 130) | function Kr(e,n){return"function"==typeof n?n(e):n}
  function Zr (line 130) | function Zr(e){var n=Jr(),t=n.queue;if(null===t)throw Error("Should have...
  function el (line 130) | function el(e){var n=Jr(),t=n.queue;if(null===t)throw Error("Should have...
  function nl (line 130) | function nl(){}
  function tl (line 130) | function tl(e,n){var t=Qr,r=Jr(),l=n(),a=!Ct(r.memoizedState,l);if(a&&(r...
  function rl (line 130) | function rl(e,n,t){e.flags|=16384,e={getSnapshot:n,value:t},null===(n=Qr...
  function ll (line 130) | function ll(e,n,t,r){n.value=t,n.getSnapshot=r,il(n)&&gi(e,1,-1)}
  function al (line 130) | function al(e,n,t){return t(function(){il(n)&&gi(e,1,-1)})}
  function il (line 130) | function il(e){var n=e.getSnapshot;e=e.value;try{var t=n();return!Ct(e,t...
  function ul (line 130) | function ul(e){var n=Gr();return"function"==typeof e&&(e=e()),n.memoized...
  function ol (line 130) | function ol(e,n,t,r){return e={tag:e,create:n,destroy:t,deps:r,next:null...
  function sl (line 130) | function sl(){return Jr().memoizedState}
  function cl (line 130) | function cl(e,n,t,r){var l=Gr();Qr.flags|=e,l.memoizedState=ol(1|n,t,voi...
  function dl (line 130) | function dl(e,n,t,r){var l=Jr();r=void 0===r?null:r;var a=void 0;if(null...
  function fl (line 130) | function fl(e,n){return cl(8390656,8,e,n)}
  function pl (line 130) | function pl(e,n){return dl(2048,8,e,n)}
  function hl (line 130) | function hl(e,n){return dl(4,2,e,n)}
  function gl (line 130) | function gl(e,n){return dl(4,4,e,n)}
  function ml (line 130) | function ml(e,n){return"function"==typeof n?(e=e(),n(e),function(){n(nul...
  function vl (line 130) | function vl(e,n,t){return t=null!==t&&void 0!==t?t.concat([e]):null,dl(4...
  function bl (line 130) | function bl(){}
  function yl (line 130) | function yl(e,n){var t=Jr();n=void 0===n?null:n;var r=t.memoizedState;re...
  function Sl (line 130) | function Sl(e,n){var t=Jr();n=void 0===n?null:n;var r=t.memoizedState;re...
  function kl (line 130) | function kl(e,n,t){return 0==(21&Hr)?(e.baseState&&(e.baseState=!1,Ql=!0...
  function wl (line 130) | function wl(e,n){var t=jn;jn=0!==t&&4>t?t:4,e(!0);var r=jr.transition;jr...
  function xl (line 130) | function xl(){return Jr().memoizedState}
  function El (line 130) | function El(e,n,t){var r=hi(e);t={lane:r,action:t,hasEagerState:!1,eager...
  function Pl (line 130) | function Pl(e,n,t){var r=hi(e),l={lane:r,action:t,hasEagerState:!1,eager...
  function Rl (line 130) | function Rl(e){var n=e.alternate;return e===Qr||null!==n&&n===Qr}
  function Tl (line 130) | function Tl(e,n){Vr=Or=!0;var t=e.pending;null===t?n.next=n:(n.next=t.ne...
  function _l (line 130) | function _l(e,n,t){vi(e)?(null===(e=n.interleaved)?(t.next=t,null===tr?t...
  function Nl (line 130) | function Nl(e,n,t){if(0!=(4194240&t)){var r=n.lanes;t|=r&=e.pendingLanes...
  function Ul (line 130) | function Ul(e,n){try{var t="",r=n;do{t+=Vt(r),r=r.return}while(r);var l=...
  function Ml (line 130) | function Ml(e,n){try{!1!==u.ReactFiberErrorDialog.showErrorDialog({compo...
  function Dl (line 130) | function Dl(e,n,t){(t=ir(-1,t)).tag=3,t.payload={element:null};var r=n.v...
  function Al (line 130) | function Al(e,n,t){(t=ir(-1,t)).tag=3;var r=e.type.getDerivedStateFromEr...
  function jl (line 130) | function jl(e,n,t){var r=e.pingCache;if(null===r){r=e.pingCache=new Fl;v...
  function Bl (line 130) | function Bl(e,n,t,r){n.child=null===e?Er(n,null,t,r):xr(n,e.child,t,r)}
  function Wl (line 130) | function Wl(e,n,t,r,l){t=t.render;var a=n.ref;return er(n,l),r=Xr(e,n,t,...
  function Ol (line 130) | function Ol(e,n,t,r,l){if(null===e){var a=t.type;return"function"!=typeo...
  function Vl (line 130) | function Vl(e,n,t,r,l){if(null!==e){var a=e.memoizedProps;if(Ot(a,r)&&e....
  function Yl (line 130) | function Yl(e,n,t){var r=n.pendingProps,l=r.children,a=null!==e?e.memoiz...
  function ql (line 130) | function ql(e,n){var t=n.ref;(null===e&&null!==t||null!==e&&e.ref!==t)&&...
  function $l (line 130) | function $l(e,n,t,r,l){var a=Et(t)?wt:St.current;return a=xt(n,a),er(n,l...
  function Xl (line 130) | function Xl(e,n,t,r,l){if(Et(t)){var a=!0;_t(n)}else a=!1;if(er(n,l),nul...
  function Gl (line 130) | function Gl(e,n,t,r,l,a){ql(e,n);var i=0!=(128&n.flags);if(!r&&!i)return...
  function Jl (line 130) | function Jl(e){var n=e.stateNode;n.pendingContext?Rt(0,n.pendingContext,...
  function ra (line 130) | function ra(e){return{baseLanes:e,cachePool:null,transitions:null}}
  function la (line 130) | function la(e,n,t){var r,l=n.pendingProps,a=Ur.current,i=!1,u=0!=(128&n....
  function aa (line 130) | function aa(e,n){return(n=Ki({mode:"visible",children:n},e.mode,0,null))...
  function ia (line 130) | function ia(e,n,t,r){return null!==r&&(null===Bt?Bt=[r]:Bt.push(r)),xr(n...
  function ua (line 130) | function ua(e,n,t,r,l,a,i){if(t)return 256&n.flags?(n.flags&=-257,ia(e,n...
  function oa (line 130) | function oa(e,n,t){e.lanes|=n;var r=e.alternate;null!==r&&(r.lanes|=n),Z...
  function sa (line 130) | function sa(e,n,t,r,l){var a=e.memoizedState;null===a?e.memoizedState={i...
  function ca (line 130) | function ca(e,n,t){var r=n.pendingProps,l=r.revealOrder,a=r.tail;if(Bl(e...
  function da (line 130) | function da(e,n){0==(1&n.mode)&&null!==e&&(e.alternate=null,n.alternate=...
  function fa (line 130) | function fa(e,n,t){if(null!==e&&(n.dependencies=e.dependencies),Xa|=n.la...
  function pa (line 130) | function pa(e,n,t){switch(n.tag){case 3:Jl(n);break;case 5:Ir(n);break;c...
  function ha (line 130) | function ha(e,n){if(null!==e&&e.child===n.child)return!0;if(0!=(16&n.fla...
  function ga (line 130) | function ga(e,n,t,r){for(var l=n.child;null!==l;){if(5===l.tag){var a=l....
  function ma (line 130) | function ma(e,n){switch(e.tailMode){case"hidden":n=e.tail;for(var t=null...
  function va (line 130) | function va(e){var n=null!==e.alternate&&e.alternate.child===e.child,t=0...
  function ba (line 130) | function ba(e,n,t){var r=n.pendingProps;switch(Qt(n),n.tag){case 2:case ...
  function ya (line 130) | function ya(e,n){switch(Qt(n),n.tag){case 1:return Et(n.type)&&Pt(),6553...
  function wa (line 130) | function wa(e,n){var t=e.ref;if(null!==t)if("function"==typeof t)try{t(n...
  function xa (line 130) | function xa(e,n,t){try{t()}catch(t){ji(e,n,t)}}
  function Pa (line 130) | function Pa(e,n){for(ka=n;null!==ka;)if(n=(e=ka).child,0!=(1028&e.subtre...
  function Ra (line 130) | function Ra(e,n,t){var r=n.updateQueue;if(null!==(r=null!==r?r.lastEffec...
  function Ta (line 130) | function Ta(e,n){if(null!==(n=null!==(n=n.updateQueue)?n.lastEffect:null...
  function _a (line 130) | function _a(e){var n=e.alternate;null!==n&&(e.alternate=null,_a(n)),e.ch...
  function Na (line 130) | function Na(e,n,t){for(t=t.child;null!==t;)Ca(e,n,t),t=t.sibling}
  function Ca (line 130) | function Ca(e,n,t){if(xn&&"function"==typeof xn.onCommitFiberUnmount)try...
  function za (line 130) | function za(e){var n=e.updateQueue;if(null!==n){e.updateQueue=null;var t...
  function Ia (line 130) | function Ia(e,n){var t=n.deletions;if(null!==t)for(var r=0;r<t.length;r+...
  function La (line 130) | function La(e,n){var t=e.alternate,r=e.flags;switch(e.tag){case 0:case 1...
  function Ua (line 130) | function Ua(e){var n=e.flags;2&n&&(e.flags&=-3),4096&n&&(e.flags&=-4097)}
  function Ma (line 130) | function Ma(e){for(ka=e;null!==ka;){var n=ka,t=n.child;if(0!=(8772&n.sub...
  function pi (line 130) | function pi(){return 0!=(6&Qa)?vn():-1!==di?di:di=vn()}
  function hi (line 130) | function hi(e){if(0==(1&e.mode))return 1;if(0!=(2&Qa)&&0!==Oa)return Oa&...
  function gi (line 130) | function gi(e,n,t){if(50<si)throw si=0,ci=null,Error("Maximum update dep...
  function mi (line 130) | function mi(e,n){e.lanes|=n;var t=e.alternate;for(null!==t&&(t.lanes|=n)...
  function vi (line 130) | function vi(e){return(null!==Ba||null!==tr)&&0!=(1&e.mode)&&0==(2&Qa)}
  function bi (line 130) | function bi(e,n){for(var t=e.callbackNode,r=e.suspendedLanes,l=e.pingedL...
  function yi (line 130) | function yi(e,n){if(di=-1,fi=0,0!=(6&Qa))throw Error("Should not already...
  function Si (line 130) | function Si(e,n){var t=Ka;return e.current.memoizedState.isDehydrated&&(...
  function ki (line 130) | function ki(e){null===Za?Za=e:Za.push.apply(Za,e)}
  function wi (line 130) | function wi(e){for(var n=e;;){if(16384&n.flags){var t=n.updateQueue;if(n...
  function xi (line 130) | function xi(e,n){for(n&=~Ja,n&=~Ga,e.suspendedLanes|=n,e.pingedLanes&=~n...
  function Ei (line 130) | function Ei(e){if(0!=(6&Qa))throw Error("Should not already be working."...
  function Pi (line 130) | function Pi(){Va=Ya.current,vt(Ya)}
  function Ri (line 130) | function Ri(e,n){e.finishedWork=null,e.finishedLanes=0;var t=e.timeoutHa...
  function Ti (line 130) | function Ti(e,n){for(;;){var t=Wa;try{if(Jt(),Ar.current=Cl,Or){for(var ...
  function _i (line 130) | function _i(){var e=Aa.current;return Aa.current=Cl,null===e?Cl:e}
  function Ni (line 130) | function Ni(){0!==qa&&3!==qa&&2!==qa||(qa=4),null===Ba||0==(268435455&Xa...
  function Ci (line 130) | function Ci(e,n){var t=Qa;Qa|=2;var r=_i();for(Ba===e&&Oa===n||(ti=null,...
  function zi (line 130) | function zi(){for(;null!==Wa;)Li(Wa)}
  function Ii (line 130) | function Ii(){for(;null!==Wa&&!gn();)Li(Wa)}
  function Li (line 130) | function Li(e){var n=Fa(e.alternate,e,Va);e.memoizedProps=e.pendingProps...
  function Ui (line 130) | function Ui(e){var n=e;do{var t=n.alternate;if(e=n.return,0==(32768&n.fl...
  function Mi (line 130) | function Mi(e,n,t){var r=jn,l=Ha.transition;try{Ha.transition=null,jn=1,...
  function Fi (line 130) | function Fi(e,n,t,r){do{Di()}while(null!==ui);if(0!=(6&Qa))throw Error("...
  function Di (line 130) | function Di(){if(null!==ui){var e=Hn(oi),n=Ha.transition,t=jn;try{if(Ha....
  function Ai (line 130) | function Ai(e,n,t){ur(e,n=Dl(e,n=Ul(t,n),1)),n=pi(),null!==(e=mi(e,1))&&...
  function ji (line 130) | function ji(e,n,t){if(3===e.tag)Ai(e,e,t);else for(n=e.return;null!==n;)...
  function Hi (line 130) | function Hi(e,n,t){var r=e.pingCache;null!==r&&r.delete(n),n=pi(),e.ping...
  function Qi (line 130) | function Qi(e,n){0===n&&(0==(1&e.mode)?n=1:(n=Nn,0==(130023424&(Nn<<=1))...
  function Bi (line 130) | function Bi(e){var n=e.memoizedState,t=0;null!==n&&(t=n.retryLane),Qi(e,t)}
  function Wi (line 130) | function Wi(e,n){var t=0;switch(e.tag){case 13:var r=e.stateNode,l=e.mem...
  function Oi (line 130) | function Oi(e,n){return pn(e,n)}
  function Vi (line 130) | function Vi(e,n,t,r){this.tag=e,this.key=t,this.sibling=this.child=this....
  function Yi (line 130) | function Yi(e,n,t,r){return new Vi(e,n,t,r)}
  function qi (line 130) | function qi(e){return!(!(e=e.prototype)||!e.isReactComponent)}
  function $i (line 130) | function $i(e){if("function"==typeof e)return qi(e)?1:0;if(void 0!==e&&n...
  function Xi (line 130) | function Xi(e,n){var t=e.alternate;return null===t?((t=Yi(e.tag,n,e.key,...
  function Gi (line 130) | function Gi(e,n,t,r,l,a){var i=2;if(r=e,"function"==typeof e)qi(e)&&(i=1...
  function Ji (line 130) | function Ji(e,n,t,r){return(e=Yi(7,e,r,n)).lanes=t,e}
  function Ki (line 130) | function Ki(e,n,t,r){return(e=Yi(22,e,r,n)).elementType=He,e.lanes=t,e.s...
  function Zi (line 130) | function Zi(e,n,t){return(e=Yi(6,e,null,n)).lanes=t,e}
  function eu (line 130) | function eu(e,n,t){return(n=Yi(4,null!==e.children?e.children:[],e.key,n...
  function nu (line 130) | function nu(e,n,t,r,l){this.tag=n,this.containerInfo=e,this.finishedWork...
  function tu (line 130) | function tu(e,n,t){var r=3<arguments.length&&void 0!==arguments[3]?argum...
  function ru (line 130) | function ru(e){var n=e._reactInternals;if(void 0===n){if("function"==typ...
  function lu (line 130) | function lu(e,n,t,r){var l=n.current,a=pi(),i=hi(l);e:if(t){t=t._reactIn...
  function au (line 130) | function au(e){return null==e?null:"number"==typeof e?e:e._nativeTag?e._...
  function iu (line 130) | function iu(e){console.error(e)}
  function y (line 132) | function y(e){return null===e||"object"!=typeof e?null:"function"==typeo...
  function m (line 132) | function m(e,t,r){this.props=e,this.context=t,this.refs=h,this.updater=r...
  function v (line 132) | function v(){}
  function b (line 132) | function b(e,t,r){this.props=e,this.context=t,this.refs=h,this.updater=r...
  function k (line 132) | function k(t,r,n){var o,u={},c=null,a=null;if(null!=r)for(o in void 0!==...
  function C (line 132) | function C(t,r){return{$$typeof:e,type:t.type,key:r,ref:t.ref,props:t.pr...
  function g (line 132) | function g(t){return"object"==typeof t&&null!==t&&t.$$typeof===e}
  function j (line 132) | function j(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,func...
  function P (line 132) | function P(e,t){return"object"==typeof e&&null!==e&&null!=e.key?j(""+e.k...
  function x (line 132) | function x(r,n,o,u,c){var a=typeof r;"undefined"!==a&&"boolean"!==a||(r=...
  function I (line 132) | function I(e,t,r){if(null==e)return e;var n=[],o=0;return x(e,n,"","",fu...
  function T (line 132) | function T(e){if(-1===e._status){var t=e._result;(t=t()).then(function(t...
  function n (line 134) | function n(n,e){var t=n.length;n.push(e);n:for(;0<t;){var r=t-1>>>1,l=n[...
  function e (line 134) | function e(n){return 0===n.length?null:n[0]}
  function t (line 134) | function t(n){if(0===n.length)return null;var e=n[0],t=n.pop();if(t!==e)...
  function a (line 134) | function a(n,e){var t=n.sortIndex-e.sortIndex;return 0!==t?t:n.id-e.id}
  function g (line 134) | function g(a){for(var r=e(s);null!==r;){if(null===r.callback)t(s);else{i...
  function h (line 134) | function h(n){if(p=!1,g(n),!v)if(null!==e(o))v=!0,E(k);else{var t=e(s);n...
  function k (line 134) | function k(n,a){v=!1,p&&(p=!1,m(T),T=-1),d=!0;var r=b;try{for(g(a),f=e(o...
  function L (line 134) | function L(){return!(_e.unstable_now()-C<P)}
  function M (line 134) | function M(){if(null!==I){var n=_e.unstable_now();C=n;var e=!0;try{e=I(!...
  function E (line 134) | function E(n){I=n,x||(x=!0,w())}
  function N (line 134) | function N(n,e){T=y(function(){n(_e.unstable_now())},e)}
  function p (line 135) | function p(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function y (line 135) | function y(t,n){if(!n&&t&&t.__esModule)return t;if(null===t||"object"!=t...
  function b (line 135) | function b(t,o){return f.default.register(t,function(){var f,s=null!=(f=...
  function w (line 135) | function w(t){return(0,s.default)(null==n,'Unexpected invocation!'),null...
  function s (line 136) | function s(t,c,o,p){for(var v in o){var f=o[v];if(p.hasOwnProperty(v)){v...
  function l (line 136) | function l(t){return'object'!=typeof t||Array.isArray(t)?null:t}
  function u (line 138) | function u(t,n){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function c (line 138) | function c(t){for(var i=1;i<arguments.length;i++){var c=null!=arguments[...
  function l (line 138) | function l(t,n){var i;return null==t||null==n?null!=(i=null!=t?t:n)?i:{}...
  function r (line 143) | function r(r,n,t){return t<0&&(t+=1),t>1&&(t-=1),t<.16666666666666666?r+...
  function n (line 143) | function n(n,t,u){var s=u<.5?u*(1+t):u+t-u*t,c=2*u-s,l=r(c,s,n+.33333333...
  function c (line 143) | function c(){for(var r=arguments.length,n=new Array(r),t=0;t<r;t++)n[t]=...
  function l (line 143) | function l(r){var n=parseInt(r,10);return n<0?0:n>255?255:n}
  function o (line 143) | function o(r){return(parseFloat(r)%360+360)%360/360}
  function g (line 143) | function g(r){var n=parseFloat(r);return n<0?0:n>1?255:Math.round(255*n)}
  function h (line 143) | function h(r){var n=parseFloat(r);return n<0?0:n>100?1:n/100}
  function b (line 143) | function b(r){switch(r){case'transparent':return 0;case'aliceblue':retur...
  function o (line 152) | function o(e,n){var t=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function i (line 152) | function i(e){for(var t=1;t<arguments.length;t++){var i=null!=arguments[...
  function v (line 152) | function v(){return y||(w=t.default.getConstants(),y=!0),w}
  function p (line 152) | function p(e){if(void 0===s[e]&&g.nativeCallSyncHook&&t.default.getConst...
  function O (line 152) | function O(e){var n=v()[e];s[e]=n,n.Manager&&(c(n,'Constants',{get:funct...
  function t (line 153) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function n (line 155) | function n(t,n){var s=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function s (line 155) | function s(s){for(var o=1;o<arguments.length;o++){var i=null!=arguments[...
  function O (line 155) | function O(t){var n=v.getConstants();n.ViewManagerNames||n.LazyViewManag...
  function C (line 155) | function C(t,n){if(!n)return t;if(!t)return n;for(var s in n)if(n.hasOwn...
  function E (line 155) | function E(t){switch(t){case'CATransform3D':return p;case'CGPoint':retur...
  function w (line 155) | function w(t){switch(t){case'CGColor':case'UIColor':return c;case'CGColo...
  function l (line 156) | function l(){if(u)return u;var t=g.nativeExtensions&&g.nativeExtensions....
  function v (line 156) | function v(){if(void 0===n){var t=l(),s=t&&t.match(/^https?:\/\/.*?\//);...
  function p (line 156) | function p(t){if(t){if(t.startsWith('assets://'))return null;(t=t.substr...
  function v (line 158) | function v(t){var s=u(t.scales,n.get()),o=1===s?'':'@'+s+'x';return f(t)...
  function l (line 158) | function l(s,n,u){t(this,l),this.serverUrl=s,this.jsbundleUrl=n,this.ass...
  function o (line 159) | function o(){t(this,o)}
  function t (line 160) | function t(){(0,s.default)(this,t)}
  function t (line 161) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function n (line 163) | function n(n){if(n.toString()in t)return t[n.toString()];throw new Error...
  function s (line 163) | function s(t){var n=t.httpServerLocation;return n.startsWith('/')?n.subs...
  function t (line 164) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function u (line 165) | function u(u){var o=(0,n.default)(u);return null==o?(console.error('Inva...
  function f (line 167) | function f(t,n){var u={};function s(t,n,o){if(typeof t==typeof n||null==...
  function c (line 168) | function c(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function f (line 168) | function f(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function o (line 169) | function o(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function t (line 173) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function f (line 175) | function f(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function b (line 175) | function b(e,t){var n;(0,u.default)(this,b);var c=t.bubbles,l=t.cancelab...
  function t (line 176) | function t(s,l){(0,n.default)(this,t),this.type=s,this.bubbles=!(null==l...
  function t (line 177) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function p (line 178) | function p(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function y (line 178) | function y(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function O (line 178) | function O(t){for(var o=1;o<arguments.length;o++){var i=null!=arguments[...
  function o (line 179) | function o(t,o){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function n (line 179) | function n(n){for(var c=1;c<arguments.length;c++){var i=null!=arguments[...
  function i (line 180) | function i(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function p (line 180) | function p(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function l (line 180) | function l(t){for(var o=1;o<arguments.length;o++){var c=null!=arguments[...
  function u (line 181) | function u(o){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function p (line 181) | function p(o,t){if(!t&&o&&o.__esModule)return o;if(null===o||"object"!=t...
  function a (line 184) | function a(e,t,a){var l,s={},u=null,y=null;for(l in void 0!==a&&(u=""+a)...
  function s (line 185) | function s(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function y (line 185) | function y(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function h (line 185) | function h(){return(0,t.default)(this,h),s.apply(this,arguments)}
  function j (line 190) | function j(t){if("function"!=typeof WeakMap)return null;var e=new WeakMa...
  function x (line 190) | function x(t,e){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function P (line 190) | function P(t){for(var i=1;i<arguments.length;i++){var o=null!=arguments[...
  function F (line 190) | function F(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function j (line 190) | function j(){return(0,i.default)(this,j),f.apply(this,arguments)}
  function O (line 191) | function O(n){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function y (line 191) | function y(n,t){if(!t&&n&&n.__esModule)return n;if(null===n||"object"!=t...
  function P (line 191) | function P(n,t){var o=Object.keys(n);if(Object.getOwnPropertySymbols){va...
  function v (line 191) | function v(n){for(var o=1;o<arguments.length;o++){var s=null!=arguments[...
  function h (line 191) | function h(n){var t=(0,f.useState)(n),s=(0,o.default)(t,2),l=s[0],i=s[1]...
  function n (line 192) | function n(t){if("function"!=typeof WeakMap)return null;var u=new WeakMa...
  function t (line 193) | function t(t){return{bottom:t,left:t,right:t,top:t}}
  function T (line 195) | function T(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function O (line 195) | function O(t,n){var E=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function P (line 195) | function P(t){for(var E=1;E<arguments.length;E++){var i=null!=arguments[...
  function t (line 195) | function t(n){var i=this;(0,E.default)(this,t),this._eventHandlers=null,...
  function L (line 195) | function L(t){var n=arguments.length>1&&void 0!==arguments[1]?arguments[...
  function b (line 195) | function b(t){var n=t.nativeEvent,E=n.clientX,i=n.clientY;return P(P({},...
  function t (line 198) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 199) | function t(){(0,n.default)(this,t),this._listeners=[]}
  function u (line 201) | function u(t,i){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function s (line 201) | function s(t){for(var n=1;n<arguments.length;n++){var o=null!=arguments[...
  function c (line 202) | function c(t,i){var c=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function s (line 202) | function s(t){for(var s=1;s<arguments.length;s++){var n=null!=arguments[...
  function O (line 203) | function O(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function S (line 203) | function S(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function F (line 203) | function F(e){for(var s=1;s<arguments.length;s++){var i=null!=arguments[...
  function R (line 203) | function R(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function O (line 203) | function O(){var e;(0,i.default)(this,O);for(var t=arguments.length,s=ne...
  function P (line 204) | function P(t){if("function"!=typeof WeakMap)return null;var e=new WeakMa...
  function w (line 204) | function w(t,e){var s=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function F (line 204) | function F(t){for(var s=1;s<arguments.length;s++){var i=null!=arguments[...
  function j (line 204) | function j(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function w (line 204) | function w(){var t;(0,i.default)(this,w);for(var e=arguments.length,s=ne...
  function i (line 205) | function i(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function f (line 205) | function f(t,n){if(!n&&t&&t.__esModule)return t;if(null===t||"object"!=t...
  function p (line 205) | function p(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  method FlatList (line 205) | get FlatList(){return r(d[5])}
  method Image (line 205) | get Image(){return r(d[6])}
  method ScrollView (line 205) | get ScrollView(){return r(d[7])}
  method SectionList (line 205) | get SectionList(){return r(d[8])}
  method Text (line 205) | get Text(){return r(d[9])}
  method View (line 205) | get View(){return r(d[10])}
  function o (line 206) | function o(t,n){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function u (line 206) | function u(t){for(var i=1;i<arguments.length;i++){var u=null!=arguments[...
  function j (line 206) | function j(t){return function(n){t(null==n?n:function(){if(h)console.war...
  function _ (line 207) | function _(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function b (line 207) | function b(t){if(void 0===t||null===t)return null;if(y(t))return t;var e...
  function y (line 207) | function y(t){return t&&'number'==typeof t.r&&'number'==typeof t.g&&'num...
  function p (line 207) | function p(t){return t&&t.r instanceof u.default&&t.g instanceof u.defau...
  function C (line 207) | function C(t,a){var s;(0,e.default)(this,C),(s=h.call(this))._listeners=...
  function _ (line 208) | function _(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function v (line 208) | function v(t){var e=new Set;!(function t(n){'function'==typeof n.update?...
  function V (line 208) | function V(e,n){var s;if(t(this,V),s=N.call(this),'number'!=typeof e)thr...
  function u (line 209) | function u(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function c (line 209) | function c(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function p (line 209) | function p(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[...
  function g (line 209) | function g(t){if(t.outputRange&&'string'==typeof t.outputRange[0])return...
  function v (line 209) | function v(t,e,n,r,a,o,i,u){var c=t;if(c<e){if('identity'===i)return c;'...
  function y (line 209) | function y(t){var e=h(t);return null===e||'number'!=typeof e?t:"rgba("+(...
  function x (line 209) | function x(t){var e=t.outputRange;s(e.length>=2,'Bad output range'),R(e=...
  function R (line 209) | function R(t){for(var e=t[0].replace(b,''),n=1;n<t.length;++n)s(e===t[n]...
  function O (line 209) | function O(t,e){var n;for(n=1;n<e.length-1&&!(e[n]>=t);++n);return n-1}
  function _ (line 209) | function _(e,n){var r;return t(this,_),(r=h.call(this))._parent=e,r._con...
  function l (line 210) | function l(){t(this,l),this._listeners={}}
  function p (line 211) | function p(t,n){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function v (line 211) | function v(t){for(var i=1;i<arguments.length;i++){var o=null!=arguments[...
  function L (line 211) | function L(){E=c.default.addListener('onNativeAnimatedModuleGetValue',fu...
  function j (line 211) | function j(t){return B.hasOwnProperty(t)}
  function Q (line 211) | function Q(t){return F.hasOwnProperty(t)}
  function k (line 211) | function k(t){return I.hasOwnProperty(t)}
  method nativeEventEmitter (line 211) | get nativeEventEmitter(){return N||(N=new u.default('ios'!==l.default.OS...
  function t (line 212) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 213) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function o (line 214) | function o(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 214) | function y(){var e;return t(this,y),(e=N.call(this))._children=[],e}
  function E (line 215) | function E(){h||(h=w>0?setTimeout(S,0):setImmediate(S))}
  function S (line 215) | function S(){h=0;var n=f.size;l.forEach(function(n){return f.add(n)}),v....
  function u (line 216) | function u(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function s (line 216) | function s(t){for(var n=1;n<arguments.length;n++){var s=null!=arguments[...
  function e (line 216) | function e(n){var u=n.onMoreTasks;t(this,e),this._onMoreTasks=u,this._qu...
  function u (line 217) | function u(t,n,l,u){var _=[];f(l[0]&&l[0].nativeEvent,'Native driven eve...
  function v (line 217) | function v(n,s){var o=this;t(this,v),this._listeners=[],this._callListen...
  function o (line 218) | function o(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function p (line 218) | function p(e,s){var n;t(this,p),n=k.call(this);var u=e||{x:0,y:0};return...
  function i (line 219) | function i(t,n){var r=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function o (line 219) | function o(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[...
  function o (line 220) | function o(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function p (line 220) | function p(e,n){var a;return t(this,p),(a=y.call(this))._a='number'==typ...
  function l (line 221) | function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function p (line 221) | function p(e,a,n){var u;return t(this,p),(u=v.call(this))._a=e,u._min=a,...
  function u (line 222) | function u(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function b (line 222) | function b(e,n){var o;return t(this,b),(o=p.call(this))._warnedAboutDivi...
  function c (line 223) | function c(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 223) | function y(e,u){var n;return t(this,y),(n=v.call(this))._a=e,n._modulus=...
  function o (line 224) | function o(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 224) | function y(e,n){var a;return t(this,y),(a=p.call(this))._a='number'==typ...
  function o (line 225) | function o(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function p (line 225) | function p(e,n){var a;return t(this,p),(a=y.call(this))._a='number'==typ...
  function _ (line 226) | function _(t,e){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function s (line 226) | function s(e){for(var i=1;i<arguments.length;i++){var n=null!=arguments[...
  function l (line 226) | function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function O (line 226) | function O(t,i,n,o,c){var u;return e(this,O),(u=y.call(this))._value=t,u...
  function l (line 227) | function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 227) | function y(e){var n,o,s,c;return t(this,y),(c=p.call(this))._deceleratio...
  function _ (line 228) | function _(){t(this,_)}
  function _ (line 229) | function _(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 229) | function y(t){var e,n,o,l,h,_,f,v,V,T,b,M;if((0,s.default)(this,y),(V=p....
  function n (line 230) | function n(n){return 3.62*(n-30)+194}
  function t (line 230) | function t(n){return 3*(n-8)+25}
  function f (line 230) | function f(n,t,o){return(n-t)/(o-t)}
  function c (line 230) | function c(n,t,o){return t+n*(o-t)}
  function s (line 230) | function s(n,t,o){return n*o+(1-n)*t}
  function p (line 230) | function p(n){return 44e-6*Math.pow(n,3)-.006*Math.pow(n,2)+.36*n+2}
  function h (line 230) | function h(n){return 4.5e-7*Math.pow(n,3)-332e-6*Math.pow(n,2)+.1078*n+5...
  function l (line 231) | function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function v (line 231) | function v(){if(!f){var t=r(d[13]);f=t.inOut(t.ease)}return f}
  function y (line 231) | function y(t){var n,o,s,u,_,l;return(0,e.default)(this,y),(l=p.call(this...
  function c (line 233) | function c(n,t){return 1-3*t+3*n}
  function v (line 233) | function v(n,t){return 3*t-6*n}
  function s (line 233) | function s(n){return 3*n}
  function w (line 233) | function w(n,t,u){return((c(t,u)*n+v(t,u))*n+s(t))*n}
  function l (line 233) | function l(n,t,u){return 3*c(t,u)*n*n+2*v(t,u)*n+s(t)}
  function y (line 233) | function y(n,t,f,i,c){var v,s,l=0,y=t,b=f;do{(v=w(s=y+(b-y)/2,i,c)-n)>0?...
  function b (line 233) | function b(t,u,o,f){for(var i=u,c=0;c<n;++c){var v=l(i,o,f);if(0===v)ret...
  function h (line 233) | function h(u){for(var i=0,c=1;10!==c&&v[c]<=u;++c)i+=f;var s=i+(u-v[--c]...
  function v (line 234) | function v(t){if("function"!=typeof WeakMap)return null;var e=new WeakMa...
  function y (line 234) | function y(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function b (line 234) | function b(t){for(var e=1;e<arguments.length;e++){var o=null!=arguments[...
  function P (line 234) | function P(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 234) | function y(){var t;(0,i.default)(this,y);for(var e=arguments.length,n=ne...
  function n (line 235) | function n(t){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function _ (line 236) | function _(e,t){var i=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function u (line 236) | function u(t){for(var i=1;i<arguments.length;i++){var n=null!=arguments[...
  function l (line 236) | function l(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function b (line 236) | function b(e,i){var n;return t(this,b),n=V.call(this),e.style&&(e=u(u({}...
  function c (line 237) | function c(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function u (line 237) | function u(e){for(var n=1;n<arguments.length;n++){var i=null!=arguments[...
  function f (line 237) | function f(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function b (line 237) | function b(t){var n;return e(this,b),n=O.call(this),(t=y(t)||{}).transfo...
  function s (line 238) | function s(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function k (line 238) | function k(n){var e;return t(this,k),(e=y.call(this))._transforms=n,e}
  function c (line 240) | function c(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function f (line 240) | function f(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function u (line 240) | function u(n){for(var o=1;o<arguments.length;o++){var c=null!=arguments[...
  function v (line 241) | function v(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function y (line 241) | function y(e){for(var t=1;t<arguments.length;t++){var i=null!=arguments[...
  function C (line 241) | function C(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function P (line 241) | function P(e){return null!=e?e:1}
  function j (line 241) | function j(e){var t;return(0,i.default)(this,j),(t=S.call(this,e))._virt...
  function f (line 242) | function f(t,n,f){for(var l=arguments.length>3&&void 0!==arguments[3]?ar...
  function l (line 242) | function l(t,n){return n.last-n.first+1-Math.max(0,1+Math.min(n.last,t.l...
  function n (line 243) | function n(n,u){if(n.length!==u.length)return!1;for(var i=0;i<n.length;i...
  function _ (line 244) | function _(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function y (line 244) | function y(e,t){var o=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function v (line 244) | function v(e){for(var o=1;o<arguments.length;o++){var s=null!=arguments[...
  function L (line 244) | function L(e){var t=C();return function(){var o,s=(0,c.default)(e);if(t)...
  function C (line 244) | function C(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function P (line 244) | function P(e){return null!=e&&e}
  function K (line 244) | function K(e){return null!=e?e:10}
  function F (line 244) | function F(e){return null!=e?e:10}
  function V (line 244) | function V(e){return null!=e?e:2}
  function j (line 244) | function j(e){return null!=e?e:21}
  function c (line 244) | function c(e){var t,s;if((0,o.default)(this,c),(s=l.call(this,e))._getSc...
  function n (line 244) | function n(){var e;(0,o.default)(this,n);for(var s=arguments.length,i=ne...
  function B (line 244) | function B(e){for(var t="VirtualizedList trace:\n  Child ("+(e.horizonta...
  function l (line 245) | function l(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function u (line 245) | function u(t,n){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function s (line 245) | function s(t){for(var i=1;i<arguments.length;i++){var o=null!=arguments[...
  function h (line 246) | function h(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function v (line 246) | function v(e,t){if(!t&&e&&e.__esModule)return e;if(null===e||"object"!=t...
  function y (line 246) | function y(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function O (line 246) | function O(e){for(var n=1;n<arguments.length;n++){var o=null!=arguments[...
  function R (line 246) | function R(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function y (line 246) | function y(){var e;(0,o.default)(this,y);for(var t=arguments.length,n=ne...
  function f (line 247) | function f(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function o (line 248) | function o(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function B (line 249) | function B(e){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function P (line 249) | function P(e,o){if(!o&&e&&e.__esModule)return e;if(null===e||"object"!=t...
  function L (line 249) | function L(e,o){var t=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function W (line 249) | function W(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[...
  function z (line 249) | function z(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function A (line 249) | function A(e){var o,t,l,s,c;return(0,n.default)(this,A),(c=x.call(this,e...
  function J (line 249) | function J(e,o){return(0,j.jsx)(Z,W(W({},e),{},{scrollViewRef:o}))}
  function Y (line 250) | function Y(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function L (line 250) | function L(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function Y (line 250) | function Y(){var e;(0,t.default)(this,Y);for(var n=arguments.length,l=ne...
  function t (line 251) | function t(){var u=this;(0,n.default)(this,t),this._emitter=new l.defaul...
  function s (line 252) | function s(n,u,s){var c,p;if(!t.default.isTesting&&l){var y,f,b=!1,I=fun...
  function c (line 252) | function c(n,t,u){return{duration:n,create:{type:t,property:u},update:{t...
  function t (line 254) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 256) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 259) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function n (line 260) | function n(t){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 262) | function t(o){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function n (line 263) | function n(t){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function i (line 264) | function i(o){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function c (line 264) | function c(o,t){var n=Object.keys(o);if(Object.getOwnPropertySymbols){va...
  function s (line 265) | function s(n,l){t(this,s),this._delay=l,this._callback=n}
  function s (line 266) | function s(t,n){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function l (line 266) | function l(n){for(var i=1;i<arguments.length;i++){var l=null!=arguments[...
  function t (line 266) | function t(n){i(this,t),this._anyBlankStartTime=null,this._enabled=!1,th...
  function o (line 267) | function o(t,i){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function c (line 267) | function c(i){for(var n=1;n<arguments.length;n++){var s=null!=arguments[...
  function t (line 267) | function t(){var i=arguments.length>0&&void 0!==arguments[0]?arguments[0...
  function f (line 267) | function f(t,i,n,s,o,c){if(v(n,s,o))return!0;var l=h(n,s,o);return 100*(...
  function h (line 267) | function h(t,i,n){var s=Math.min(i,n)-Math.max(t,0);return Math.max(0,s)}
  function v (line 267) | function v(t,i,n){return t>=0&&i<=n&&i>t}
  function t (line 268) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function n (line 269) | function n(n,t,o,u,c,s,f){try{var v=n[s](f),p=v.value}catch(n){return vo...
  function v (line 269) | function v(t){n(f,c,s,v,p,"next",t)}
  function p (line 269) | function p(t){n(f,c,s,v,p,"throw",t)}
  function n (line 270) | function n(t){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function s (line 271) | function s(t){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function u (line 271) | function u(t,o){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function n (line 272) | function n(t){if("function"!=typeof WeakMap)return null;var u=new WeakMa...
  function t (line 273) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function c (line 274) | function c(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function f (line 274) | function f(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function u (line 274) | function u(n){for(var o=1;o<arguments.length;o++){var c=null!=arguments[...
  function u (line 275) | function u(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function i (line 275) | function i(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function p (line 275) | function p(t){for(var o=1;o<arguments.length;o++){var c=null!=arguments[...
  function h (line 276) | function h(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function O (line 276) | function O(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function R (line 276) | function R(e){for(var n=1;n<arguments.length;n++){var o=null!=arguments[...
  function b (line 276) | function b(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function O (line 276) | function O(){var e;(0,o.default)(this,O);for(var t=arguments.length,n=ne...
  function _ (line 277) | function _(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function b (line 277) | function b(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function x (line 277) | function x(e){for(var t=1;t<arguments.length;t++){var n=null!=arguments[...
  function k (line 277) | function k(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function _ (line 277) | function _(){var e;(0,o.default)(this,_);for(var t=arguments.length,n=ne...
  function O (line 277) | function O(e){var n=e.LeadingSeparatorComponent,i=e.SeparatorComponent,o...
  function t (line 278) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 279) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function b (line 280) | function b(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function j (line 280) | function j(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function w (line 280) | function w(e){for(var n=1;n<arguments.length;n++){var o=null!=arguments[...
  function P (line 280) | function P(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function j (line 280) | function j(){var e;(0,o.default)(this,j);for(var t=arguments.length,n=ne...
  function h (line 281) | function h(t){if("function"!=typeof WeakMap)return null;var e=new WeakMa...
  function v (line 281) | function v(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function j (line 281) | function j(){return(0,e.default)(this,j),O.apply(this,arguments)}
  function k (line 283) | function k(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function w (line 283) | function w(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function L (line 283) | function L(e){for(var n=1;n<arguments.length;n++){var o=null!=arguments[...
  function P (line 283) | function P(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function R (line 283) | function R(e){var t,n;return(0,i.default)(this,R),(t=w.call(this,e))._fr...
  function R (line 284) | function R(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function f (line 284) | function f(t){var e;return(0,s.default)(this,f),(e=o.call(this,t))._iden...
  function t (line 286) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function v (line 288) | function v(t){if("function"!=typeof WeakMap)return null;var e=new WeakMa...
  function y (line 288) | function y(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function b (line 288) | function b(){var t;(0,e.default)(this,b);for(var n=arguments.length,o=ne...
  function t (line 289) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 291) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function b (line 292) | function b(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function y (line 292) | function y(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function O (line 292) | function O(t){for(var o=1;o<arguments.length;o++){var s=null!=arguments[...
  function v (line 292) | function v(t){var n=(0,l.useState)(!1),s=(0,o.default)(n,2),u=s[0],i=s[1...
  function s (line 293) | function s(n){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function u (line 294) | function u(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function p (line 296) | function p(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function v (line 296) | function v(t,n){var i=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function b (line 296) | function b(t){for(var i=1;i<arguments.length;i++){var o=null!=arguments[...
  function b (line 298) | function b(t){if("function"!=typeof WeakMap)return null;var e=new WeakMa...
  function S (line 298) | function S(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function O (line 298) | function O(t,e){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function h (line 298) | function h(t){for(var e=1;e<arguments.length;e++){var n=null!=arguments[...
  function _ (line 298) | function _(t){var e,n,l=null!=(e=t.animated)&&e,o=null!=(n=t.showHideTra...
  function b (line 298) | function b(){var t;(0,n.default)(this,b);for(var e=arguments.length,l=ne...
  function t (line 299) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 300) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function y (line 301) | function y(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function O (line 301) | function O(t,n){if(!n&&t&&t.__esModule)return t;if(null===t||"object"!=t...
  function h (line 301) | function h(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function C (line 301) | function C(t){for(var o=1;o<arguments.length;o++){var l=null!=arguments[...
  function u (line 303) | function u(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function o (line 304) | function o(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function j (line 305) | function j(n){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function x (line 305) | function x(n,t){var u=Object.keys(n);if(Object.getOwnPropertySymbols){va...
  function P (line 305) | function P(n){for(var u=1;u<arguments.length;u++){var l=null!=arguments[...
  function A (line 305) | function A(n){var t,O,j,x=B(null),A=null==n.selection?null:{start:n.sele...
  function t (line 306) | function t(t,o){if(null!=t)return t;var n=new Error(void 0!==o?o:'Got un...
  function c (line 307) | function c(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function f (line 307) | function f(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function l (line 307) | function l(t){for(var o=1;o<arguments.length;o++){var u=null!=arguments[...
  function u (line 308) | function u(t){if("function"!=typeof WeakMap)return null;var e=new WeakMa...
  function h (line 308) | function h(t,e){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function R (line 308) | function R(t){for(var e=1;e<arguments.length;e++){var i=null!=arguments[...
  function n (line 309) | function n(t,o){this.width=t,this.height=o}
  function l (line 311) | function l(t,o){this.left=t,this.top=o}
  function v (line 312) | function v(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function _ (line 312) | function _(e,t){var s=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function O (line 312) | function O(e){for(var s=1;s<arguments.length;s++){var i=null!=arguments[...
  function w (line 312) | function w(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function S (line 312) | function S(){var e;(0,i.default)(this,S);for(var t=arguments.length,s=ne...
  function y (line 313) | function y(e){if("function"!=typeof WeakMap)return null;var t=new WeakMa...
  function b (line 313) | function b(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function h (line 313) | function h(e){for(var n=1;n<arguments.length;n++){var s=null!=arguments[...
  function O (line 313) | function O(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function P (line 313) | function P(){var e;(0,s.default)(this,P);for(var t=arguments.length,n=ne...
  function j (line 313) | function j(e){var t;return{cancelable:!e.rejectResponderTermination,disa...
  function l (line 314) | function l(t,o){var n=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function s (line 314) | function s(t){for(var n=1;n<arguments.length;n++){var i=null!=arguments[...
  function t (line 315) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 317) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function u (line 319) | function u(n,t){var o=Object.keys(n);if(Object.getOwnPropertySymbols){va...
  function f (line 319) | function f(n){for(var o=1;o<arguments.length;o++){var i=null!=arguments[...
  function t (line 321) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function o (line 322) | function o(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function v (line 322) | function v(){return(0,u.default)(this,v),p.apply(this,arguments)}
  function f (line 323) | function f(){_.addFileSource('react_hierarchy.txt',function(){return r(d...
  function t (line 323) | function t(){(0,l.default)(this,t)}
  function t (line 324) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 325) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function p (line 328) | function p(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function l (line 328) | function l(t){for(var o=1;o<arguments.length;o++){var c=null!=arguments[...
  function u (line 329) | function u(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function t (line 331) | function t(){var u=this;if((0,n.default)(this,t),this.currentState=null,...
  function t (line 333) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function s (line 334) | function s(e){return e?(Array.isArray(e)?e:[e]).map(function(e){return c...
  function c (line 334) | function c(e){if(!e)return null;var t=new Error(e.message);return t.key=...
  function t (line 335) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 336) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 338) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 341) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 343) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function y (line 344) | function y(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function O (line 344) | function O(){return(0,e.default)(this,O),h.call(this,'ios'===s.default.O...
  function t (line 345) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 346) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function l (line 348) | function l(o,t,u,s){o.handle&&(n.clearInteractionHandle(o.handle),o.hand...
  function O (line 350) | function O(i,n){var s=Object.keys(i);if(Object.getOwnPropertySymbols){va...
  function S (line 350) | function S(i){for(var s=1;s<arguments.length;s++){var o=null!=arguments[...
  function i (line 350) | function i(){(0,o.default)(this,i),this.PERMISSIONS=R,this.RESULTS=C}
  function t (line 351) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function t (line 352) | function t(n){var l=this;(0,o.default)(this,t),this._data={},this._remot...
  function t (line 353) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function l (line 354) | function l(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function u (line 354) | function u(t){for(var o=1;o<arguments.length;o++){var i=null!=arguments[...
  function t (line 354) | function t(){(0,o.default)(this,t)}
  function t (line 355) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function a (line 358) | function a(t){var e=t.getSnapshot;t=t.value;try{var u=e();return!n(t,u)}...
  function t (line 359) | function t(t){var n=t.window;c.width===n.width&&c.height===n.height&&c.s...
  function t (line 362) | function t(n){if("function"!=typeof WeakMap)return null;var o=new WeakMa...
  function o (line 363) | function o(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function p (line 363) | function p(){return t(this,p),y.apply(this,arguments)}
  function t (line 364) | function t(n){(0,o.default)(this,t),this._listenerCount=0,n.__expo_modul...
  function b (line 366) | function b(t,n){var i=f(t,O),u=l(t,O);return o.default.createElement(v,c...
  function u (line 366) | function u(t,n){var o=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function c (line 366) | function c(t){for(var o=1;o<arguments.length;o++){var i=null!=arguments[...
  function f (line 366) | function f(t,n){var o=c({},t);for(var i of n)delete o[i];return o}
  function l (line 366) | function l(t,n){return n.reduce(function(n,o){return o in t&&(n[o]=t[o])...
  function f (line 370) | function f(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function h (line 370) | function h(t,e){var o;return(0,n.default)(this,h),(o=v.call(this,e)).cod...
  function f (line 371) | function f(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function p (line 371) | function p(e,t){return(0,n.default)(this,p),y.call(this,'ERR_UNAVAILABLE...
  function v (line 374) | function v(n,o){return n+": "+o}
  function f (line 374) | function f(n){return n.replace(/[-.]/g,'_').toUpperCase()}
  function n (line 375) | function n(t){var n,i,o=t.replace(/^v/,'').replace(/\+.*$/,''),f=(i='-',...
  function i (line 375) | function i(t){return isNaN(Number(t))?t:Number(t)}
  function o (line 375) | function o(n){if('string'!=typeof n)throw new TypeError('Invalid argumen...
  function f (line 375) | function f(t,f){[t,f].forEach(o);for(var u=n(t),p=n(f),s=0;s<Math.max(u....
  function s (line 375) | function s(t){if('string'!=typeof t)throw new TypeError('Invalid operato...
  function l (line 378) | function l(t,l){var s=(0,c.useRef)(!0),v=(0,c.useState)(null),h=(0,o.def...
  function p (line 386) | function p(t){if("function"!=typeof WeakMap)return null;var e=new WeakMa...
  function v (line 386) | function v(t,e){if(!e&&t&&t.__esModule)return t;if(null===t||"object"!=t...
  function t (line 386) | function t(e){var s=e.name,o=e.type,l=e.hash,f=void 0===l?null:l,y=e.uri...
  function c (line 389) | function c(t,n){var s=Object.keys(t);if(Object.getOwnPropertySymbols){va...
  function h (line 389) | function h(t){for(var s=1;s<arguments.length;s++){var o=null!=arguments[...
  function v (line 389) | function v(t){if(!l.manifestBaseUrl)return t;if(''!==new f.default(t).pr...
  function t (line 390) | function t(t){if('string'!=typeof t)throw new TypeError('Path must be a ...
  function n (line 390) | function n(t,n){for(var i,l='',o=0,h=-1,f=0,c=0;c<=t.length;++c){if(c<t....
  function i (line 390) | function i(t,n){var i=n.dir||n.root,l=n.base||(n.name||'')+(n.ext||'');r...
  function i (line 391) | function i(o){return(o||'').toString().replace(s,'')}
  function w (line 391) | function w(o){var t,s=('undefined'!=typeof window?window:void 0!==g?g:'u...
  function y (line 391) | function y(o){return'file:'===o||'ftp:'===o||'http:'===o||'https:'===o||...
  function C (line 391) | function C(o,t){o=(o=i(o)).replace(n,''),t=t||{};var s,p=l.exec(o),c=p[1...
  function v (line 391) | function v(o,t){if(''===o)return t;for(var s=(t||'/').split('/').slice(0...
  function I (line 391) | function I(s,p,c){if(s=(s=i(s)).replace(n,''),!(this instanceof I))retur...
  function e (line 393) | function e(n){try{return decodeURIComponent(n.replace(/\+/g,' '))}catch(...
  function u (line 393) | function u(n){try{return encodeURIComponent(n)}catch(n){return null}}
  function o (line 394) | function o(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function _ (line 395) | function _(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function U (line 395) | function U(){return(U=(0,l.default)(function*(t,n,u,l){return E?D(t,n,u,...
  function D (line 395) | function D(t,n,u,l){return M.apply(this,arguments)}
  function M (line 395) | function M(){return(M=(0,l.default)(function*(t,n,u,l){var f=n||(0,o.def...
  function I (line 395) | function I(t,n,u){return P.apply(this,arguments)}
  function P (line 395) | function P(){return(P=(0,l.default)(function*(t,n,u){if(t.startsWith('fi...
  function t (line 396) | function t(n,t){var o=(65535&n)+(65535&t);return(n>>16)+(t>>16)+(o>>16)<...
  function o (line 396) | function o(n,o,u,c,f,i){return t((a=t(t(o,n),t(c,i)))<<(h=f)|a>>>32-h,u)...
  function u (line 396) | function u(n,t,u,c,f,i,a){return o(t&u|~t&c,n,t,f,i,a)}
  function c (line 396) | function c(n,t,u,c,f,i,a){return o(t&c|u&~c,n,t,f,i,a)}
  function f (line 396) | function f(n,t,u,c,f,i,a){return o(t^u^c,n,t,f,i,a)}
  function i (line 396) | function i(n,t,u,c,f,i,a){return o(u^(t|~c),n,t,f,i,a)}
  function a (line 396) | function a(n,o){var a,h,d,v,l;n[o>>5]|=128<<o%32,n[14+(o+64>>>9<<4)]=o;v...
  function h (line 396) | function h(n){var t,o='',u=32*n.length;for(t=0;t<u;t+=8)o+=String.fromCh...
  function d (line 396) | function d(n){var t,o=[];for(o[(n.length>>2)-1]=void 0,t=0;t<o.length;t+...
  function v (line 396) | function v(n){return h(a(d(n),8*n.length))}
  function l (line 396) | function l(n,t){var o,u,c=d(n),f=[],i=[];for(f[15]=i[15]=void 0,c.length...
  function p (line 396) | function p(n){var t,o,u='';for(o=0;o<n.length;o+=1)t=n.charCodeAt(o),u+=...
  function s (line 396) | function s(n){return unescape(encodeURIComponent(n))}
  function C (line 396) | function C(n){return v(s(n))}
  function A (line 396) | function A(n,t){return l(s(n),s(t))}
  function b (line 396) | function b(n,t,o){return t?o?A(t,n):p(A(t,n)):o?C(n):p(C(n))}
  function A (line 398) | function A(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){va...
  function v (line 398) | function v(e){for(var n=1;n<arguments.length;n++){var i=null!=arguments[...
  function w (line 398) | function w(e){var t=b();return function(){var n,o=(0,s.default)(e);if(t)...
  function b (line 398) | function b(){if("undefined"==typeof Reflect||!Reflect.construct)return!1...
  function k (line 398) | function k(e){return null!=e?e.replace(/\/*$/,'')+'/':null}
  function E (line 398) | function E(){return(E=(0,u.default)(function*(e){var t=arguments.length>...
  function F (line 398) | function F(e,t){return C.apply(this,arguments)}
  function C (line 398) | function C(){return(C=(0,u.default)(function*(e,t){if(!p.default.readAsS...
  function O (line 398) | function O(){return(O=(0,u.default)(function*(e){if('android'===y.Platfo...
  function x (line 398) | function x(e,t){return P.apply(this,arguments)}
  function P (line 398) | function P(){return(P=(0,u.default)(function*(e,t){var n=arguments.lengt...
  function R (line 398) | function R(e){return j.apply(this,arguments)}
  function j (line 398) | function j(){return(j=(0,u.default)(function*(e){var t=arguments.length>...
  function N (line 398) | function N(){return(N=(0,u.default)(function*(){if('android'===y.Platfor...
  function _ (line 398) | function _(e){return B.apply(this,arguments)}
  function B (line 398) | function B(){return(B=(0,u.default)(function*(e){if(!p.default.moveAsync...
  function I (line 398) | function I(e){return M.apply(this,arguments)}
  function M (line 398) | function M(){return(M=(0,u.default)(function*(e){if(!p.default.copyAsync...
  function q (line 398) | function q(){return(q=(0,u.default)(function*(e){var t=arguments.length>...
  function G (line 398) | function G(){return(G=(0,u.default)(function*(e){if(!p.default.readDirec...
  function K (line 398) | function K(){return(K=(0,u.default)(function*(){if(!p.default.getFreeDis...
  function W (line 398) | function W(){return(W=(0,u.default)(function*(){if(!p.default.getTotalDi...
  function L (line 398) | function L(){return(L=(0,u.default)(function*(e,t){var n=arguments.lengt...
  function Y (line 398) | function Y(){return(Y=(0,u.default)(function*(e,t){var n=arguments.lengt...
  function e (line 398) | function e(){(0,o.default)(this,e),this._uuid=(0,f.v4)(),this.taskWasCan...
  function i (line 398) | function i(e,n,s,l){var u,c;(0,o.default)(this,i),(c=t.call(this)).url=e...
  function i (line 398) | function i(e,n){var s,l=arguments.length>2&&void 0!==arguments[2]?argume...
  function t (line 398) | function t(){return(t=(0,u.default)(function*(){var e=arguments.length>0...
  function n (line 398) | function n(){return(n=(0,u.default)(function*(e){if(!p.default.readSAFDi...
  function i (line 398) | function i(){return(i=(0,u.default)(function*(e,t){if(!p.default.makeSAF...
  function s (line 398) | function s(){return(s=(0,u.default)(function*(e,t,n){if(!p.default.creat...
  method name (line 405) | get name(){return'ExponentFileSystem'}
  method documentDirectory (line 405) | get documentDirectory(){return null}
  method cacheDirectory (line 405) | get cacheDirectory(){return null}
  method bundledAssets (line 405) | get bundledAssets(){return null}
  method bundleDirectory (line 405) | get bundleDirectory(){return null}
  function s (line 407) | function s(t){var s=new n.default(t,{}).pathname;return s.substring(s.la...
  function s (line 409) | function s(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function o (line 410) | function o(t){if("function"!=typeof WeakMap)return null;var n=new WeakMa...
  function y (line 413) | function y(n){return n in s}
  function p (line 413) | function p(){return(p=(0,t.default)(function*(n,t){if('object'!=typeof n...
  function h (line 413) | function h(n,t){return A.apply(this,arguments)}
  function A (line 413) | function A(){return
Copy disabled (too large) Download .json
Condensed preview — 348 files, each showing path, character count, and a content snippet. Download the .json file for the full structured content (10,564K chars).
[
  {
    "path": ".github/FUNDING.yml",
    "chars": 98,
    "preview": "# These are supported funding model platforms\n\ngithub: [https://github.com/sponsors/axelmarciano]\n"
  },
  {
    "path": ".github/workflows/push.yml",
    "chars": 1264,
    "preview": "name: Push workflow\n\non:\n  push:\n    branches:\n      - '**'\n\npermissions:\n  contents: write\n\njobs:\n  test:\n    runs-on: "
  },
  {
    "path": ".github/workflows/release.yml",
    "chars": 4055,
    "preview": "name: Release Workflow\n\non:\n  push:\n    tags:\n      - \"v*\"\n\npermissions:\n  id-token: write\n  contents: write\n  packages:"
  },
  {
    "path": ".gitignore",
    "chars": 659,
    "preview": "# Binaries for programs and plugins\n*.exe\n*.exe~\n*.dll\n*.so\n*.dylib\n*.test\n.idea/\nkeys/\n\n# Output of the go coverage too"
  },
  {
    "path": "Dockerfile",
    "chars": 723,
    "preview": "FROM --platform=$BUILDPLATFORM node:24-alpine AS dashboard-builder\nWORKDIR /app/apps/dashboard\nCOPY apps/dashboard/packa"
  },
  {
    "path": "Dockerfile-ci",
    "chars": 448,
    "preview": "FROM node:18-alpine AS dashboard-builder\n\nWORKDIR /app/apps/dashboard\n\nCOPY apps/dashboard/package.json apps/dashboard/p"
  },
  {
    "path": "Dockerfile-dev",
    "chars": 989,
    "preview": "# Start with the official Golang base image\nFROM golang:1.24-alpine\n\n# Install necessary packages\nRUN apk add --no-cache"
  },
  {
    "path": "LICENSE.md",
    "chars": 1074,
    "preview": "MIT License\n\nCopyright (c) [2025] [Axel Marciano]\n\nPermission is hereby granted, free of charge, to any person obtaining"
  },
  {
    "path": "Makefile",
    "chars": 1262,
    "preview": "DOCKER_FLAG := $(findstring docker, $(MAKECMDGOALS))\nHTML_FLAG := $(findstring html, $(MAKECMDGOALS))\nMAKEFLAGS += --sil"
  },
  {
    "path": "README.md",
    "chars": 2834,
    "preview": "<p align=\"center\">\n  <img src=\"apps/docs/static/img/social_card.png\" alt=\"Expo Open OTA\" />\n  <img src=\"apps/docs/static"
  },
  {
    "path": "apps/dashboard/.gitignore",
    "chars": 253,
    "preview": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndis"
  },
  {
    "path": "apps/dashboard/.prettierrc",
    "chars": 166,
    "preview": "{\n  \"printWidth\": 100,\n  \"tabWidth\": 2,\n  \"singleQuote\": true,\n  \"bracketSameLine\": true,\n  \"trailingComma\": \"es5\",\n  \"a"
  },
  {
    "path": "apps/dashboard/README.md",
    "chars": 1607,
    "preview": "# React + TypeScript + Vite\n\nThis template provides a minimal setup to get React working in Vite with HMR and some ESLin"
  },
  {
    "path": "apps/dashboard/components.json",
    "chars": 440,
    "preview": "{\n  \"$schema\": \"https://ui.shadcn.com/schema.json\",\n  \"style\": \"new-york\",\n  \"rsc\": false,\n  \"tsx\": true,\n  \"tailwind\": "
  },
  {
    "path": "apps/dashboard/eslint.config.js",
    "chars": 734,
    "preview": "import js from '@eslint/js'\nimport globals from 'globals'\nimport reactHooks from 'eslint-plugin-react-hooks'\nimport reac"
  },
  {
    "path": "apps/dashboard/index.html",
    "chars": 418,
    "preview": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/"
  },
  {
    "path": "apps/dashboard/package.json",
    "chars": 1581,
    "preview": "{\n  \"name\": \"dashboard\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"VITE_OT"
  },
  {
    "path": "apps/dashboard/postcss.config.js",
    "chars": 80,
    "preview": "export default {\n  plugins: {\n    tailwindcss: {},\n    autoprefixer: {},\n  },\n}\n"
  },
  {
    "path": "apps/dashboard/public/env.js",
    "chars": 37,
    "preview": "// Will be overwritten by the server\n"
  },
  {
    "path": "apps/dashboard/src/App.tsx",
    "chars": 1163,
    "preview": "import { Layout } from '@/containers/Layout';\nimport { Route, Routes, useNavigate } from 'react-router';\nimport { isAuth"
  },
  {
    "path": "apps/dashboard/src/components/APIError/index.tsx",
    "chars": 439,
    "preview": "import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert.tsx';\nimport { AlertCircle } from 'lucide-rea"
  },
  {
    "path": "apps/dashboard/src/components/Combobox/index.tsx",
    "chars": 2199,
    "preview": "'use client';\n\nimport * as React from 'react';\nimport { Check, ChevronsUpDown } from 'lucide-react';\n\nimport { cn } from"
  },
  {
    "path": "apps/dashboard/src/components/DataTable/index.tsx",
    "chars": 3789,
    "preview": "import {\n  ColumnDef,\n  flexRender,\n  getCoreRowModel,\n  getSortedRowModel,\n  SortingState,\n  useReactTable,\n} from '@ta"
  },
  {
    "path": "apps/dashboard/src/components/UpdateDetailsSheet/index.tsx",
    "chars": 5895,
    "preview": "import { forwardRef, useImperativeHandle, useState } from 'react';\nimport {\n  Sheet,\n  SheetContent,\n  SheetDescription,"
  },
  {
    "path": "apps/dashboard/src/components/app-sidebar.tsx",
    "chars": 2246,
    "preview": "import { Link, useLocation } from 'react-router';\nimport {\n  Sidebar,\n  SidebarContent,\n  SidebarFooter,\n  SidebarGroup,"
  },
  {
    "path": "apps/dashboard/src/components/ui/alert.tsx",
    "chars": 1598,
    "preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
  },
  {
    "path": "apps/dashboard/src/components/ui/badge.tsx",
    "chars": 1140,
    "preview": "import * as React from \"react\"\nimport { cva, type VariantProps } from \"class-variance-authority\"\n\nimport { cn } from \"@/"
  },
  {
    "path": "apps/dashboard/src/components/ui/breadcrumb.tsx",
    "chars": 2712,
    "preview": "import * as React from \"react\"\nimport { Slot } from \"@radix-ui/react-slot\"\nimport { ChevronRight, MoreHorizontal } from "
  },
  {
    "path": "apps/dashboard/src/components/ui/button.tsx",
    "chars": 1853,
    "preview": "import * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { cva, type VariantProps } from 'cla"
  },
  {
    "path": "apps/dashboard/src/components/ui/card.tsx",
    "chars": 1811,
    "preview": "import * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst Card = React.forwardRef<HTMLDivElement, React."
  },
  {
    "path": "apps/dashboard/src/components/ui/command.tsx",
    "chars": 4856,
    "preview": "import * as React from 'react';\nimport { type DialogProps } from '@radix-ui/react-dialog';\nimport { Command as CommandPr"
  },
  {
    "path": "apps/dashboard/src/components/ui/dialog.tsx",
    "chars": 3849,
    "preview": "\"use client\"\n\nimport * as React from \"react\"\nimport * as DialogPrimitive from \"@radix-ui/react-dialog\"\nimport { X } from"
  },
  {
    "path": "apps/dashboard/src/components/ui/form.tsx",
    "chars": 4108,
    "preview": "import * as React from 'react';\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport { Slot } from '@radix-ui"
  },
  {
    "path": "apps/dashboard/src/components/ui/input.tsx",
    "chars": 774,
    "preview": "import * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst Input = React.forwardRef<HTMLInputElement, Rea"
  },
  {
    "path": "apps/dashboard/src/components/ui/label.tsx",
    "chars": 715,
    "preview": "'use client';\n\nimport * as React from 'react';\nimport * as LabelPrimitive from '@radix-ui/react-label';\nimport { cva, ty"
  },
  {
    "path": "apps/dashboard/src/components/ui/popover.tsx",
    "chars": 1301,
    "preview": "import * as React from 'react';\nimport * as PopoverPrimitive from '@radix-ui/react-popover';\n\nimport { cn } from '@/lib/"
  },
  {
    "path": "apps/dashboard/src/components/ui/progress.tsx",
    "chars": 763,
    "preview": "import * as React from 'react';\nimport * as ProgressPrimitive from '@radix-ui/react-progress';\n\nimport { cn } from '@/li"
  },
  {
    "path": "apps/dashboard/src/components/ui/separator.tsx",
    "chars": 722,
    "preview": "import * as React from 'react';\nimport * as SeparatorPrimitive from '@radix-ui/react-separator';\n\nimport { cn } from '@/"
  },
  {
    "path": "apps/dashboard/src/components/ui/sheet.tsx",
    "chars": 4227,
    "preview": "'use client';\n\nimport * as React from 'react';\nimport * as SheetPrimitive from '@radix-ui/react-dialog';\nimport { cva, t"
  },
  {
    "path": "apps/dashboard/src/components/ui/sidebar.tsx",
    "chars": 23555,
    "preview": "import * as React from 'react';\nimport { Slot } from '@radix-ui/react-slot';\nimport { VariantProps, cva } from 'class-va"
  },
  {
    "path": "apps/dashboard/src/components/ui/skeleton.tsx",
    "chars": 239,
    "preview": "import { cn } from '@/lib/utils';\n\nfunction Skeleton({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n  "
  },
  {
    "path": "apps/dashboard/src/components/ui/table.tsx",
    "chars": 2815,
    "preview": "import * as React from 'react';\n\nimport { cn } from '@/lib/utils';\n\nconst Table = React.forwardRef<HTMLTableElement, Rea"
  },
  {
    "path": "apps/dashboard/src/components/ui/toast.tsx",
    "chars": 4834,
    "preview": "import * as React from 'react';\nimport * as ToastPrimitives from '@radix-ui/react-toast';\nimport { cva, type VariantProp"
  },
  {
    "path": "apps/dashboard/src/components/ui/toaster.tsx",
    "chars": 743,
    "preview": "import { useToast } from '@/hooks/use-toast';\nimport {\n  Toast,\n  ToastClose,\n  ToastDescription,\n  ToastProvider,\n  Toa"
  },
  {
    "path": "apps/dashboard/src/components/ui/tooltip.tsx",
    "chars": 1212,
    "preview": "import * as React from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\n\nimport { cn } from '@/lib/"
  },
  {
    "path": "apps/dashboard/src/containers/Layout/index.tsx",
    "chars": 439,
    "preview": "import { SidebarProvider, SidebarTrigger } from '@/components/ui/sidebar';\nimport { AppSidebar } from '@/components/app-"
  },
  {
    "path": "apps/dashboard/src/hooks/use-mobile.tsx",
    "chars": 565,
    "preview": "import * as React from \"react\"\n\nconst MOBILE_BREAKPOINT = 768\n\nexport function useIsMobile() {\n  const [isMobile, setIsM"
  },
  {
    "path": "apps/dashboard/src/hooks/use-toast.ts",
    "chars": 4004,
    "preview": "'use client';\n\n// Inspired by react-hot-toast library\nimport * as React from 'react';\n\nimport type { ToastActionElement,"
  },
  {
    "path": "apps/dashboard/src/index.css",
    "chars": 2386,
    "preview": "@tailwind base;\n@tailwind components;\n@tailwind utilities;\n\n@layer base {\n  :root {\n    --radius: 0.5rem;\n    --backgrou"
  },
  {
    "path": "apps/dashboard/src/lib/api.ts",
    "chars": 5288,
    "preview": "import { getRefreshToken, getToken, logout, setTokens } from '@/lib/auth.ts';\n\nexport class ApiClient {\n  private baseUr"
  },
  {
    "path": "apps/dashboard/src/lib/auth.ts",
    "chars": 571,
    "preview": "export const isAuthenticated = () => {\n  return !!localStorage.getItem('token') && !!localStorage.getItem('refreshToken'"
  },
  {
    "path": "apps/dashboard/src/lib/utils.ts",
    "chars": 166,
    "preview": "import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\nexport function cn(...inputs: Cla"
  },
  {
    "path": "apps/dashboard/src/main.tsx",
    "chars": 544,
    "preview": "import { BrowserRouter } from 'react-router';\nimport { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/"
  },
  {
    "path": "apps/dashboard/src/pages/Channels/components/SelectBranch/index.tsx",
    "chars": 1027,
    "preview": "import { useQuery } from '@tanstack/react-query';\nimport { api } from '@/lib/api.ts';\nimport { ApiError } from '@/compon"
  },
  {
    "path": "apps/dashboard/src/pages/Channels/index.tsx",
    "chars": 2805,
    "preview": "import { useMutation, useQuery } from '@tanstack/react-query';\nimport { api } from '@/lib/api.ts';\nimport { ApiError } f"
  },
  {
    "path": "apps/dashboard/src/pages/Login/index.tsx",
    "chars": 2298,
    "preview": "import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card.tsx';\nimport { Input } from '@/components"
  },
  {
    "path": "apps/dashboard/src/pages/Logout/index.tsx",
    "chars": 280,
    "preview": "import { useEffect } from 'react';\nimport { logout } from '@/lib/auth.ts';\nimport { useNavigate } from 'react-router';\n\n"
  },
  {
    "path": "apps/dashboard/src/pages/Settings/index.tsx",
    "chars": 895,
    "preview": "import { useQuery } from '@tanstack/react-query';\nimport { api } from '@/lib/api.ts';\nimport { DataTable } from '@/compo"
  },
  {
    "path": "apps/dashboard/src/pages/Updates/components/BranchesTable/index.tsx",
    "chars": 1870,
    "preview": "import { useQuery } from '@tanstack/react-query';\nimport { api } from '@/lib/api.ts';\nimport { ApiError } from '@/compon"
  },
  {
    "path": "apps/dashboard/src/pages/Updates/components/RuntimeVersionsTable/index.tsx",
    "chars": 3634,
    "preview": "import { useQuery } from '@tanstack/react-query';\nimport { api } from '@/lib/api.ts';\nimport { ApiError } from '@/compon"
  },
  {
    "path": "apps/dashboard/src/pages/Updates/components/UpdatesTable/index.tsx",
    "chars": 4884,
    "preview": "import { useQuery } from '@tanstack/react-query';\nimport { api } from '@/lib/api.ts';\nimport { ApiError } from '@/compon"
  },
  {
    "path": "apps/dashboard/src/pages/Updates/index.tsx",
    "chars": 982,
    "preview": "import { useSearchParams } from 'react-router';\nimport { useMemo } from 'react';\nimport { BranchesTable } from '@/pages/"
  },
  {
    "path": "apps/dashboard/src/vite-env.d.ts",
    "chars": 38,
    "preview": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "apps/dashboard/tailwind.config.js",
    "chars": 1979,
    "preview": "/** @type {import('tailwindcss').Config} */\nexport default {\n    darkMode: [\"class\"],\n    content: [\"./index.html\", \"./s"
  },
  {
    "path": "apps/dashboard/tsconfig.app.json",
    "chars": 748,
    "preview": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n    \"target\": \"ES2020\",\n"
  },
  {
    "path": "apps/dashboard/tsconfig.json",
    "chars": 213,
    "preview": "{\n  \"files\": [],\n  \"references\": [\n    { \"path\": \"./tsconfig.app.json\" },\n    { \"path\": \"./tsconfig.node.json\" }\n  ],\n  "
  },
  {
    "path": "apps/dashboard/tsconfig.node.json",
    "chars": 593,
    "preview": "{\n  \"compilerOptions\": {\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n    \"target\": \"ES2022\","
  },
  {
    "path": "apps/dashboard/vite.config.ts",
    "chars": 308,
    "preview": "import { defineConfig } from 'vite';\nimport react from '@vitejs/plugin-react-swc';\nimport * as path from 'node:path';\n\n/"
  },
  {
    "path": "apps/docs/.gitignore",
    "chars": 233,
    "preview": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\n\n# Misc\n.DS_Store\n.env.lo"
  },
  {
    "path": "apps/docs/README.md",
    "chars": 768,
    "preview": "# Website\n\nThis website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.\n\n### Ins"
  },
  {
    "path": "apps/docs/docs/advanced/_category_.json",
    "chars": 43,
    "preview": "{\n  \"label\": \"Advanced\",\n  \"position\": 6\n}\n"
  },
  {
    "path": "apps/docs/docs/advanced/prometheus.mdx",
    "chars": 22882,
    "preview": "---\nsidebar_position: 1\n---\n\n# Prometheus and Grafana\n\n## Prometheus\n\nYou can monitor the **Expo Open OTA** server using"
  },
  {
    "path": "apps/docs/docs/dashboard.mdx",
    "chars": 1508,
    "preview": "---\nsidebar_position: 5\n---\nimport useBaseUrl from '@docusaurus/useBaseUrl';\n\n# Dashboard\n\nThe **Expo-Open-OTA Dashboard"
  },
  {
    "path": "apps/docs/docs/deployment/_category_.json",
    "chars": 202,
    "preview": "{\n  \"label\": \"Deployment\",\n  \"position\": 3,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"title\": \"Deployment\",\n    \"d"
  },
  {
    "path": "apps/docs/docs/deployment/custom.mdx",
    "chars": 894,
    "preview": "---\nsidebar_position: 3\n---\n# Custom Deployment\nDeploy **Expo Open OTA** on your own infrastructure with docker.\n\n## Pul"
  },
  {
    "path": "apps/docs/docs/deployment/helm.mdx",
    "chars": 2920,
    "preview": "---\nsidebar_position: 2\n---\n# Helm\nDeploy **Expo Open OTA** using Helm, a package manager for Kubernetes.\n\nA ready-to-us"
  },
  {
    "path": "apps/docs/docs/deployment/railway.mdx",
    "chars": 501,
    "preview": "---\nsidebar_position: 1\n---\n# Railway\nDeploy **Expo Open OTA** using Railway, a platform for deploying and managing appl"
  },
  {
    "path": "apps/docs/docs/deployment/testing.mdx",
    "chars": 2804,
    "preview": "---\nsidebar_position: 3\n---\n# Local testing\nIf you want to test **Expo Open OTA** locally, you can use the provided dock"
  },
  {
    "path": "apps/docs/docs/eoas/_category_.json",
    "chars": 112,
    "preview": "{\n  \"label\": \"Configure Your App\",\n  \"position\": 2,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"eoas/intro\"\n  }\n}\n"
  },
  {
    "path": "apps/docs/docs/eoas/configure.mdx",
    "chars": 1040,
    "preview": "---\nsidebar_position: 2\n---\n\n# Configure your Expo Project\n\n## Prerequisites\nA running and deployed **Expo Open OTA** se"
  },
  {
    "path": "apps/docs/docs/eoas/intro.mdx",
    "chars": 1539,
    "preview": "---\nsidebar_position: 1\n---\n\n# EOAS\n\nEOAS (**Expo Open Application Service**) is an npm package maintained by the **Expo"
  },
  {
    "path": "apps/docs/docs/eoas/publish.mdx",
    "chars": 3039,
    "preview": "---\nsidebar_position: 3\n---\n\n# Publish OTA updates\n\n## Runtime version\n\nEOAS uses official Expo packages to resolve the "
  },
  {
    "path": "apps/docs/docs/eoas/republish.mdx",
    "chars": 774,
    "preview": "---\nsidebar_position: 5\n---\n\n# Republish\n\nThe `republish` command lets you resend an existing update to a specified bran"
  },
  {
    "path": "apps/docs/docs/eoas/rollback.mdx",
    "chars": 560,
    "preview": "---\nsidebar_position: 4\n---\n\n# Rollback\n\nThe `rollback` command allows you to publish a rollback update to a specific br"
  },
  {
    "path": "apps/docs/docs/getting-started/_category_.json",
    "chars": 127,
    "preview": "{\n  \"label\": \"Getting Started\",\n  \"position\": 1,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"getting-started/introduction\""
  },
  {
    "path": "apps/docs/docs/getting-started/introduction.mdx",
    "chars": 4588,
    "preview": "---\nsidebar_position: 1\n---\n\n# Introduction\n\n**Expo Open OTA** is an open-source, multi-cloud OTA update server for Expo"
  },
  {
    "path": "apps/docs/docs/getting-started/prerequisites.mdx",
    "chars": 2343,
    "preview": "---\nsidebar_position: 2\n---\n\n# Prerequisites\n\nTo get started with **Expo Open OTA**, you need to review the following pr"
  },
  {
    "path": "apps/docs/docs/getting-started/quick-start.mdx",
    "chars": 3521,
    "preview": "---\nsidebar_position: 3\n---\n\n# Quick Start\n\nGet your first OTA update running in minutes with a minimal local setup.\n\n::"
  },
  {
    "path": "apps/docs/docs/reference/_category_.json",
    "chars": 191,
    "preview": "{\n  \"label\": \"Reference\",\n  \"position\": 7,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"title\": \"Reference\",\n    \"des"
  },
  {
    "path": "apps/docs/docs/reference/environment.mdx",
    "chars": 8823,
    "preview": "---\nsidebar_position: 1\n---\n\n# Environment variables\n\nThe **Expo Open OTA** server requires several environment variable"
  },
  {
    "path": "apps/docs/docs/server-configuration/_category_.json",
    "chars": 244,
    "preview": "{\n  \"label\": \"Server Configuration\",\n  \"position\": 4,\n  \"link\": {\n    \"type\": \"generated-index\",\n    \"title\": \"Server Co"
  },
  {
    "path": "apps/docs/docs/server-configuration/cache.mdx",
    "chars": 3846,
    "preview": "---\nsidebar_position: 3\nid: cache\n---\n\n# Caching\n\nThe **Expo Open OTA server** uses a cache to improve performance and r"
  },
  {
    "path": "apps/docs/docs/server-configuration/cdn/_category_.json",
    "chars": 117,
    "preview": "{\n  \"label\": \"CDN\",\n  \"position\": 4,\n  \"link\": {\n    \"type\": \"doc\",\n    \"id\": \"server-configuration/cdn/intro\"\n  }\n}\n"
  },
  {
    "path": "apps/docs/docs/server-configuration/cdn/cloudfront.mdx",
    "chars": 5478,
    "preview": "---\nsidebar_position: 2\n---\n\n# Cloudfront\nimport BrowserWindow from '@site/src/components/BrowserWindow';\nimport Tabs fr"
  },
  {
    "path": "apps/docs/docs/server-configuration/cdn/generic.mdx",
    "chars": 917,
    "preview": "---\nsidebar_position: 2\n---\n\n# Generic\n\nThe generic CDN feature requires your storage mode to be set to `s3`. You can fo"
  },
  {
    "path": "apps/docs/docs/server-configuration/cdn/intro.mdx",
    "chars": 528,
    "preview": "---\nsidebar_position: 1\n---\nimport DocCardList from '@theme/DocCardList';\n\n# CDN\n\nThe CDN feature in **Expo Open OTA** a"
  },
  {
    "path": "apps/docs/docs/server-configuration/key-store.mdx",
    "chars": 4467,
    "preview": "---\nsidebar_position: 2\n---\n\n# Key Store\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n\nThe **"
  },
  {
    "path": "apps/docs/docs/server-configuration/storage.mdx",
    "chars": 5149,
    "preview": "---\nsidebar_position: 1\nid: storage\n---\n\n# Storage\n\n**Expo Open OTA** supports multiple storage solutions for hosting yo"
  },
  {
    "path": "apps/docs/docusaurus.config.ts",
    "chars": 3428,
    "preview": "import {themes as prismThemes} from 'prism-react-renderer';\nimport type {Config} from '@docusaurus/types';\nimport type *"
  },
  {
    "path": "apps/docs/package.json",
    "chars": 1316,
    "preview": "{\n  \"name\": \"docs\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"start\":"
  },
  {
    "path": "apps/docs/sidebars.ts",
    "chars": 634,
    "preview": "import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';\n\n// This runs in Node.js - Don't use client-side co"
  },
  {
    "path": "apps/docs/src/components/BrowserWindow/index.tsx",
    "chars": 1501,
    "preview": "import React, {type CSSProperties, type ReactNode} from 'react';\nimport clsx from 'clsx';\n\nimport styles from './styles."
  },
  {
    "path": "apps/docs/src/components/BrowserWindow/styles.module.css",
    "chars": 1684,
    "preview": "/**\n * Copyright (c) Facebook, Inc. and its affiliates.\n *\n * This source code is licensed under the MIT license found i"
  },
  {
    "path": "apps/docs/src/components/HomepageFeatures/index.tsx",
    "chars": 1923,
    "preview": "import type {ReactNode} from 'react';\nimport clsx from 'clsx';\nimport Heading from '@theme/Heading';\nimport styles from "
  },
  {
    "path": "apps/docs/src/components/HomepageFeatures/styles.module.css",
    "chars": 138,
    "preview": ".features {\n  display: flex;\n  align-items: center;\n  padding: 2rem 0;\n  width: 100%;\n}\n\n.featureSvg {\n  height: 200px;\n"
  },
  {
    "path": "apps/docs/src/css/custom.css",
    "chars": 1215,
    "preview": ":root {\n  --ifm-color-primary: #9d4edd; /* Rich premium pink/purple */\n  --ifm-color-primary-dark: #3c096c; /* Slightly "
  },
  {
    "path": "apps/docs/src/pages/index.module.css",
    "chars": 1194,
    "preview": "/**\n * CSS files with the .module.css suffix will be treated as CSS modules\n * and scoped locally.\n */\n\n.heroBanner {\n  "
  },
  {
    "path": "apps/docs/src/pages/index.tsx",
    "chars": 1536,
    "preview": "import type {ReactNode} from 'react';\nimport clsx from 'clsx';\nimport Link from '@docusaurus/Link';\nimport useDocusaurus"
  },
  {
    "path": "apps/docs/src/pages/markdown-page.md",
    "chars": 118,
    "preview": "---\ntitle: Markdown page example\n---\n\n# Markdown page example\n\nYou don't need React to write simple standalone pages.\n"
  },
  {
    "path": "apps/docs/static/.nojekyll",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "apps/docs/tsconfig.json",
    "chars": 215,
    "preview": "{\n  // This file is not used in compilation. It is here just for a nice editor experience.\n  \"extends\": \"@docusaurus/tsc"
  },
  {
    "path": "apps/eoas/.eslintignore",
    "chars": 13,
    "preview": "node_modules\n"
  },
  {
    "path": "apps/eoas/.eslintrc.js",
    "chars": 2227,
    "preview": "module.exports = {\n  root: true,\n  extends: ['universe/node'],\n  plugins: ['node'],\n  ignorePatterns: ['bin/'],\n  rules:"
  },
  {
    "path": "apps/eoas/.gitignore",
    "chars": 87,
    "preview": "node_modules\n*-debug.log\n*-error.log\n.DS_Store\n\n.idea\n.vscode\n.history\n\ncoverage\ndist/\n"
  },
  {
    "path": "apps/eoas/.prettierrc",
    "chars": 166,
    "preview": "{\n  \"printWidth\": 100,\n  \"tabWidth\": 2,\n  \"singleQuote\": true,\n  \"bracketSameLine\": true,\n  \"trailingComma\": \"es5\",\n  \"a"
  },
  {
    "path": "apps/eoas/README.md",
    "chars": 696,
    "preview": "# EOAS (Expo Open Application Services)\n\nEOAS ((Expo Open Application Services) is a powerful helper package designed to"
  },
  {
    "path": "apps/eoas/package.json",
    "chars": 2637,
    "preview": "{\n  \"name\": \"eoas\",\n  \"version\": \"2.2.2\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"build\": \"tsc --project tsconfig.json"
  },
  {
    "path": "apps/eoas/src/commands/generate-certs.ts",
    "chars": 3584,
    "preview": "import {\n  convertCertificateToCertificatePEM,\n  convertKeyPairToPEM,\n  generateKeyPair,\n  generateSelfSignedCodeSigning"
  },
  {
    "path": "apps/eoas/src/commands/init.ts",
    "chars": 3921,
    "preview": "import { Command } from '@oclif/core';\nimport fs from 'fs-extra';\nimport path from 'path';\n\nimport {\n  createOrModifyExp"
  },
  {
    "path": "apps/eoas/src/commands/publish.ts",
    "chars": 15118,
    "preview": "import { Env, Platform } from '@expo/eas-build-job';\nimport spawnAsync from '@expo/spawn-async';\nimport { Command, Flags"
  },
  {
    "path": "apps/eoas/src/commands/republish.ts",
    "chars": 6303,
    "preview": "import { Env } from '@expo/eas-build-job';\nimport { Command, Flags } from '@oclif/core';\nimport ora from 'ora';\n\nimport "
  },
  {
    "path": "apps/eoas/src/commands/rollback.ts",
    "chars": 6132,
    "preview": "import { Env, Platform } from '@expo/eas-build-job';\nimport { Command, Flags } from '@oclif/core';\n\nimport { getAuthExpo"
  },
  {
    "path": "apps/eoas/src/index.d.ts",
    "chars": 164,
    "preview": "declare module 'better-opn' {\n  function open(\n    target: string,\n    options?: any\n  ): Promise<import('child_process'"
  },
  {
    "path": "apps/eoas/src/lib/assets.ts",
    "chars": 4326,
    "preview": "// This file is partially copied from eas-cli[https://github.com/expo/eas-cli] to ensure consistent user experience acro"
  },
  {
    "path": "apps/eoas/src/lib/auth.ts",
    "chars": 1660,
    "preview": "import { homedir } from 'os';\nimport path from 'path';\n\nexport interface ExpoCredentials {\n  token?: string;\n  sessionSe"
  },
  {
    "path": "apps/eoas/src/lib/channel.ts",
    "chars": 986,
    "preview": "import { ExpoCredentials, getAuthExpoHeaders } from './auth';\nimport { fetchWithRetries } from './fetch';\n\nexport async "
  },
  {
    "path": "apps/eoas/src/lib/expoConfig.ts",
    "chars": 8995,
    "preview": "// This file is copied from eas-cli[https://github.com/expo/eas-cli] to ensure consistent user experience across the CLI"
  },
  {
    "path": "apps/eoas/src/lib/fetch.ts",
    "chars": 633,
    "preview": "import fetchRetry from 'fetch-retry';\nimport originalFetch, { RequestInit, Response } from 'node-fetch';\n\nimport Log fro"
  },
  {
    "path": "apps/eoas/src/lib/log.ts",
    "chars": 3522,
    "preview": "// This file is copied from eas-cli[https://github.com/expo/eas-cli] to ensure consistent user experience across the CLI"
  },
  {
    "path": "apps/eoas/src/lib/ora.ts",
    "chars": 3306,
    "preview": "// This file is copied from eas-cli[https://github.com/expo/eas-cli] to ensure consistent user experience across the CLI"
  },
  {
    "path": "apps/eoas/src/lib/package.ts",
    "chars": 240,
    "preview": "import { getPackageJson } from '@expo/config';\n\nexport function isExpoInstalled(projectDir: string): boolean {\n  const p"
  },
  {
    "path": "apps/eoas/src/lib/packageRunner.ts",
    "chars": 2178,
    "preview": "import fs from 'fs-extra';\nimport path from 'path';\n\nconst DEFAULT_PACKAGE_RUNNER = 'npx';\n\nconst VALID_RUNNER_RE = /^[a"
  },
  {
    "path": "apps/eoas/src/lib/prompts.ts",
    "chars": 2452,
    "preview": "// This file is copied from eas-cli[https://github.com/expo/eas-cli] to ensure consistent user experience across the CLI"
  },
  {
    "path": "apps/eoas/src/lib/repo.ts",
    "chars": 1710,
    "preview": "// This file is copied from eas-cli[https://github.com/expo/eas-cli] to ensure consistent user experience across the CLI"
  },
  {
    "path": "apps/eoas/src/lib/runtimeVersion.ts",
    "chars": 5384,
    "preview": "import { ExpoConfig } from '@expo/config';\nimport { Updates } from '@expo/config-plugins';\nimport { Env, Workflow } from"
  },
  {
    "path": "apps/eoas/src/lib/utils.ts",
    "chars": 122,
    "preview": "export function isValidUpdateUrl(updateUrl: string): boolean {\n  return updateUrl.match(/^https?:\\/\\/[^/]+$/) !== null;\n"
  },
  {
    "path": "apps/eoas/src/lib/vcs/README.md",
    "chars": 107,
    "preview": "This library is copied from eas-cli[https://github.com/expo/eas-cli] to ensure consistent user experience.\n"
  },
  {
    "path": "apps/eoas/src/lib/vcs/clients/git.ts",
    "chars": 11773,
    "preview": "import * as PackageManagerUtils from '@expo/package-manager';\nimport spawnAsync from '@expo/spawn-async';\nimport { Error"
  },
  {
    "path": "apps/eoas/src/lib/vcs/clients/gitNoCommit.ts",
    "chars": 1659,
    "preview": "import spawnAsync from '@expo/spawn-async';\nimport chalk from 'chalk';\nimport path from 'path';\n\nimport GitClient from '"
  },
  {
    "path": "apps/eoas/src/lib/vcs/clients/noVcs.ts",
    "chars": 698,
    "preview": "import { Ignore, getRootPath, makeShallowCopyAsync } from '../local';\nimport { Client } from '../vcs';\n\nexport default c"
  },
  {
    "path": "apps/eoas/src/lib/vcs/git.ts",
    "chars": 1563,
    "preview": "import spawnAsync from '@expo/spawn-async';\n\nexport async function isGitInstalledAsync(): Promise<boolean> {\n  try {\n   "
  },
  {
    "path": "apps/eoas/src/lib/vcs/index.ts",
    "chars": 877,
    "preview": "import chalk from 'chalk';\n\nimport GitClient from './clients/git';\nimport GitNoCommitClient from './clients/gitNoCommit'"
  },
  {
    "path": "apps/eoas/src/lib/vcs/local.ts",
    "chars": 2690,
    "preview": "import fg from 'fast-glob';\nimport fs from 'fs-extra';\nimport createIgnore, { Ignore as SingleFileIgnore } from 'ignore'"
  },
  {
    "path": "apps/eoas/src/lib/vcs/vcs.ts",
    "chars": 3836,
    "preview": "export abstract class Client {\n  // makeShallowCopyAsync should copy current project (result of getRootPathAsync()) to t"
  },
  {
    "path": "apps/eoas/src/lib/workflow.ts",
    "chars": 1405,
    "preview": "import { AndroidConfig, IOSConfig } from '@expo/config-plugins';\nimport { Platform, Workflow } from '@expo/eas-build-job"
  },
  {
    "path": "apps/eoas/tsconfig.json",
    "chars": 421,
    "preview": "{\n  \"extends\": \"@tsconfig/node18/tsconfig.json\",\n  \"compilerOptions\": {\n    \"declaration\": true,\n    \"importHelpers\": tr"
  },
  {
    "path": "apps/example-app/.eslintrc.json",
    "chars": 10405,
    "preview": "{\n  \"root\": true,\n  \"globals\": {\n    \"expect\": true,\n    \"NodeJS\": true,\n    \"React\": true,\n    \"JSX\": true,\n    \"__DEV_"
  },
  {
    "path": "apps/example-app/.gitignore",
    "chars": 416,
    "preview": "# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files\n\n# dependencies\nnode_modules"
  },
  {
    "path": "apps/example-app/.prettierignore",
    "chars": 18,
    "preview": "*.html\ntypings.ts\n"
  },
  {
    "path": "apps/example-app/.prettierrc",
    "chars": 201,
    "preview": "{\n  \"parser\": \"typescript\",\n  \"arrowParens\": \"avoid\",\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"semi\": false,\n"
  },
  {
    "path": "apps/example-app/README.md",
    "chars": 1739,
    "preview": "# Welcome to your Expo app 👋\n\nThis is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.n"
  },
  {
    "path": "apps/example-app/app/+not-found.tsx",
    "chars": 792,
    "preview": "import { Link, Stack } from 'expo-router';\nimport { StyleSheet } from 'react-native';\n\nimport { ThemedText } from '@/com"
  },
  {
    "path": "apps/example-app/app/_layout.tsx",
    "chars": 1107,
    "preview": "import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';\nimport { useFonts } from 'expo-font';"
  },
  {
    "path": "apps/example-app/app/index.tsx",
    "chars": 3717,
    "preview": "import {\n  Platform,\n  SafeAreaView,\n  ScrollView,\n  Alert,\n  StyleSheet,\n  Button,\n  ActivityIndicator,\n  View,\n} from "
  },
  {
    "path": "apps/example-app/app.config.ts",
    "chars": 567,
    "preview": "import { ExpoConfig } from '@expo/config-types'\nimport { ConfigContext } from '@expo/config'\n\nexport default ({ config }"
  },
  {
    "path": "apps/example-app/app.json",
    "chars": 1208,
    "preview": "{\n  \"expo\": {\n    \"name\": \"example-app\",\n    \"slug\": \"example-app\",\n    \"version\": \"1.0.0\",\n    \"orientation\": \"portrait"
  },
  {
    "path": "apps/example-app/components/LogViewer.tsx",
    "chars": 1965,
    "preview": "import React, { useState } from 'react'\nimport { View, Text, ScrollView, TouchableOpacity } from 'react-native'\nimport J"
  },
  {
    "path": "apps/example-app/components/ThemedText.tsx",
    "chars": 1283,
    "preview": "import { Text, type TextProps, StyleSheet } from 'react-native';\n\nimport { useThemeColor } from '@/hooks/useThemeColor';"
  },
  {
    "path": "apps/example-app/components/ThemedView.tsx",
    "chars": 468,
    "preview": "import { View, type ViewProps } from 'react-native';\n\nimport { useThemeColor } from '@/hooks/useThemeColor';\n\nexport typ"
  },
  {
    "path": "apps/example-app/components/__tests__/ThemedText-test.tsx",
    "chars": 275,
    "preview": "import * as React from 'react';\nimport renderer from 'react-test-renderer';\n\nimport { ThemedText } from '../ThemedText';"
  },
  {
    "path": "apps/example-app/components/__tests__/__snapshots__/ThemedText-test.tsx.snap",
    "chars": 338,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`renders correctly 1`] = `\n<Text\n  style={\n    [\n      {\n        \"co"
  },
  {
    "path": "apps/example-app/components/ui/IconSymbol.ios.tsx",
    "chars": 598,
    "preview": "import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';\nimport { StyleProp, ViewStyle } from 'react-na"
  },
  {
    "path": "apps/example-app/components/ui/IconSymbol.tsx",
    "chars": 1353,
    "preview": "// This file is a fallback for using MaterialIcons on Android and web.\n\nimport MaterialIcons from '@expo/vector-icons/Ma"
  },
  {
    "path": "apps/example-app/components/ui/TabBarBackground.ios.tsx",
    "chars": 697,
    "preview": "import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';\nimport { BlurView } from 'expo-blur';\nimport { St"
  },
  {
    "path": "apps/example-app/components/ui/TabBarBackground.tsx",
    "chars": 159,
    "preview": "// This is a shim for web and Android where the tab bar is generally opaque.\nexport default undefined;\n\nexport function "
  },
  {
    "path": "apps/example-app/constants/Colors.ts",
    "chars": 750,
    "preview": "/**\n * Below are the colors that are used in the app. The colors are defined in the light and dark mode.\n * There are ma"
  },
  {
    "path": "apps/example-app/hooks/useColorScheme.ts",
    "chars": 47,
    "preview": "export { useColorScheme } from 'react-native';\n"
  },
  {
    "path": "apps/example-app/hooks/useColorScheme.web.ts",
    "chars": 480,
    "preview": "import { useEffect, useState } from 'react';\nimport { useColorScheme as useRNColorScheme } from 'react-native';\n\n/**\n * "
  },
  {
    "path": "apps/example-app/hooks/useThemeColor.ts",
    "chars": 536,
    "preview": "/**\n * Learn more about light and dark modes:\n * https://docs.expo.dev/guides/color-schemes/\n */\n\nimport { Colors } from"
  },
  {
    "path": "apps/example-app/package.json",
    "chars": 2962,
    "preview": "{\n  \"name\": \"example-app\",\n  \"license\": \"0BSD\",\n  \"main\": \"expo-router/entry\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    "
  },
  {
    "path": "apps/example-app/scripts/network_security_config.xml",
    "chars": 293,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\">\n    "
  },
  {
    "path": "apps/example-app/scripts/reset-project.js",
    "chars": 3580,
    "preview": "#!/usr/bin/env node\n\n/**\n * This script is used to reset the project to a blank state.\n * It deletes or moves the /app, "
  },
  {
    "path": "apps/example-app/scripts/trust_local_certs.js",
    "chars": 1237,
    "preview": "const { AndroidConfig, withAndroidManifest } = require('@expo/config-plugins')\nconst { Paths } = require('@expo/config-p"
  },
  {
    "path": "apps/example-app/tsconfig.json",
    "chars": 242,
    "preview": "{\n  \"extends\": \"expo/tsconfig.base\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"paths\": {\n      \"@/*\": [\n        \"."
  },
  {
    "path": "apps/example-app-runtime-switch/.eslintrc.json",
    "chars": 10405,
    "preview": "{\n  \"root\": true,\n  \"globals\": {\n    \"expect\": true,\n    \"NodeJS\": true,\n    \"React\": true,\n    \"JSX\": true,\n    \"__DEV_"
  },
  {
    "path": "apps/example-app-runtime-switch/.gitignore",
    "chars": 416,
    "preview": "# Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files\n\n# dependencies\nnode_modules"
  },
  {
    "path": "apps/example-app-runtime-switch/.prettierignore",
    "chars": 18,
    "preview": "*.html\ntypings.ts\n"
  },
  {
    "path": "apps/example-app-runtime-switch/.prettierrc",
    "chars": 201,
    "preview": "{\n  \"parser\": \"typescript\",\n  \"arrowParens\": \"avoid\",\n  \"trailingComma\": \"all\",\n  \"singleQuote\": true,\n  \"semi\": false,\n"
  },
  {
    "path": "apps/example-app-runtime-switch/README.md",
    "chars": 1739,
    "preview": "# Welcome to your Expo app 👋\n\nThis is an [Expo](https://expo.dev) project created with [`create-expo-app`](https://www.n"
  },
  {
    "path": "apps/example-app-runtime-switch/app/+not-found.tsx",
    "chars": 792,
    "preview": "import { Link, Stack } from 'expo-router';\nimport { StyleSheet } from 'react-native';\n\nimport { ThemedText } from '@/com"
  },
  {
    "path": "apps/example-app-runtime-switch/app/_layout.tsx",
    "chars": 1107,
    "preview": "import { DarkTheme, DefaultTheme, ThemeProvider } from '@react-navigation/native';\nimport { useFonts } from 'expo-font';"
  },
  {
    "path": "apps/example-app-runtime-switch/app/index.tsx",
    "chars": 4242,
    "preview": "import {\n  Platform,\n  SafeAreaView,\n  ScrollView,\n  Alert,\n  StyleSheet,\n  Button,\n  ActivityIndicator,\n  View,\n} from "
  },
  {
    "path": "apps/example-app-runtime-switch/app.config.ts",
    "chars": 608,
    "preview": "import { ExpoConfig } from '@expo/config-types'\nimport { ConfigContext } from '@expo/config'\n\nexport default ({ config }"
  },
  {
    "path": "apps/example-app-runtime-switch/app.json",
    "chars": 1264,
    "preview": "{\n  \"expo\": {\n    \"name\": \"example-app-runtime-switch\",\n    \"slug\": \"example-app-runtime-switch\",\n    \"version\": \"1.0.0\""
  },
  {
    "path": "apps/example-app-runtime-switch/components/LogViewer.tsx",
    "chars": 1965,
    "preview": "import React, { useState } from 'react'\nimport { View, Text, ScrollView, TouchableOpacity } from 'react-native'\nimport J"
  },
  {
    "path": "apps/example-app-runtime-switch/components/ThemedText.tsx",
    "chars": 1283,
    "preview": "import { Text, type TextProps, StyleSheet } from 'react-native';\n\nimport { useThemeColor } from '@/hooks/useThemeColor';"
  },
  {
    "path": "apps/example-app-runtime-switch/components/ThemedView.tsx",
    "chars": 468,
    "preview": "import { View, type ViewProps } from 'react-native';\n\nimport { useThemeColor } from '@/hooks/useThemeColor';\n\nexport typ"
  },
  {
    "path": "apps/example-app-runtime-switch/components/__tests__/ThemedText-test.tsx",
    "chars": 275,
    "preview": "import * as React from 'react';\nimport renderer from 'react-test-renderer';\n\nimport { ThemedText } from '../ThemedText';"
  },
  {
    "path": "apps/example-app-runtime-switch/components/__tests__/__snapshots__/ThemedText-test.tsx.snap",
    "chars": 338,
    "preview": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`renders correctly 1`] = `\n<Text\n  style={\n    [\n      {\n        \"co"
  },
  {
    "path": "apps/example-app-runtime-switch/components/ui/IconSymbol.ios.tsx",
    "chars": 598,
    "preview": "import { SymbolView, SymbolViewProps, SymbolWeight } from 'expo-symbols';\nimport { StyleProp, ViewStyle } from 'react-na"
  },
  {
    "path": "apps/example-app-runtime-switch/components/ui/IconSymbol.tsx",
    "chars": 1353,
    "preview": "// This file is a fallback for using MaterialIcons on Android and web.\n\nimport MaterialIcons from '@expo/vector-icons/Ma"
  },
  {
    "path": "apps/example-app-runtime-switch/components/ui/TabBarBackground.ios.tsx",
    "chars": 697,
    "preview": "import { useBottomTabBarHeight } from '@react-navigation/bottom-tabs';\nimport { BlurView } from 'expo-blur';\nimport { St"
  },
  {
    "path": "apps/example-app-runtime-switch/components/ui/TabBarBackground.tsx",
    "chars": 159,
    "preview": "// This is a shim for web and Android where the tab bar is generally opaque.\nexport default undefined;\n\nexport function "
  },
  {
    "path": "apps/example-app-runtime-switch/constants/Colors.ts",
    "chars": 750,
    "preview": "/**\n * Below are the colors that are used in the app. The colors are defined in the light and dark mode.\n * There are ma"
  },
  {
    "path": "apps/example-app-runtime-switch/hooks/useColorScheme.ts",
    "chars": 47,
    "preview": "export { useColorScheme } from 'react-native';\n"
  },
  {
    "path": "apps/example-app-runtime-switch/hooks/useColorScheme.web.ts",
    "chars": 480,
    "preview": "import { useEffect, useState } from 'react';\nimport { useColorScheme as useRNColorScheme } from 'react-native';\n\n/**\n * "
  },
  {
    "path": "apps/example-app-runtime-switch/hooks/useThemeColor.ts",
    "chars": 536,
    "preview": "/**\n * Learn more about light and dark modes:\n * https://docs.expo.dev/guides/color-schemes/\n */\n\nimport { Colors } from"
  },
  {
    "path": "apps/example-app-runtime-switch/package.json",
    "chars": 2761,
    "preview": "{\n  \"name\": \"example-app\",\n  \"license\": \"0BSD\",\n  \"main\": \"expo-router/entry\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    "
  },
  {
    "path": "apps/example-app-runtime-switch/scripts/network_security_config.xml",
    "chars": 293,
    "preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config>\n    <base-config cleartextTrafficPermitted=\"true\">\n    "
  },
  {
    "path": "apps/example-app-runtime-switch/scripts/reset-project.js",
    "chars": 3580,
    "preview": "#!/usr/bin/env node\n\n/**\n * This script is used to reset the project to a blank state.\n * It deletes or moves the /app, "
  }
]

// ... and 148 more files (download for full content)

About this extraction

This page contains the full source code of the axelmarciano/expo-open-ota GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 348 files (9.9 MB), approximately 2.6M tokens, and a symbol index with 16156 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!