next.js/.github/workflows/build_reusable.yml
build_reusable.yml438 lines17.1 KB
name: Build Reusable

on:
  workflow_call:
    inputs:
      afterBuild:
        required: false
        description: 'additional steps to run'
        type: string
      skipInstallBuild:
        required: false
        description: 'whether to skip pnpm install && pnpm build'
        type: string
      skipNativeBuild:
        required: false
        description: 'whether to skip building native modules'
        type: string
      skipNativeInstall:
        required: false
        description: 'whether to skip native postinstall script'
        type: string
        default: 'yes'
      uploadAnalyzerArtifacts:
        required: false
        description: 'whether to upload analyzer artifacts'
        type: string
      nodeVersion:
        required: false
        description: 'version of Node.js to use'
        type: string
      needsRust:
        required: false
        description: 'if rust is needed'
        type: string
      needsNextest:
        required: false
        description: 'if nextest rust dep is needed'
        type: string
      rustBuildProfile:
        required: false
        description: 'The profile to use for the build, default is `release-with-assertions`, also supports `` for debug and `release` for normal release'
        type: string
        default: 'release-with-assertions'
      uploadSwcArtifact:
        required: false
        description: 'if swc artifact needs uploading'
        type: string
      rustCacheKey:
        required: false
        description: 'rustCacheKey to cache shared target assets'
        type: string
      stepName:
        required: true
        description: 'name of the step, to be used for the upload artifact unique key '
        type: string
      timeout_minutes:
        description: 'Timeout in minutes'
        required: false
        type: number
        default: 30
      runs_on_labels:
        description: 'List of runner labels'
        required: false
        type: string
        default: '["ubuntu-latest-16-core-oss"]'
      buildNativeTarget:
        description: 'Target for build-native step'
        required: false
        type: string
        default: 'x86_64-unknown-linux-gnu'
      overrideProxyAddress:
        description: Override the proxy address to use for the test
        required: false
        type: string
        default: ''

      testTimingsArtifact:
        description: 'Name of an uploaded artifact containing test-timings.json. When set, download it instead of fetching via turbo.'
        required: false
        type: string
        default: ''
      testReportsArtifactPrefix:
        description: 'Artifact name prefix for uploading test `*.results.json` files. Empty string disables upload.'
        required: false
        type: string
        default: ''

      uploadNativeArtifact:
        description: 'Artifact name to upload native .node files to after building. When set, this job is the native binary producer.'
        required: false
        type: string
        default: ''

      browser:
        description: 'Browser to use for tests'
        required: false
        type: string
        default: 'chromium'
env:
  NAPI_CLI_VERSION: 2.18.4
  TURBO_VERSION: 2.9.4
  TURBO_ARGS: '-v --env-mode loose --remote-cache-timeout 300 --log-order stream'
  NODE_LTS_VERSION: 20.9.0
  # run-tests.js reads `TEST_CONCURRENCY` if no explicit `--concurrency` or `-c`
  # argument is provided
  TEST_CONCURRENCY: 8
  # disable backtrace for test snapshots
  RUST_BACKTRACE: 0

  TURBO_TEAM: 'vtest314-next-adapter-e2e-tests'
  # Prefer shared remote cache across runs, but keep local cache enabled so jobs
  # degrade gracefully if the remote cache or token is unavailable.
  TURBO_CACHE: 'local:rw,remote:rw'
  TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}

  NEXT_TELEMETRY_DISABLED: 1
  # allow not skipping install-native postinstall script if we don't have a binary available already
  NEXT_SKIP_NATIVE_POSTINSTALL: ${{ inputs.skipNativeInstall == 'yes' && '1' || '' }}
  DATADOG_API_KEY: ${{ secrets.DATA_DOG_API_KEY }}
  NEXT_JUNIT_TEST_REPORT: 'true'
  DD_ENV: 'ci'
  # Vercel KV Store for test timings
  KV_REST_API_URL: ${{ secrets.KV_REST_API_URL }}
  KV_REST_API_TOKEN: ${{ secrets.KV_REST_API_TOKEN }}
  NEXT_TEST_JOB: 1
  VERCEL_TEST_TOKEN: ${{ secrets.VERCEL_TEST_TOKEN }}
  VERCEL_TEST_TEAM: vtest314-next-e2e-tests
  VERCEL_ADAPTER_TEST_TOKEN: ${{ secrets.VERCEL_ADAPTER_TEST_TOKEN }}
  VERCEL_ADAPTER_TEST_TEAM: vtest314-next-adapter-e2e-tests
  VERCEL_TURBOPACK_TEST_TOKEN: ${{ secrets.VERCEL_TURBOPACK_TEST_TOKEN }}
  VERCEL_TURBOPACK_TEST_TEAM: vtest314-next-turbo-e2e-tests
  NEXT_TEST_PREFER_OFFLINE: 1
  NEXT_CI_RUNNER: ${{ inputs.runs_on_labels }}
  NEXT_TEST_PROXY_ADDRESS: ${{ inputs.overrideProxyAddress || '' }}

  # defaults to 256, but we run a lot of tests in parallel, so the limit should be lower
  NEXT_TURBOPACK_IO_CONCURRENCY: 64

  # Disable warnings from baseline-browser-mapping
  # https://github.com/web-platform-dx/baseline-browser-mapping/blob/ec8136ae9e034b332fab991d63a340d2e13b8afc/README.md?plain=1#L34
  BASELINE_BROWSER_MAPPING_IGNORE_OLD_DATA: 1

jobs:
  build:
    timeout-minutes: ${{ inputs.timeout_minutes }}
    runs-on: ${{ fromJson(inputs.runs_on_labels) }}

    defaults:
      run:
        shell: bash -leo pipefail {0}

    outputs:
      input_step_key: ${{ steps.var.outputs.input_step_key }}

    steps:
      # enforce consistent line endings for git on windows
      - name: Configure git to use LF endings
        if: ${{ runner.os == 'Windows' }}
        run: |
          git config --global core.autocrlf false
          git config --global core.eol lf
        shell: bash

      - name: Check if fnm is installed
        id: check-fnm
        run: |
          if [ -x "$(command -v fnm)" ]; then
            echo "fnm found."
            echo "found=true" >> $GITHUB_OUTPUT
          else
            echo "fnm not found."
            echo "found=false" >> $GITHUB_OUTPUT
          fi

      - name: Install fnm
        if: steps.check-fnm.outputs.found != 'true'
        run: |
          export FNM_DIR="${HOME}/.local/share/fnm"
          curl -fsSL https://fnm.vercel.app/install | bash
          export PATH="$FNM_DIR:$PATH"
          echo "$FNM_DIR" >> $GITHUB_PATH
          fnm env --json | jq -r 'to_entries|map("\(.key)=\(.value|tostring)")|.[]' | xargs -I {} echo "{}" >> $GITHUB_ENV

      - name: Normalize input step names into path key
        uses: actions/github-script@v7
        id: var
        with:
          script: |
            core.setOutput('input_step_key', '${{ inputs.stepName }}'.toLowerCase().replaceAll(/[/.]/g, '-').trim('-'));

      - name: Use Node.js ${{ inputs.nodeVersion || env.NODE_LTS_VERSION }} for default shell
        run: |
          node --version || true
          which node || true
          # TODO: May be sufficient to just set `$FNM_MULTISHELL_PATH/bin` from `fnm env --json` into GITHUB_PATH
          fnm_env=$(fnm env)
          # Debug what we're about to eval
          echo "$fnm_env"
          eval "$fnm_env"
          fnm use --install-if-missing ${{ inputs.nodeVersion || env.NODE_LTS_VERSION }}
          fnm default ${{ inputs.nodeVersion || env.NODE_LTS_VERSION }}
          node --version
          which node
          NODE_SHELL_PATH=$(dirname "$(which node)")
          echo "Adding '$NODE_SHELL_PATH' to GITHUB_PATH"
          echo "$NODE_SHELL_PATH" >> "$GITHUB_PATH"
      # Debug used Node.js version in a separate step to ensure
      # the Node.js version is set for the entire job
      - name: Verify Node.js version
        run: |
          which node
          node --version
      - name: Prepare corepack
        if: ${{ runner.os == 'Linux' }}
        run: |
          npm i -g corepack@0.31
      - run: corepack enable
      - run: pwd

      - name: Kill stale sccache processes
        if: ${{ runner.os == 'Windows' }}
        shell: powershell
        run: Get-Process sccache -ErrorAction SilentlyContinue | Stop-Process -Force

      - run: rm -rf .git

      - uses: actions/checkout@v4
        with:
          fetch-depth: 25

      # Cache pnpm store on GitHub-hosted runners (self-hosted runners have their own persistent storage)
      - name: Get pnpm store directory
        if: ${{ runner.environment == 'github-hosted' }}
        id: get-store-path
        run: echo STORE_PATH=$(pnpm store path) >> $GITHUB_OUTPUT

      - name: Cache pnpm store
        if: ${{ runner.environment == 'github-hosted' }}
        uses: actions/cache@v4
        timeout-minutes: 5
        id: cache-pnpm-store
        with:
          path: ${{ steps.get-store-path.outputs.STORE_PATH }}
          key: pnpm-store-v2-${{ hashFiles('pnpm-lock.yaml') }}
          # Do not use restore-keys since it leads to indefinite growth of the cache.

      # local action -> needs to run after checkout
      - name: Install Rust
        uses: ./.github/actions/setup-rust
        if: ${{ (inputs.uploadNativeArtifact != '') || inputs.needsNextest == 'yes' || inputs.needsRust == 'yes' }}

      - name: Install nextest
        if: ${{ inputs.needsNextest == 'yes' }}
        run: cargo binstall --no-confirm cargo-nextest@0.9.133

      - run: rustc --version
        if: ${{ (inputs.uploadNativeArtifact != '') || inputs.needsNextest == 'yes' || inputs.needsRust == 'yes' }}

      - run: corepack prepare --activate yarn@1.22.19 && npm i -g "@napi-rs/cli@${NAPI_CLI_VERSION}"

      # clean up any previous artifacts to avoid hitting disk space limits
      - run: git clean -xdf && rm -rf /tmp/next-repo-*; rm -rf /tmp/next-install-* /tmp/yarn-* /tmp/ncc-cache target

      # Configure a git user so that Create Next App can initialize git repos during integration tests.
      - name: Set CI git user
        run: |
          git config --global user.name "vercel-ci-bot"
          git config --global user.email "infra+ci@vercel.com"

      # normalize versions before build-native for better cache hits
      - run: node scripts/normalize-version-bump.js
        name: normalize versions

      - name: Start sccache
        uses: ./.github/actions/sccache
        if: ${{ runner.os != 'Windows' && ((inputs.uploadNativeArtifact != '') || inputs.needsNextest == 'yes' || inputs.needsRust == 'yes') }}
        with:
          turbo-token: ${{ secrets.TURBO_TOKEN }}

      - name: Download pre-built native binary
        if: ${{ inputs.skipNativeBuild != 'yes' && inputs.uploadNativeArtifact == '' }}
        id: native-download
        continue-on-error: true
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: ${{ runner.os == 'Windows' && 'next-swc-windows' || 'next-swc-linux' }}
          path: packages/next-swc/native/

      - run: pnpm dlx turbo@${TURBO_VERSION} run build-native-${{ inputs.rustBuildProfile }} ${TURBO_ARGS} --summarize -- --target ${{ inputs.buildNativeTarget }}
        if: ${{ inputs.skipNativeBuild != 'yes' && (inputs.uploadNativeArtifact != '' || steps.native-download.outcome != 'success') }}

      - name: Upload native artifact
        if: ${{ inputs.uploadNativeArtifact != '' }}
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ inputs.uploadNativeArtifact }}
          path: packages/next-swc/native/next-swc.*.node
          retention-days: 1

      - name: Upload next-swc artifact
        if: ${{ inputs.uploadSwcArtifact == 'yes' }}
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: next-swc-binary
          path: packages/next-swc/native/next-swc.linux-x64-gnu.node

      # undo normalize version changes for install/build
      - run: git checkout .
        if: ${{ inputs.skipInstallBuild != 'yes' }}

      - run: pnpm install
        if: ${{ inputs.skipInstallBuild != 'yes' || inputs.needsNextest == 'yes' }}

      - name: Install node-file-trace test dependencies
        if: ${{ inputs.needsNextest == 'yes' }}
        working-directory: turbopack/crates/turbopack-tracing/tests/node-file-trace
        run: pnpm install -r --side-effects-cache false

      - run: ANALYZE=1 pnpm build
        if: ${{ inputs.skipInstallBuild != 'yes' }}

      - name: Install Playwright browsers
        if: ${{ inputs.skipInstallBuild != 'yes' }}
        run: |
          PW_VERSION=$(pnpm playwright --version 2>/dev/null || echo "unknown")
          CACHE_DIR="$HOME/.cache"
          VERSION_FILE="$CACHE_DIR/playwright-version"
          STAMP="$CACHE_DIR/playwright-deps-$(echo "${{ inputs.browser }}" | tr ' ' '-')"

          # Clear stamp files when Playwright version changes
          if [ -f "$VERSION_FILE" ] && [ "$(cat "$VERSION_FILE")" != "$PW_VERSION" ]; then
            echo "Playwright version changed ($(cat "$VERSION_FILE") -> $PW_VERSION), clearing stamps"
            rm -f "$CACHE_DIR"/playwright-deps-*
          fi

          mkdir -p "$CACHE_DIR"
          echo "$PW_VERSION" > "$VERSION_FILE"

          if [ -f "$STAMP" ]; then
            pnpm playwright install ${{ inputs.browser }}
          else
            pnpm playwright install --with-deps ${{ inputs.browser }}
            touch "$STAMP"
          fi

      - name: Download pre-built test timings
        if: ${{ inputs.testTimingsArtifact != '' }}
        uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
        with:
          name: ${{ inputs.testTimingsArtifact }}

      - name: Verify test timings
        if: ${{ inputs.testTimingsArtifact != '' }}
        run: |
          if [ ! -f test-timings.json ]; then
            echo "::error::test-timings.json not found"
            exit 1
          fi
          echo "Test timings loaded ($(wc -c < test-timings.json) bytes)"

      - name: Fetch test timings via turbo
        if: ${{ inputs.testTimingsArtifact == '' }}
        run: pnpm dlx turbo@${TURBO_VERSION} run get-test-timings -- --build ${{ github.sha }}

      - run: ${{ inputs.afterBuild }}
        # defaults.run.shell sets a stronger options (`-leo pipefail`)
        # Set this back to github action's weaker defaults:
        # https://docs.github.com/en/actions/writing-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsshell
        #
        # We must use a login shell: fnm installation may modify the `.profile`
        shell: bash -le {0}
        timeout-minutes: ${{ inputs.timeout_minutes }}

      - name: Upload test result artifacts
        if: ${{ inputs.testReportsArtifactPrefix != '' && inputs.afterBuild && always() }}
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: ${{ inputs.testReportsArtifactPrefix }}-${{ steps.var.outputs.input_step_key }}
          path: test/**/*.results.json
          if-no-files-found: ignore
          retention-days: 1

      # This file messes up the tests because it influences the build root autodetection.
      # Jest has a global cache, so PRs that poison the cache can bring down CI
      - name: Clean up stray files
        if: ${{ always() }}
        run: rm -f /tmp/package-lock.json

      - name: Upload Turborepo summary
        if: ${{ always() }}
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: turbo-run-summary-${{ steps.var.outputs.input_step_key }}
          path: .turbo/runs
          if-no-files-found: ignore

      - name: Upload bundle analyzer artifacts
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: ${{ inputs.uploadAnalyzerArtifacts == 'yes' }}
        with:
          name: webpack bundle analysis stats-${{ steps.var.outputs.input_step_key }}
          path: packages/next/dist/compiled/next-server/report.*.html

      - name: Install datadog-ci
        if: ${{ inputs.afterBuild && always() && !github.event.pull_request.head.repo.fork && env.DATADOG_API_KEY != '' }}
        uses: ./.github/actions/setup-datadog-ci

      - name: Upload test report to datadog
        if: ${{ inputs.afterBuild && always() && !github.event.pull_request.head.repo.fork && env.DATADOG_API_KEY != '' }}
        run: |
          # Add a `test.type` tag to distinguish between turbopack and next.js runs
          # Add a `nextjs.test_session.name` tag to help identify the job
          if [ -d ./test/test-junit-report ]; then
            "$DATADOG_CI_PATH" junit upload \
              --service nextjs \
              --tags test.type:nextjs \
              --tags test_session.name:"${{ inputs.stepName }}" \
              --tags runner.name:"${{ runner.name }}" \
              ./test/test-junit-report
          fi
          if [ -d ./test/turbopack-test-junit-report ]; then
            "$DATADOG_CI_PATH" junit upload \
              --service nextjs \
              --tags test.type:turbopack \
              --tags test_session.name:"${{ inputs.stepName }}" \
              --tags runner.name:"${{ runner.name }}" \
              ./test/turbopack-test-junit-report
          fi

      - name: Upload Playwright Snapshots
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        if: ${{ inputs.afterBuild && always() }}
        with:
          name: test-playwright-snapshots-${{ steps.var.outputs.input_step_key }}
          path: |
            test/traces
          if-no-files-found: ignore
Quest for Codev2.0.0
/
SIGN IN