name: CI on: push: branches: - develop - main pull_request: branches: - develop - main workflow_dispatch: inputs: build_only: description: 'Build Docker image only (skip deploy)' type: boolean default: true jobs: tests: runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.4 tools: composer:v2 coverage: xdebug - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Install Node Dependencies run: bun install --frozen-lockfile - name: Install Dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader - name: Build Assets run: bun run build - name: Copy Environment File run: cp .env.example .env - name: Generate Application Key run: php artisan key:generate - name: Tests run: ./vendor/bin/pest --exclude-testsuite=Browser,Performance env: TESTCONTAINERS: false DB_CONNECTION: mysql DB_HOST: 127.0.0.1 DB_PORT: 3306 DB_DATABASE: testing DB_USERNAME: root DB_PASSWORD: password browser-tests: if: github.event_name == 'pull_request' runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.4 tools: composer:v2 - name: Setup Node uses: actions/setup-node@v4 with: node-version: lts/* - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Install Node Dependencies run: bun install --frozen-lockfile - name: Install Playwright Browsers run: npx playwright install --with-deps - name: Install PHP Dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader - name: Build Assets run: bun run build - name: Copy Environment File run: cp .env.example .env - name: Generate Application Key run: php artisan key:generate - name: Browser Tests run: ./vendor/bin/pest --testsuite=Browser --ci env: TESTCONTAINERS: false DB_CONNECTION: mysql DB_HOST: 127.0.0.1 DB_PORT: 3306 DB_DATABASE: testing DB_USERNAME: root DB_PASSWORD: password static-analysis: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.4' - name: Install PHP Dependencies run: composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist - name: Run PHPStan run: vendor/bin/phpstan analyse --memory-limit=512M --no-progress linter: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: '8.4' - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Install Dependencies run: | composer install -q --no-ansi --no-interaction --no-scripts --no-progress --prefer-dist bun install --frozen-lockfile - name: Run Pint run: vendor/bin/pint --test - name: Format Frontend run: bun run format - name: Lint Frontend run: bun run lint - name: Frontend Tests run: bun run test performance-tests: runs-on: ubuntu-latest services: mysql: image: mysql:8.0 env: MYSQL_ROOT_PASSWORD: password MYSQL_DATABASE: testing ports: - 3306:3306 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 steps: - name: Checkout uses: actions/checkout@v4 - name: Setup PHP uses: shivammathur/setup-php@v2 with: php-version: 8.4 tools: composer:v2 - name: Setup Bun uses: oven-sh/setup-bun@v2 - name: Install Node Dependencies run: bun install --frozen-lockfile - name: Install Dependencies run: composer install --no-interaction --prefer-dist --optimize-autoloader - name: Build Assets run: bun run build - name: Copy Environment File run: cp .env.example .env - name: Generate Application Key run: php artisan key:generate - name: Performance Tests run: ./vendor/bin/pest --testsuite=Performance env: TESTCONTAINERS: false DB_CONNECTION: mysql DB_HOST: 127.0.0.1 DB_PORT: 3306 DB_DATABASE: testing DB_USERNAME: root DB_PASSWORD: password build-image: runs-on: ubuntu-latest needs: [tests, linter, static-analysis, performance-tests] if: (github.ref == 'refs/heads/main' && github.event_name == 'push') || github.event_name == 'workflow_dispatch' env: SENTRY_RELEASE: whisper-money@${{ github.sha }} permissions: contents: read packages: write steps: - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 - name: Install Sentry CLI run: curl -sL https://sentry.io/get-cli/ | bash - name: Create Sentry release env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} run: | if ! sentry-cli releases info "$SENTRY_RELEASE" >/dev/null 2>&1; then sentry-cli releases new "$SENTRY_RELEASE" fi sentry-cli releases set-commits "$SENTRY_RELEASE" --auto sentry-cli releases finalize "$SENTRY_RELEASE" - name: Set up QEMU uses: docker/setup-qemu-action@v3 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 - name: Login to GitHub Container Registry uses: docker/login-action@v3 with: registry: ghcr.io username: ${{ github.actor }} password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata id: meta uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} tags: | type=sha,prefix= type=raw,value=latest - name: Build and push uses: docker/build-push-action@v6 with: context: . platforms: linux/amd64,linux/arm64 push: true tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | SENTRY_RELEASE=${{ env.SENTRY_RELEASE }} cache-from: type=gha cache-to: type=gha,mode=max deploy: runs-on: ubuntu-latest needs: [build-image] if: github.ref == 'refs/heads/main' && github.event_name == 'push' env: SENTRY_RELEASE: whisper-money@${{ github.sha }} concurrency: group: production-deploy-main cancel-in-progress: true steps: - name: Checkout uses: actions/checkout@v4 - name: Check if this is the latest main commit id: deploy_guard run: | latest_main_sha=$(git ls-remote origin refs/heads/main | cut -f1) echo "Current workflow SHA: ${{ github.sha }}" echo "Latest main SHA: $latest_main_sha" if [ -z "$latest_main_sha" ]; then echo "Unable to determine the latest main SHA." exit 1 fi if [ "$latest_main_sha" != "${{ github.sha }}" ]; then echo "This workflow is not for the latest commit on main. Skipping deploy." echo "should_deploy=false" >> "$GITHUB_OUTPUT" exit 0 fi echo "This workflow is for the latest commit on main. Proceeding with deploy." echo "should_deploy=true" >> "$GITHUB_OUTPUT" - name: Trigger deployment if: steps.deploy_guard.outputs.should_deploy == 'true' run: | max_attempts=5 attempt=1 while [ $attempt -le $max_attempts ]; do echo "Attempt $attempt of $max_attempts..." response=$(curl -s -w "\n%{http_code}" \ --connect-timeout 30 \ --max-time 120 \ -H "Authorization: Bearer ${{ secrets.DEPLOYMENT_TOKEN }}" \ "http://147.93.126.54:8000/api/v1/deploy?uuid=ww00sswosco8w80k08c0occ8&force=false") || true http_code=$(echo "$response" | tail -n1) body=$(echo "$response" | sed '$d') echo "Response: $body" echo "HTTP Status: ${http_code:-timeout}" if [ -n "$http_code" ] && [ "$http_code" -ge 200 ] && [ "$http_code" -lt 300 ]; then echo "Deployment triggered successfully!" exit 0 fi echo "Deployment request failed with status $http_code" if [ $attempt -lt $max_attempts ]; then delay=$((30 * (2 ** (attempt - 1)))) echo "Retrying in ${delay}s..." sleep $delay fi attempt=$((attempt + 1)) done echo "All $max_attempts attempts failed. Giving up." exit 1 - name: Install Sentry CLI if: steps.deploy_guard.outputs.should_deploy == 'true' run: curl -sL https://sentry.io/get-cli/ | bash - name: Mark Sentry release deployed if: steps.deploy_guard.outputs.should_deploy == 'true' env: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} SENTRY_ORG: ${{ secrets.SENTRY_ORG }} SENTRY_PROJECT: ${{ secrets.SENTRY_PROJECT }} run: sentry-cli releases deploys "$SENTRY_RELEASE" new -e production - name: Deployment skipped if: steps.deploy_guard.outputs.should_deploy == 'false' run: echo "Skipped deployment because a newer commit is already on main."