name: 'Build & Test'
on:
  push:
    branches:
      - develop
      - master
      - release/**
  pull_request:
  workflow_dispatch:
    inputs:
      commit:
        description: If the commit you want to test isn't the head of a branch, provide its SHA here
        required: false
  schedule:
    # Run every day at midnight (without cache)
    - cron: '0 0 * * *'

# Cancel in progress workflows on pull_requests.
# https://docs.github.com/en/actions/using-jobs/using-concurrency#example-using-a-fallback-value
concurrency:
  group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }}
  cancel-in-progress: true

env:
  HEAD_COMMIT: ${{ github.event.inputs.commit || github.sha }}

  CACHED_DEPENDENCY_PATHS: |
    ${{ github.workspace }}/node_modules
    ${{ github.workspace }}/packages/*/node_modules
    ~/.cache/ms-playwright/
    ~/.cache/mongodb-binaries/

  # DEPENDENCY_CACHE_KEY: can't be set here because we don't have access to yarn.lock

  # packages/utils/cjs and packages/utils/esm: Symlinks to the folders inside of `build`, needed for tests
  CACHED_BUILD_PATHS: |
    ${{ github.workspace }}/packages/*/build
    ${{ github.workspace }}/packages/ember/*.d.ts
    ${{ github.workspace }}/packages/gatsby/*.d.ts
    ${{ github.workspace }}/packages/core/src/version.ts
    ${{ github.workspace }}/packages/serverless
    ${{ github.workspace }}/packages/utils/cjs
    ${{ github.workspace }}/packages/utils/esm

  BUILD_CACHE_KEY: ${{ github.event.inputs.commit || github.sha }}
  BUILD_CACHE_TARBALL_KEY: tarball-${{ github.event.inputs.commit || github.sha }}

  # GH will use the first restore-key it finds that matches
  # So it will start by looking for one from the same branch, else take the newest one it can find elsewhere
  # We want to prefer the cache from the current develop branch, if we don't find any on the current branch
  NX_CACHE_RESTORE_KEYS: |
    nx-Linux-${{ github.ref }}-${{ github.event.inputs.commit || github.sha }}
    nx-Linux-${{ github.ref }}
    nx-Linux

jobs:
  job_get_metadata:
    name: Get Metadata
    runs-on: ubuntu-20.04
    permissions:
      pull-requests: read
    steps:
      - name: Check out current commit
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
          # We need to check out not only the fake merge commit between the PR and the base branch which GH creates, but
          # also its parents, so that we can pull the commit message from the head commit of the PR
          fetch-depth: 2
      - name: Get metadata
        id: get_metadata
        # We need to try a number of different options for finding the head commit, because each kind of trigger event
        # stores it in a different location
        run: |
          COMMIT_SHA=$(git rev-parse --short ${{ github.event.pull_request.head.sha || github.event.head_commit.id || env.HEAD_COMMIT }})
          echo "COMMIT_SHA=$COMMIT_SHA" >> $GITHUB_ENV
          echo "COMMIT_MESSAGE=$(git log -n 1 --pretty=format:%s $COMMIT_SHA)" >> $GITHUB_ENV

      - name: Determine changed packages
        uses: getsentry/paths-filter@v2.11.1
        id: changed
        with:
          filters: |
            shared: &shared
              - '*.{js,ts,json,yml,lock}'
              - 'CHANGELOG.md'
              - '.github/**'
              - 'jest/**'
              - 'rollup/**'
              - 'scripts/**'
              - 'packages/core/**'
              - 'packages/tracing/**'
              - 'packages/tracing-internal/**'
              - 'packages/utils/**'
              - 'packages/types/**'
              - 'packages/integrations/**'
            browser: &browser
              - *shared
              - 'packages/browser/**'
              - 'packages/replay/**'
              - 'packages/feedback/**'
            browser_integration:
              - *shared
              - *browser
              - 'packages/browser-integration-tests/**'
            ember:
              - *shared
              - *browser
              - 'packages/ember/**'
            nextjs:
              - *shared
              - *browser
              - 'packages/nextjs/**'
              - 'packages/node/**'
              - 'packages/react/**'
            remix:
              - *shared
              - *browser
              - 'packages/remix/**'
              - 'packages/node/**'
              - 'packages/react/**'
            node:
              - *shared
              - 'packages/node/**'
              - 'packages/node-integration-tests/**'
            deno:
              - *shared
              - *browser
              - 'packages/deno/**'
            any_code:
              - '!**/*.md'

      - name: Get PR labels
        id: pr-labels
        uses: mydea/pr-labels-action@update-core

    outputs:
      commit_label: '${{ env.COMMIT_SHA }}: ${{ env.COMMIT_MESSAGE }}'
      changed_nextjs: ${{ steps.changed.outputs.nextjs }}
      changed_ember: ${{ steps.changed.outputs.ember }}
      changed_remix: ${{ steps.changed.outputs.remix }}
      changed_node: ${{ steps.changed.outputs.node }}
      changed_deno: ${{ steps.changed.outputs.deno }}
      changed_browser: ${{ steps.changed.outputs.browser }}
      changed_browser_integration: ${{ steps.changed.outputs.browser_integration }}
      changed_any_code: ${{ steps.changed.outputs.any_code }}
      # Note: These next three have to be checked as strings ('true'/'false')!
      is_develop: ${{ github.ref == 'refs/heads/develop' }}
      is_release: ${{ startsWith(github.ref, 'refs/heads/release/') }}
      # When merging into master, or from master
      is_gitflow_sync: ${{ github.head_ref == 'master' || github.ref == 'refs/heads/master' }}
      has_gitflow_label:
        ${{ github.event_name == 'pull_request' && contains(steps.pr-labels.outputs.labels, ' Gitflow ') }}
      force_skip_cache:
        ${{ github.event_name == 'schedule' || (github.event_name == 'pull_request' &&
        contains(steps.pr-labels.outputs.labels, ' ci-skip-cache ')) }}

  job_install_deps:
    name: Install Dependencies
    needs: job_get_metadata
    runs-on: ubuntu-20.04
    timeout-minutes: 15
    if: |
      (needs.job_get_metadata.outputs.is_gitflow_sync == 'false' && needs.job_get_metadata.outputs.has_gitflow_label == 'false') &&
      (needs.job_get_metadata.outputs.changed_any_code == 'true' || github.event_name != 'pull_request')
    steps:
      - name: 'Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})'
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
        # we use a hash of yarn.lock as our cache key, because if it hasn't changed, our dependencies haven't changed,
        # so no need to reinstall them
      - name: Compute dependency cache key
        id: compute_lockfile_hash
        run: echo "hash=${{ hashFiles('yarn.lock') }}" >> "$GITHUB_OUTPUT"

      - name: Check dependency cache
        uses: actions/cache@v3
        id: cache_dependencies
        with:
          path: ${{ env.CACHED_DEPENDENCY_PATHS }}
          key: ${{ steps.compute_lockfile_hash.outputs.hash }}

      - name: Install dependencies
        if: steps.cache_dependencies.outputs.cache-hit != 'true'
        run: yarn install --ignore-engines --frozen-lockfile
    outputs:
      dependency_cache_key: ${{ steps.compute_lockfile_hash.outputs.hash }}

  job_check_branches:
    name: Check PR branches
    needs: job_get_metadata
    runs-on: ubuntu-20.04
    if: github.event_name == 'pull_request'
    permissions:
      pull-requests: write
    steps:
      - name: PR is opened against master
        uses: mshick/add-pr-comment@a65df5f64fc741e91c59b8359a4bc56e57aaf5b1
        if: ${{ github.base_ref == 'master' && !startsWith(github.head_ref, 'prepare-release/') }}
        with:
          message: |
            ⚠️ This PR is opened against **master**. You probably want to open it against **develop**.

  job_build:
    name: Build
    needs: [job_get_metadata, job_install_deps]
    runs-on: ubuntu-20.04
    timeout-minutes: 30
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Check dependency cache
        uses: actions/cache/restore@v3
        with:
          path: ${{ env.CACHED_DEPENDENCY_PATHS }}
          key: ${{ needs.job_install_deps.outputs.dependency_cache_key }}
          fail-on-cache-miss: true

      - name: Check build cache
        uses: actions/cache@v3
        id: cache_built_packages
        with:
          path: ${{ env.CACHED_BUILD_PATHS }}
          key: ${{ env.BUILD_CACHE_KEY }}

      - name: NX cache
        uses: actions/cache@v3
        # Disable cache when:
        # - on release branches
        # - when PR has `ci-skip-cache` label or on nightly builds
        if: |
          needs.job_get_metadata.outputs.is_release == 'false' &&
          needs.job_get_metadata.outputs.force_skip_cache == 'false'
        with:
          path: .nxcache
          key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }}
          # On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it
          restore-keys:
            ${{needs.job_get_metadata.outputs.is_develop == 'false' && env.NX_CACHE_RESTORE_KEYS || 'nx-never-restore'}}

      - name: Build packages
        # Under normal circumstances, using the git SHA as a cache key, there shouldn't ever be a cache hit on the built
        # packages, and so `yarn build` should always run. This `if` check is therefore only there for testing CI issues
        # where the built packages are beside the point. In that case, you can change `BUILD_CACHE_KEY` (at the top of
        # this file) to a constant and skip rebuilding all of the packages each time CI runs.
        if: steps.cache_built_packages.outputs.cache-hit == ''
        run: yarn build
    outputs:
      # this needs to be passed on, because the `needs` context only looks at direct ancestors (so steps which depend on
      # `job_build` can't see `job_install_deps` and what it returned)
      dependency_cache_key: ${{ needs.job_install_deps.outputs.dependency_cache_key }}

  job_size_check:
    name: Size Check
    needs: [job_get_metadata, job_build]
    timeout-minutes: 15
    runs-on: ubuntu-20.04
    if:
      github.event_name == 'pull_request' || needs.job_get_metadata.outputs.is_develop == 'true' ||
      needs.job_get_metadata.outputs.is_release == 'true'
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          # The size limit action runs `yarn` and `yarn build` when this job is executed on
          # use Node 14 for now.
          node-version: '14'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Check bundle sizes
        uses: getsentry/size-limit-action@runForBranch
        with:
          github_token: ${{ secrets.GITHUB_TOKEN }}
          skip_step: build
          main_branch: develop
          # When on release branch, we want to always run
          # Else, we fall back to the default handling of the action
          run_for_branch: ${{ (needs.job_get_metadata.outputs.is_release == 'true' && 'true') || '' }}

  job_lint:
    name: Lint
    # Even though the linter only checks source code, not built code, it needs the built code in order check that all
    # inter-package dependencies resolve cleanly.
    needs: [job_get_metadata, job_build]
    timeout-minutes: 10
    runs-on: ubuntu-20.04
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Lint source files
        run: yarn lint
      - name: Validate ES5 builds
        run: yarn validate:es5

  job_circular_dep_check:
    name: Circular Dependency Check
    needs: [job_get_metadata, job_build]
    timeout-minutes: 10
    runs-on: ubuntu-20.04
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Run madge
        run: yarn circularDepCheck

  job_artifacts:
    name: Upload Artifacts
    needs: [job_get_metadata, job_build]
    runs-on: ubuntu-20.04
    # Build artifacts are only needed for releasing workflow.
    if: needs.job_get_metadata.outputs.is_release == 'true'
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Pack
        run: yarn build:tarball
      - name: Archive artifacts
        uses: actions/upload-artifact@v3.1.3
        with:
          name: ${{ github.sha }}
          path: |
            ${{ github.workspace }}/packages/browser/build/bundles/**
            ${{ github.workspace }}/packages/integrations/build/bundles/**
            ${{ github.workspace }}/packages/replay/build/bundles/**
            ${{ github.workspace }}/packages/**/*.tgz
            ${{ github.workspace }}/packages/serverless/build/aws/dist-serverless/*.zip

  job_browser_unit_tests:
    name: Browser Unit Tests
    needs: [job_get_metadata, job_build]
    timeout-minutes: 10
    runs-on: ubuntu-latest
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Run tests
        env:
          NODE_VERSION: 16
        run: yarn test-ci-browser
      - name: Compute test coverage
        uses: codecov/codecov-action@v3

  job_bun_unit_tests:
    name: Bun Unit Tests
    needs: [job_get_metadata, job_build]
    timeout-minutes: 10
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Set up Bun
        uses: oven-sh/setup-bun@v1
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Run tests
        run: |
          yarn test-ci-bun
      - name: Compute test coverage
        uses: codecov/codecov-action@v3

  job_deno_unit_tests:
    name: Deno Unit Tests
    needs: [job_get_metadata, job_build]
    if: needs.job_get_metadata.outputs.changed_deno == 'true' || github.event_name != 'pull_request'
    timeout-minutes: 10
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Set up Deno
        uses: denoland/setup-deno@v1.1.3
        with:
          deno-version: v1.37.1
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Run tests
        run: |
          cd packages/deno
          yarn build
          yarn test
      - name: Compute test coverage
        uses: codecov/codecov-action@v3

  job_node_unit_tests:
    name: Node (${{ matrix.node }}) Unit Tests
    needs: [job_get_metadata, job_build]
    timeout-minutes: 10
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
      matrix:
        node: [8, 10, 12, 14, 16, 18, 20]
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Run tests
        env:
          NODE_VERSION: ${{ matrix.node }}
        run: |
          [[ $NODE_VERSION == 8 ]] && yarn add --dev --ignore-engines --ignore-scripts --ignore-workspace-root-check ts-node@8.10.2
          yarn test-ci-node
      - name: Compute test coverage
        uses: codecov/codecov-action@v3

  job_nextjs_integration_test:
    name: Nextjs (Node ${{ matrix.node }}) Tests
    needs: [job_get_metadata, job_build]
    if: needs.job_get_metadata.outputs.changed_nextjs == 'true' || github.event_name != 'pull_request'
    timeout-minutes: 25
    runs-on: ubuntu-20.04
    strategy:
      fail-fast: false
      matrix:
        node: [10, 12, 14, 16, 18, 20]
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Get npm cache directory
        id: npm-cache-dir
        run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
      - name: Get Playwright version
        id: playwright-version
        run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT
      - uses: actions/cache@v3
        name: Check if Playwright browser is cached
        id: playwright-cache
        with:
          path: ${{ steps.npm-cache-dir.outputs.dir }}
          key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}}
      - name: Install Playwright browser if not cached
        if: steps.playwright-cache.outputs.cache-hit != 'true' && matrix.node >= 14
        run: npx playwright install --with-deps
        env:
          PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}}
      - name: Install OS dependencies of Playwright if cache hit
        if: steps.playwright-cache.outputs.cache-hit == 'true' && matrix.node >= 14
        run: npx playwright install-deps
      - name: Run tests
        env:
          NODE_VERSION: ${{ matrix.node }}
        run: |
          cd packages/nextjs
          yarn test:integration

  job_browser_playwright_tests:
    name: Playwright (${{ matrix.bundle }}) Tests
    needs: [job_get_metadata, job_build]
    if: needs.job_get_metadata.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request'
    runs-on: ubuntu-20.04
    timeout-minutes: 25
    strategy:
      fail-fast: false
      matrix:
        bundle:
          - esm
          - cjs
          - bundle_es5
          - bundle_es5_min
          - bundle_es6
          - bundle_es6_min
          - bundle_replay_es6
          - bundle_replay_es6_min
          - bundle_tracing_es5
          - bundle_tracing_es5_min
          - bundle_tracing_es6
          - bundle_tracing_es6_min
          - bundle_tracing_replay_es6
          - bundle_tracing_replay_es6_min

    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Get npm cache directory
        id: npm-cache-dir
        run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
      - name: Get Playwright version
        id: playwright-version
        run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT
      - uses: actions/cache@v3
        name: Check if Playwright browser is cached
        id: playwright-cache
        with:
          path: ${{ steps.npm-cache-dir.outputs.dir }}
          key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}}
      - name: Install Playwright browser if not cached
        if: steps.playwright-cache.outputs.cache-hit != 'true'
        run: npx playwright install --with-deps
        env:
          PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}}
      - name: Install OS dependencies of Playwright if cache hit
        if: steps.playwright-cache.outputs.cache-hit == 'true'
        run: npx playwright install-deps
      - name: Run Playwright tests
        env:
          PW_BUNDLE: ${{ matrix.bundle }}
        run: |
          cd packages/browser-integration-tests
          yarn test:ci

  job_browser_loader_tests:
    name: Playwright Loader (${{ matrix.bundle }}) Tests
    needs: [job_get_metadata, job_build]
    if: needs.job_get_metadata.outputs.changed_browser_integration == 'true' || github.event_name != 'pull_request'
    runs-on: ubuntu-20.04
    timeout-minutes: 15
    strategy:
      fail-fast: false
      matrix:
        bundle:
          - loader_base
          - loader_eager
          - loader_debug
          - loader_tracing
          - loader_replay
          - loader_tracing_replay

    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Get npm cache directory
        id: npm-cache-dir
        run: echo "dir=$(npm config get cache)" >> $GITHUB_OUTPUT
      - name: Get Playwright version
        id: playwright-version
        run: echo "version=$(node -p "require('@playwright/test/package.json').version")" >> $GITHUB_OUTPUT
      - uses: actions/cache@v3
        name: Check if Playwright browser is cached
        id: playwright-cache
        with:
          path: ${{ steps.npm-cache-dir.outputs.dir }}
          key: ${{ runner.os }}-Playwright-${{steps.playwright-version.outputs.version}}
      - name: Install Playwright browser if not cached
        if: steps.playwright-cache.outputs.cache-hit != 'true'
        run: npx playwright install --with-deps
        env:
          PLAYWRIGHT_BROWSERS_PATH: ${{steps.npm-cache-dir.outputs.dir}}
      - name: Install OS dependencies of Playwright if cache hit
        if: steps.playwright-cache.outputs.cache-hit == 'true'
        run: npx playwright install-deps
      - name: Run Playwright Loader tests
        env:
          PW_BUNDLE: ${{ matrix.bundle }}
        run: |
          cd packages/browser-integration-tests
          yarn test:loader

  job_browser_integration_tests:
    name: Browser (${{ matrix.browser }}) Tests
    needs: [job_get_metadata, job_build]
    if: needs.job_get_metadata.outputs.changed_browser == 'true' || github.event_name != 'pull_request'
    runs-on: ubuntu-20.04
    timeout-minutes: 20
    strategy:
      fail-fast: false
      matrix:
        browser:
          - ChromeHeadless
          - FirefoxHeadless
          - WebkitHeadless
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Run integration tests
        env:
          KARMA_BROWSER: ${{ matrix.browser }}
        run: |
          cd packages/browser
          [[ $KARMA_BROWSER == WebkitHeadless ]] && yarn run playwright install-deps webkit
          yarn test:integration

  job_browser_build_tests:
    name: Browser Build Tests
    needs: [job_get_metadata, job_build]
    runs-on: ubuntu-20.04
    timeout-minutes: 5
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Run browser build tests
        run: |
          cd packages/browser
          yarn test:package
      - name: Run utils build tests
        run: |
          cd packages/utils
          yarn test:package

  job_node_integration_tests:
    name:
      Node (${{ matrix.node }})${{ (matrix.typescript && format(' (TS {0})', matrix.typescript)) || '' }} Integration
      Tests
    needs: [job_get_metadata, job_build]
    if: needs.job_get_metadata.outputs.changed_node == 'true' || github.event_name != 'pull_request'
    runs-on: ubuntu-20.04
    timeout-minutes: 15
    strategy:
      fail-fast: false
      matrix:
        node: [10, 12, 14, 16, 18, 20]
        typescript:
          - false
        include:
          # Only check typescript for latest version (to streamline CI)
          - node: 20
            typescript: '3.8'
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}

      - name: Overwrite typescript version
        if: matrix.typescript
        run: yarn add --dev --ignore-workspace-root-check typescript@${{ matrix.typescript }}

      - name: Run integration tests
        env:
          NODE_VERSION: ${{ matrix.node }}
        run: |
          cd packages/node-integration-tests
          yarn test

  job_remix_integration_tests:
    name: Remix v${{ matrix.remix }} (Node ${{ matrix.node }}) Tests
    needs: [job_get_metadata, job_build]
    if: needs.job_get_metadata.outputs.changed_remix == 'true' || github.event_name != 'pull_request'
    runs-on: ubuntu-20.04
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        node: [14, 16, 18, 20]
        remix: [1, 2]
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version: ${{ matrix.node }}
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: Run integration tests
        env:
          NODE_VERSION: ${{ matrix.node }}
          REMIX_VERSION: ${{ matrix.remix }}
        run: |
          cd packages/remix
          yarn test:integration:ci

  job_e2e_prepare:
    name: Prepare E2E tests
    if:
      (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) &&
      github.actor != 'dependabot[bot]'
    needs: [job_get_metadata, job_build]
    runs-on: ubuntu-20.04
    timeout-minutes: 15
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}
      - name: NX cache
        uses: actions/cache/restore@v3
        with:
          path: .nxcache
          key: nx-Linux-${{ github.ref }}-${{ env.HEAD_COMMIT }}
          # On develop branch, we want to _store_ the cache (so it can be used by other branches), but never _restore_ from it
          restore-keys: ${{ env.NX_CACHE_RESTORE_KEYS }}
      - name: Build tarballs
        run: yarn build:tarball
      - name: Stores tarballs in cache
        uses: actions/cache/save@v3
        with:
          path: ${{ github.workspace }}/packages/*/*.tgz
          key: ${{ env.BUILD_CACHE_TARBALL_KEY }}

  job_e2e_tests:
    name: E2E ${{ matrix.label || matrix.test-application }} Test
    # We only run E2E tests for non-fork PRs because the E2E tests require secrets to work and they can't be accessed from forks
    # Dependabot PRs sadly also don't have access to secrets, so we skip them as well
    if:
      (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) &&
      github.actor != 'dependabot[bot]'
    needs: [job_get_metadata, job_build, job_e2e_prepare]
    runs-on: ubuntu-20.04
    timeout-minutes: 10
    env:
      E2E_TEST_AUTH_TOKEN: ${{ secrets.E2E_TEST_AUTH_TOKEN }}
      E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }}
      # Needed because some apps expect a certain prefix
      NEXT_PUBLIC_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }}
      PUBLIC_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }}
      REACT_APP_E2E_TEST_DSN: ${{ secrets.E2E_TEST_DSN }}
      E2E_TEST_SENTRY_ORG_SLUG: 'sentry-javascript-sdks'
      E2E_TEST_SENTRY_TEST_PROJECT: 'sentry-javascript-e2e-tests'
    strategy:
      fail-fast: false
      matrix:
        test-application:
          [
            'node-express-app',
            'create-react-app',
            'create-next-app',
            'create-remix-app',
            'create-remix-app-v2',
            'debug-id-sourcemaps',
            'nextjs-app-dir',
            'react-create-hash-router',
            'react-router-6-use-routes',
            'standard-frontend-react',
            'standard-frontend-react-tracing-import',
            'sveltekit',
            'generic-ts3.8',
            'node-experimental-fastify-app',
          ]
        build-command:
          - false
        label:
          - false
        # Add any variations of a test app here
        # You should provide an alternate build-command as well as a matching label
        include:
          - test-application: 'create-react-app'
            build-command: 'test:build-ts3.8'
            label: 'create-react-app (TS 3.8)'
          - test-application: 'standard-frontend-react'
            build-command: 'test:build-ts3.8'
            label: 'standard-frontend-react (TS 3.8)'
          - test-application: 'create-next-app'
            build-command: 'test:build-13'
            label: 'create-next-app (next@13)'
          - test-application: 'nextjs-app-dir'
            build-command: 'test:build-13'
            label: 'nextjs-app-dir (next@13)'

    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - uses: pnpm/action-setup@v2
        with:
          version: 8.3.1
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'packages/e2e-tests/package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}

      - name: Restore tarball cache
        uses: actions/cache/restore@v3
        with:
          path: ${{ github.workspace }}/packages/*/*.tgz
          key: ${{ env.BUILD_CACHE_TARBALL_KEY }}

      - name: Get node version
        id: versions
        run: |
          echo "echo node=$(jq -r '.volta.node' packages/e2e-tests/package.json)" >> $GITHUB_OUTPUT

      - name: Validate Verdaccio
        run: yarn test:validate
        working-directory: packages/e2e-tests

      - name: Prepare Verdaccio
        run: yarn test:prepare
        working-directory: packages/e2e-tests
        env:
          E2E_TEST_PUBLISH_SCRIPT_NODE_VERSION: ${{ steps.versions.outputs.node }}

      - name: Build E2E app
        working-directory: packages/e2e-tests/test-applications/${{ matrix.test-application }}
        timeout-minutes: 5
        run: yarn ${{ matrix.build-command || 'test:build' }}

      - name: Run E2E test
        working-directory: packages/e2e-tests/test-applications/${{ matrix.test-application }}
        timeout-minutes: 5
        run: yarn test:assert

  job_required_jobs_passed:
    name: All required jobs passed or were skipped
    needs:
      [
        job_build,
        job_browser_build_tests,
        job_browser_unit_tests,
        job_bun_unit_tests,
        job_deno_unit_tests,
        job_node_unit_tests,
        job_nextjs_integration_test,
        job_node_integration_tests,
        job_browser_playwright_tests,
        job_browser_integration_tests,
        job_browser_loader_tests,
        job_remix_integration_tests,
        job_e2e_tests,
        job_artifacts,
        job_lint,
        job_circular_dep_check,
      ]
    # Always run this, even if a dependent job failed
    if: always()
    runs-on: ubuntu-20.04
    steps:
      - name: Check for failures
        if: contains(needs.*.result, 'failure')
        run: |
          echo "One of the dependent jobs have failed. You may need to re-run it." && exit 1

  overhead_metrics:
    name: Overhead metrics
    needs: [job_get_metadata, job_build]
    runs-on: ubuntu-20.04
    timeout-minutes: 30
    if: |
      contains(github.event.pull_request.labels.*.name, 'ci-overhead-measurements') ||
      needs.job_get_metadata.outputs.is_develop == 'true'
    steps:
      - name: Check out current commit (${{ needs.job_get_metadata.outputs.commit_label }})
        uses: actions/checkout@v4
        with:
          ref: ${{ env.HEAD_COMMIT }}
      - name: Set up Node
        uses: actions/setup-node@v4
        with:
          node-version-file: 'package.json'
      - name: Restore caches
        uses: ./.github/actions/restore-cache
        env:
          DEPENDENCY_CACHE_KEY: ${{ needs.job_build.outputs.dependency_cache_key }}

      - name: Collect
        run: yarn ci:collect
        working-directory: packages/overhead-metrics

      - name: Process
        id: process
        run: yarn ci:process
        working-directory: packages/overhead-metrics
        # Don't run on forks - the PR comment cannot be added.
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
        env:
          GITHUB_TOKEN: ${{ github.token }}

      - name: Upload results
        uses: actions/upload-artifact@v3.1.3
        if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository
        with:
          name: ${{ steps.process.outputs.artifactName }}
          path: ${{ steps.process.outputs.artifactPath }}
