fix(stats): login loop, telemetry, collection dashboard, auto-deploy #1361
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Tests | |
| on: | |
| pull_request_target: | |
| branches: | |
| - main | |
| - v3 | |
| push: | |
| branches: | |
| - main | |
| - v3 | |
| jobs: | |
| # Authorization job - gates fork PRs behind environment approval | |
| authorize: | |
| runs-on: ubuntu-latest | |
| # Use 'external' environment (requires approval) for fork PRs, 'internal' for same-repo PRs | |
| environment: | |
| name: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.repo.full_name != github.repository && 'external' || 'internal' }} | |
| steps: | |
| - name: Authorize | |
| run: | | |
| echo "✅ Workflow authorized" | |
| echo "Event: ${{ github.event_name }}" | |
| echo "Repository: ${{ github.repository }}" | |
| echo "PR Head Repo: ${{ github.event.pull_request.head.repo.full_name || 'N/A' }}" | |
| echo "Is Fork: ${{ github.event.pull_request.head.repo.full_name != github.repository }}" | |
| test: | |
| needs: authorize | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 90 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v4 | |
| with: | |
| # For pull_request_target, we need to checkout the PR head | |
| ref: ${{ github.event_name == 'pull_request_target' && github.event.pull_request.head.sha || github.sha }} | |
| fetch-depth: 0 | |
| - name: Setup Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| - name: Install dependencies | |
| run: npm install | |
| - name: Type check | |
| run: npm run type-check | |
| - name: Run unit tests with coverage | |
| run: npm run test:cov | |
| - name: Upload coverage to Codecov | |
| uses: codecov/codecov-action@v4 | |
| with: | |
| token: ${{ secrets.CODECOV_TOKEN }} | |
| files: ./packages/core/coverage/coverage-final.json | |
| flags: unittests | |
| fail_ci_if_error: false | |
| verbose: true | |
| - name: Build core package | |
| run: npm run build:core | |
| # Skip Cloudflare deployment and E2E tests for Dependabot PRs (no access to secrets) | |
| - name: Create fresh D1 database for PR | |
| if: github.actor != 'dependabot[bot]' | |
| id: create-db | |
| run: | | |
| cd my-sonicjs-app | |
| BRANCH_NAME="${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" | |
| # Limit to 43 chars to allow for "sonicjs-pr-" prefix (11 chars) = 54 total | |
| # Remove trailing hyphens to avoid invalid worker names | |
| SAFE_BRANCH=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9-]/-/g' | cut -c1-43 | sed 's/-*$//') | |
| DB_NAME="sonicjs-pr-${SAFE_BRANCH}" | |
| echo "Creating fresh D1 database: $DB_NAME" | |
| # Check if database already exists | |
| EXISTING_DB=$(npx wrangler d1 list --json | jq -r ".[] | select(.name == \"$DB_NAME\") | .uuid" 2>/dev/null || echo "") | |
| if [ -n "$EXISTING_DB" ]; then | |
| echo "Database $DB_NAME already exists with ID: $EXISTING_DB" | |
| echo "Deleting existing database to ensure fresh migrations..." | |
| npx wrangler d1 delete "$DB_NAME" -y || true | |
| sleep 2 | |
| fi | |
| # Create new database (always fresh) | |
| CREATE_OUTPUT=$(npx wrangler d1 create "$DB_NAME" 2>&1) | |
| echo "$CREATE_OUTPUT" | |
| # Extract database ID from creation output | |
| DB_ID=$(echo "$CREATE_OUTPUT" | grep -oP 'database_id\s*=\s*"\K[^"]+' || echo "") | |
| if [ -z "$DB_ID" ]; then | |
| # Try alternative extraction method | |
| DB_ID=$(npx wrangler d1 list --json | jq -r ".[] | select(.name == \"$DB_NAME\") | .uuid") | |
| fi | |
| if [ -z "$DB_ID" ]; then | |
| echo "Error: Failed to get database ID" | |
| exit 1 | |
| fi | |
| echo "Database ID: $DB_ID" | |
| echo "db_id=$DB_ID" >> $GITHUB_OUTPUT | |
| echo "db_name=$DB_NAME" >> $GITHUB_OUTPUT | |
| # Update wrangler.toml with the new database ID | |
| sed -i "s/database_id = \"[^\"]*\"/database_id = \"$DB_ID\"/" wrangler.toml | |
| sed -i "s/database_name = \"[^\"]*\"/database_name = \"$DB_NAME\"/" wrangler.toml | |
| echo "Updated wrangler.toml with new database" | |
| grep -A2 "d1_databases" wrangler.toml | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| - name: Run D1 migrations | |
| if: github.actor != 'dependabot[bot]' | |
| run: | | |
| cd my-sonicjs-app | |
| echo "Applying migrations to database: ${{ steps.create-db.outputs.db_name }}" | |
| npx wrangler d1 migrations apply ${{ steps.create-db.outputs.db_name }} --remote | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| - name: Deploy to Cloudflare Workers Preview | |
| if: github.actor != 'dependabot[bot]' | |
| id: deploy | |
| run: | | |
| cd my-sonicjs-app | |
| # Deploy to preview with unique name based on PR/branch | |
| # Cloudflare Workers has a 54 character limit for script names with previews | |
| # Prefix "sonicjs-pr-" is 11 chars, so max branch name is 43 chars | |
| BRANCH_NAME="${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" | |
| # Limit to 43 chars to allow for "sonicjs-pr-" prefix (11 chars) = 54 total | |
| # Remove trailing hyphens to avoid invalid worker names | |
| SAFE_BRANCH=$(echo "$BRANCH_NAME" | sed 's/[^a-zA-Z0-9-]/-/g' | cut -c1-43 | sed 's/-*$//') | |
| # Deploy and capture the URL (don't exit on error) | |
| set +e | |
| echo "Deploying to preview environment: sonicjs-pr-${SAFE_BRANCH}" | |
| DEPLOY_OUTPUT=$(npx wrangler deploy --name "sonicjs-pr-${SAFE_BRANCH}" 2>&1) | |
| DEPLOY_EXIT_CODE=$? | |
| set -e | |
| echo "=== Wrangler Deploy Output ===" | |
| echo "$DEPLOY_OUTPUT" | |
| echo "=== End Deploy Output ===" | |
| if [ $DEPLOY_EXIT_CODE -ne 0 ]; then | |
| echo "Error: Wrangler deploy failed with exit code $DEPLOY_EXIT_CODE" | |
| exit 1 | |
| fi | |
| # Extract the URL from wrangler output | |
| PREVIEW_URL=$(echo "$DEPLOY_OUTPUT" | grep -oP 'https://[^\s]+\.workers\.dev' | head -1) | |
| if [ -z "$PREVIEW_URL" ]; then | |
| echo "Failed to extract preview URL from wrangler output" | |
| exit 1 | |
| fi | |
| echo "Preview URL: $PREVIEW_URL" | |
| echo "preview_url=$PREVIEW_URL" >> $GITHUB_OUTPUT | |
| env: | |
| CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} | |
| CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }} | |
| - name: Wait for preview deployment | |
| if: github.actor != 'dependabot[bot]' | |
| run: | | |
| echo "Waiting for preview to be ready..." | |
| for i in {1..30}; do | |
| if curl -s -o /dev/null -w "%{http_code}" "${{ steps.deploy.outputs.preview_url }}" | grep -q "200\|301\|302"; then | |
| echo "Preview is ready!" | |
| exit 0 | |
| fi | |
| echo "Attempt $i/30: Preview not ready yet, waiting..." | |
| sleep 10 | |
| done | |
| echo "Preview failed to become ready" | |
| exit 1 | |
| - name: Detect changed areas for E2E test selection | |
| if: github.actor != 'dependabot[bot]' | |
| id: test-tags | |
| run: | | |
| # Get changed files vs base branch (PR diff) or last commit (push) | |
| if [ "${{ github.event_name }}" = "pull_request_target" ]; then | |
| CHANGED=$(git diff --name-only origin/${{ github.base_ref }}...HEAD 2>/dev/null || echo "") | |
| else | |
| CHANGED=$(git diff --name-only HEAD~1 2>/dev/null || echo "") | |
| fi | |
| echo "Changed files:" | |
| echo "$CHANGED" | |
| # Always include smoke | |
| TAGS="@smoke" | |
| # Map changed paths to feature tags | |
| if echo "$CHANGED" | grep -qE 'admin-media|services/media|media-documents'; then | |
| TAGS="$TAGS|@media" | |
| fi | |
| if echo "$CHANGED" | grep -qE 'admin-content|services/documents|document-repository|document-projection'; then | |
| TAGS="$TAGS|@content" | |
| fi | |
| if echo "$CHANGED" | grep -qE 'routes/api|api-content|api-documents|api-media'; then | |
| TAGS="$TAGS|@api" | |
| fi | |
| if echo "$CHANGED" | grep -qE 'middleware/auth|admin-settings|api-keys|services/api-key'; then | |
| TAGS="$TAGS|@auth|@api-keys" | |
| fi | |
| if echo "$CHANGED" | grep -qE 'admin-database|database-tools'; then | |
| TAGS="$TAGS|@database" | |
| fi | |
| if echo "$CHANGED" | grep -qE 'collection-registry|admin-collections'; then | |
| TAGS="$TAGS|@collections" | |
| fi | |
| if echo "$CHANGED" | grep -qE 'migrations/'; then | |
| TAGS="$TAGS|@content|@media|@api" | |
| fi | |
| # Deduplicate tags | |
| TAGS=$(echo "$TAGS" | tr '|' '\n' | sort -u | tr '\n' '|' | sed 's/|$//') | |
| echo "Running E2E with tag filter: $TAGS" | |
| echo "grep_pattern=$TAGS" >> $GITHUB_OUTPUT | |
| - name: Install Playwright browsers | |
| if: github.actor != 'dependabot[bot]' | |
| run: npx playwright install --with-deps chromium | |
| - name: Run E2E tests against preview | |
| if: github.actor != 'dependabot[bot]' | |
| run: npx playwright test --config=tests/playwright.config.ts --grep "${{ steps.test-tags.outputs.grep_pattern }}" | |
| env: | |
| CI: true | |
| BASE_URL: ${{ steps.deploy.outputs.preview_url }} | |
| - name: Upload test results | |
| if: always() && github.actor != 'dependabot[bot]' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: playwright-report | |
| path: playwright-report/ | |
| retention-days: 7 | |
| - name: Upload test videos | |
| if: always() && github.actor != 'dependabot[bot]' | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: test-videos | |
| path: | | |
| test-results/**/*.webm | |
| playwright-report/**/*.webm | |
| retention-days: 7 |